Documentation
¶
Overview ¶
Package security holds notifycat's inbound-request authentication: signature verifiers for the signed webhooks it receives. It exposes a provider-agnostic SignatureVerifier port and the GitHub and Bitbucket raw-body HMAC-SHA256 adapters (identical scheme; they differ only in the header the middleware reads).
Index ¶
Constants ¶
const ( SignatureHeader = "X-Hub-Signature-256" SignatureHeaderBitbucket = "X-Hub-Signature" )
SignatureHeader is the HTTP header GitHub uses to carry the HMAC-SHA256 digest of the raw request body. SignatureHeaderBitbucket is Bitbucket's equivalent — same "sha256=<hex>" scheme, different header name.
const SlackDefaultMaxAge = 5 * time.Minute
SlackDefaultMaxAge is Slack's documented replay window: requests whose timestamp is more than five minutes from now (in either direction, to absorb clock skew) are rejected before the HMAC is checked.
const SlackSignatureHeader = "X-Slack-Signature"
SlackSignatureHeader carries Slack's "v0=<hex>" request signature.
const SlackTimestampHeader = "X-Slack-Request-Timestamp"
SlackTimestampHeader carries the Unix-seconds timestamp Slack signed into the base string. It is part of the signed payload, so trusting it for replay protection is safe once the signature checks out.
Variables ¶
var ErrInvalidSignature = errors.New("security: invalid signature")
ErrInvalidSignature is returned when a signature does not match.
var ErrStaleTimestamp = errors.New("security: stale timestamp")
ErrStaleTimestamp is returned when the request timestamp falls outside the replay window. Kept distinct from ErrInvalidSignature so callers and tests can tell a replayed (or badly clock-skewed) request from a forged one, even though both map to 401 at the HTTP layer.
Functions ¶
func Sign ¶
Sign returns the "sha256=<hex>" HMAC of body under secret — the value GitHub puts in X-Hub-Signature-256. It is the inverse of Verify and shares the same scheme, so anything Sign produces, Verify accepts. Used by the smoke command to forge a correctly-signed request against the live endpoint.
Types ¶
type BitbucketVerifier ¶ added in v0.22.0
type BitbucketVerifier struct {
// contains filtered or unexported fields
}
BitbucketVerifier checks Bitbucket's HMAC-SHA256 signatures against a shared secret. The scheme is identical to GitHub's ("sha256=<hex>" over the raw body); the two differ only in the header the middleware reads (SignatureHeaderBitbucket vs SignatureHeader), so both delegate to the same verification core.
func NewBitbucketVerifier ¶ added in v0.22.0
func NewBitbucketVerifier(secret string) *BitbucketVerifier
NewBitbucketVerifier returns a BitbucketVerifier configured with the shared secret configured on the Bitbucket webhook (BITBUCKET_WEBHOOK_SECRET).
func (*BitbucketVerifier) Verify ¶ added in v0.22.0
func (v *BitbucketVerifier) Verify(body []byte, signature string) error
Verify checks that signature is a valid "sha256=<hex>" HMAC of body using the verifier's secret. Returns ErrInvalidSignature for any mismatch; the comparison runs in constant time.
type GitHubVerifier ¶
type GitHubVerifier struct {
// contains filtered or unexported fields
}
GitHubVerifier checks GitHub's HMAC-SHA256 signatures against a shared secret.
func NewGitHubVerifier ¶
func NewGitHubVerifier(secret string) *GitHubVerifier
NewGitHubVerifier returns a GitHubVerifier configured with the shared secret.
func (*GitHubVerifier) Verify ¶
func (v *GitHubVerifier) Verify(body []byte, signature string) error
Verify checks that signature is a valid "sha256=<hex>" HMAC of body using the verifier's secret. Returns ErrInvalidSignature for any mismatch. The comparison runs in constant time to prevent timing oracles.
type SignatureVerifier ¶
SignatureVerifier verifies a signed request body against its signature header.
type SlackOption ¶
type SlackOption func(*SlackVerifier)
SlackOption configures a SlackVerifier.
func WithSlackClock ¶
func WithSlackClock(now func() time.Time) SlackOption
WithSlackClock overrides the time source used for replay-window checks. Tests inject a fixed clock; production uses time.Now.
func WithSlackMaxAge ¶
func WithSlackMaxAge(d time.Duration) SlackOption
WithSlackMaxAge overrides the replay window. Defaults to SlackDefaultMaxAge.
type SlackVerifier ¶
type SlackVerifier struct {
// contains filtered or unexported fields
}
SlackVerifier checks Slack request signatures against a shared signing secret and enforces the replay window.
To satisfy SignatureVerifier, Verify accepts a compound signature of the form "<unix-timestamp>\n<v0=hex>" — the slackhook middleware builds this string from the two Slack request headers before calling Verify.
func NewSlackVerifier ¶
func NewSlackVerifier(secret string, opts ...SlackOption) *SlackVerifier
NewSlackVerifier returns a SlackVerifier configured with the given signing secret.
func (*SlackVerifier) Verify ¶
func (v *SlackVerifier) Verify(body []byte, sig string) error
Verify checks that sig is a valid compound Slack signature of the form "<unix-timestamp>\n<v0=hex>". It checks that the timestamp is within the replay window and that the HMAC of Slack's base string ("v0:{timestamp}:{rawBody}") matches.
The staleness check runs first — the timestamp is part of the signed base string, so a forged-but-fresh timestamp still fails the HMAC. The HMAC comparison runs in constant time to prevent timing oracles.
Returns ErrStaleTimestamp for a timestamp outside the window and ErrInvalidSignature for any signature mismatch or malformed input.