Documentation
¶
Overview ¶
Package webhook turns one delivery into a version; expressions reach it compiled, so one can build a string but never decide that a signature is valid.
Index ¶
Constants ¶
const Custom = "custom"
Custom is the provider whose scheme is the resource's own source.signature.
const DefaultMaxBody = 1 << 20
DefaultMaxBody is the body cap when a resource sets no max_body.
const Tolerance = 5 * time.Minute
Tolerance is how far a signed timestamp may sit from now, either way. Without it a captured delivery replays forever, which is the hole pocketci left by signing the timestamp and never reading it.
Variables ¶
var ( ErrUnauthorized = errors.New("webhook: signature did not verify") // ErrTooLarge is a body over the resource's max_body. ErrTooLarge = errors.New("webhook: body too large") // ErrExpression is a filter:/id:/version: that failed to evaluate against this delivery: the pipeline's fault, not the sender's, so it is answered 500 and the delivery can be redelivered once the pipeline is fixed. ErrExpression = errors.New("webhook: expression failed") )
var Providers = map[string]Provider{ "github": { Verify: hmacBody("X-Hub-Signature-256", "sha256="), ID: header("X-GitHub-Delivery"), Event: header("X-GitHub-Event"), Signed: []string{"X-Hub-Signature-256", "X-Hub-Signature"}, }, "gitlab": { Verify: token("X-Gitlab-Token", ""), ID: header("X-Gitlab-Event-UUID"), Event: header("X-Gitlab-Event"), Signed: []string{"X-Gitlab-Token"}, }, "gitea": { Verify: hmacBody("X-Gitea-Signature", ""), ID: header("X-Gitea-Delivery"), Event: header("X-Gitea-Event"), Signed: []string{"X-Gitea-Signature", "X-Gogs-Signature", "X-Hub-Signature-256", "X-Hub-Signature"}, }, "forgejo": { Verify: hmacBody("X-Forgejo-Signature", ""), ID: header("X-Forgejo-Delivery"), Event: header("X-Forgejo-Event"), Signed: []string{"X-Forgejo-Signature", "X-Gitea-Signature", "X-Gogs-Signature", "X-Hub-Signature-256", "X-Hub-Signature"}, }, "bitbucket": { Verify: hmacBody("X-Hub-Signature", "sha256="), ID: header("X-Request-UUID"), Event: header("X-Event-Key"), Signed: []string{"X-Hub-Signature"}, }, "slack": { Verify: slackVerify, ID: jsonField("event_id"), Event: slackEvent, Handshake: slackHandshake, Signed: []string{"X-Slack-Signature"}, }, "stripe": { Verify: stripeVerify, ID: jsonField("id"), Event: jsonField("type"), Signed: []string{"Stripe-Signature"}, }, "sentry": { Verify: hmacBody("Sentry-Hook-Signature", ""), ID: header("Request-ID"), Event: header("Sentry-Hook-Resource"), Signed: []string{"Sentry-Hook-Signature"}, }, "linear": { Verify: hmacBody("Linear-Signature", ""), ID: header("Linear-Delivery"), Event: header("Linear-Event"), Signed: []string{"Linear-Signature"}, }, "pagerduty": { Verify: pagerdutyVerify, ID: jsonField("event", "id"), Event: jsonField("event", "event_type"), Signed: []string{"X-PagerDuty-Signature"}, }, "honeybadger": { Verify: token("Honeybadger-Token", ""), ID: none, Event: jsonField("event"), Signed: []string{"Honeybadger-Token"}, }, "standard-webhooks": { Verify: standardVerify, ID: header("Webhook-Id"), Event: jsonField("type"), Secret: standardSecret, Signed: []string{"Webhook-Signature"}, }, "token": { Verify: token("Authorization", "Bearer "), ID: none, Event: none, Signed: []string{"Authorization"}, }, }
Providers is every signature scheme a resource may declare. A provider is declared by the pipeline, never detected from headers the sender controls.
Functions ¶
func ProviderNames ¶
func ProviderNames() []string
ProviderNames is every value source.provider may take, sorted: the table, and custom.
Types ¶
type Input ¶
type Input struct {
Provider string
Event string
Method string
Headers map[string]string
Query map[string]string
Body string
Payload any
}
Input is what an expression sees: the delivery, already verified.
type Provider ¶
type Provider struct {
// Verify reports whether the delivery was signed with secret. now is for the schemes that sign a timestamp.
Verify func(r Request, secret []byte, now time.Time) bool
// ID is the sender's own delivery id, or "" when it sends none.
ID func(r Request) string
// Event is what the sender says happened, or "".
Event func(r Request) string
// Handshake, when set, answers a verified delivery that is the sender checking the URL rather than reporting anything.
Handshake func(r Request) ([]byte, bool)
// Secret, when set, turns the configured secret into the key; a nil Secret uses its bytes.
Secret func(raw string) ([]byte, error)
// Signed names the headers that carry the credential, which never reach headers.json; the first is the one Verify reads.
Signed []string
}
Provider is one sender's signature scheme and where it says what happened.
type Receiver ¶
type Receiver struct {
Provider string
// Scheme is provider: custom's signature, and nil for every other provider.
Scheme *Scheme
// SecretEnv names the variable holding the secret; the caller reads it, per delivery, so a rotated secret needs no reload.
SecretEnv string
MaxBody int64
// Filter, when set, drops a delivery it answers false for.
Filter func(Input) (bool, error)
// ID, when set, replaces the sender's delivery id.
ID func(Input) (string, error)
// Version projects extra fields into the version, by name.
Version map[string]func(Input) (string, error)
}
Receiver is one webhook resource, compiled.
func (*Receiver) Accept ¶
Accept runs a delivery that has already been verified through the pipeline's own expressions, in order: handshake, filter, id, version.
type Request ¶
Request is one delivery as it arrived.
func ParseRequest ¶
ParseRequest reads a captured delivery: a request line, headers, a blank line, and the body — everything after the blank line, byte for byte, whatever Content-Length says, since a hand-written capture rarely carries one.
type Result ¶
Result is what one delivery came to. At most one of Handshake and Filtered is set; with neither, Delivery is to be recorded.
type Scheme ¶
type Scheme struct {
Header string
Prefix string
Encoding string
Algorithm string
// Signed builds the message the sender signed; nil means the raw body.
Signed func(Input) (string, error)
// Timestamp, when set, is the unix time the sender signed, refused outside Tolerance. It only protects anything when Signed includes it.
Timestamp func(Input) (string, error)
}
Scheme is an HMAC carried in one header. It is what provider: custom compiles into and what hmacBody is at its defaults (hex, sha256, the raw body signed), so a described scheme and a built-in one are checked by the same code.