All checks were successful
check / check (push) Successful in 4m23s
A receiver URL was a bare v4 UUID and nothing else: anyone who learned it could store events and, because inbound headers are forwarded to targets almost verbatim, choose what the downstream service received. Entrypoints gain an optional scheme/secret pair. GitHub's X-Hub-Signature-256 (HMAC-SHA256 hex over the raw body) and GitLab's X-Gitlab-Token (plain shared token) are supported; both compare with hmac.Equal. With nothing configured an entrypoint behaves exactly as before, which is also where every pre-existing row lands after AutoMigrate adds the columns. Verification runs after the capped body read and before the first write, so a rejected request leaves no event row, no delivery row and no delivery task. A configuration the receiver cannot apply — unknown scheme, or one half of the pair missing — is refused with a 500 rather than falling back to unverified. The secret is credential-bearing and is stored in the clear because HMAC needs the key itself. It is excluded from JSON, kept out of templates by a new handlers.EntrypointView projection, and absent from every log line including the rejection path. The UI sets and rotates it through one form that never renders the stored value.
60 lines
2.3 KiB
Go
60 lines
2.3 KiB
Go
package database
|
|
|
|
// SignatureScheme names the way an entrypoint authenticates inbound
|
|
// requests. A scheme fixes both the header the signature arrives in
|
|
// and the algorithm used to check it, so an operator cannot pair one
|
|
// sender's header with another sender's comparison.
|
|
type SignatureScheme string
|
|
|
|
// Signature scheme values. The empty scheme means the entrypoint
|
|
// performs no inbound verification: it is the default, and it is the
|
|
// state every entrypoint created before this column existed migrates
|
|
// to, so an existing deployment keeps accepting the requests it
|
|
// accepted before.
|
|
const (
|
|
SignatureSchemeNone SignatureScheme = ""
|
|
SignatureSchemeGitHub SignatureScheme = "github"
|
|
SignatureSchemeGitLab SignatureScheme = "gitlab"
|
|
)
|
|
|
|
// Entrypoint represents an inbound URL endpoint that feeds into a webhook
|
|
type Entrypoint struct {
|
|
BaseModel
|
|
|
|
WebhookID string `gorm:"type:uuid;not null" json:"webhookId"`
|
|
|
|
// Path is the URL path for this entrypoint.
|
|
Path string `gorm:"uniqueIndex;not null" json:"path"`
|
|
|
|
Description string `json:"description"`
|
|
Active bool `gorm:"default:true" json:"active"`
|
|
|
|
// SignatureScheme selects how inbound requests to this
|
|
// entrypoint are authenticated. Empty means unauthenticated,
|
|
// which is what a UUID-only entrypoint has always been.
|
|
SignatureScheme SignatureScheme `gorm:"default:''" json:"signatureScheme"`
|
|
|
|
// SignatureSecret is the secret shared with the sender.
|
|
//
|
|
// It is stored in the clear because HMAC verification needs the
|
|
// key itself: a hash of it cannot recompute the sender's digest.
|
|
// It is therefore a live credential, and json:"-" keeps it out of
|
|
// any handler that marshals the model, the way APIKey.Key and
|
|
// Target.Config are kept out. handlers.EntrypointView is the
|
|
// matching barrier for the HTML path.
|
|
SignatureSecret string `gorm:"default:''" json:"-"`
|
|
|
|
// Relations
|
|
Webhook Webhook `json:"webhook,omitzero"`
|
|
}
|
|
|
|
// SignatureConfigured reports whether this entrypoint verifies
|
|
// inbound requests. Both halves must be present: a scheme without a
|
|
// secret, or a secret without a scheme, is a broken configuration
|
|
// rather than a configured one, and signature.Verify fails those
|
|
// closed rather than treating them as "off".
|
|
func (e *Entrypoint) SignatureConfigured() bool {
|
|
return e.SignatureScheme != SignatureSchemeNone &&
|
|
e.SignatureSecret != ""
|
|
}
|