Documentation
¶
Index ¶
- Constants
- Variables
- func NoopValidator(req *http.Request) ([]byte, int)
- func ParseSpecific[T any](c Config) (T, error)
- func SHA256SignatureFromHeader(headerName string) func(req *http.Request) ([]byte, int)
- func SignHTTPRequest(header http.Header, payload []byte, secret []byte, headerName string) error
- type Config
- type Option
- func WithCounters(deniedCounter, relayedCounter, readCounter webapp.CounterInc) Option
- func WithExclusiveReads(exclusive bool) Option
- func WithExpiry(ttl, scanInterval time.Duration) Option
- func WithForwardedHeaders(names ...string) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxPayloadSize(size int64) Option
- func WithQueueSize(size int64) Option
- type Relay
- func (r *Relay) DeliveryHandler() http.Handler
- func (r *Relay) Handler(deliveryPath, relayPath string) func(w http.ResponseWriter, req *http.Request)
- func (r *Relay) PollingHandler() http.Handler
- func (r *Relay) ServeWebhook(w http.ResponseWriter, req *http.Request)
- func (r *Relay) Stop(ctx context.Context)
- func (r *Relay) WaitForWebhook(w http.ResponseWriter, req *http.Request)
- type SecretsConfig
- type Validator
Constants ¶
const ( DefaultQueueSize = 100 DefaultPayloadLimit = 1024 * 1024 // 1MB )
Variables ¶
var DefaultForwardedHeaders = []string{
"X-GitHub-Event",
"X-GitHub-Delivery",
"X-GitHub-Hook-ID",
}
DefaultForwardedHeaders are the request headers copied from an incoming webhook delivery onto the long-poll response when WithForwardedHeaders is not used. They carry the GitHub event type and delivery metadata a client needs to dispatch events. The X-Hub-Signature-256 header is deliberately omitted: the relay has already verified it, and the client has no secret to re-check.
var (
ErrWrongServiceSpecificConfig = fmt.Errorf("missing service specific config")
)
Functions ¶
func ParseSpecific ¶
func SHA256SignatureFromHeader ¶
SHA256SignatureFromHeader returns a function that extracts and decodes the HMAC SHA256 signature from the specified header in the HTTP request.
Types ¶
type Config ¶
type Config struct {
DeliveryPath string `yaml:"delivery_path" doc:"path to receive webhooks on"`
RelayPath string `yaml:"relay_path" doc:"path to read relay payloads from"`
MaxPayloadSize cmdyaml.ByteSize `yaml:"max_payload_size" doc:"maximum allowed payload size for incoming webhook requests in bytes, e.g. 1048576 for 1MB"`
MaxQueueSize int `yaml:"max_queue_size" doc:"maximum number of payloads to hold in the queue for processing, leave empty for default"`
ExclusiveReads bool `` /* 142-byte string literal not displayed */
Service string `yaml:"service" doc:"type of webhook to serve, e.g. github, etc."`
ServiceSpecific *cmdyaml.Deferred `yaml:"service_specific" doc:"additional details specific to the type of webhook being served, leave empty for default"`
}
Config represents the configuration for a webhook server.
func (Config) MarshalYAML ¶
type Option ¶
type Option func(*options)
Option is a function that configures the Relay.
func WithCounters ¶
func WithCounters(deniedCounter, relayedCounter, readCounter webapp.CounterInc) Option
WithCounters sets the counters for the Relay. If any of the counters are nil, they will be set to a no-op counter that does nothing when called. deniedCounter is incremented when a request is denied because the payload fails validation, e.g. due to an invalid signature. relayedCounter is incremented when a payload is successfully relayed to the FIFO. readCounter is incremented when a payload is successfully read from the FIFO and sent to a client.
func WithExclusiveReads ¶
WithExclusiveReads configures the long-poll endpoint to admit at most one reader at a time. While a reader is waiting for, or receiving, a delivery any additional long-poll request is rejected immediately with 409 Conflict rather than competing for deliveries. The default is to allow any number of concurrent readers, with each delivery going to exactly one of them.
func WithExpiry ¶
WithExpiry configures the relay to drop queued deliveries that have waited longer than ttl without being read by a client, guarding against unbounded staleness when no client is polling. The queue is scanned every scanInterval; if scanInterval is <= 0 it defaults to ttl. Expiry is disabled (the default) when ttl is <= 0, in which case deliveries are only dropped by the queue's drop-oldest behaviour when it is full.
func WithForwardedHeaders ¶
WithForwardedHeaders sets the request header names that are copied from an incoming webhook delivery onto the long-poll response sent to clients. It replaces DefaultForwardedHeaders; pass no names to forward none. Header matching is case-insensitive and absent headers are skipped.
func WithLogger ¶
WithLogger sets the logger for the Relay.
func WithMaxPayloadSize ¶
WithMaxPayloadSize sets the maximum allowed payload size for incoming webhook requests.
func WithQueueSize ¶
WithQueueSize sets the size of the internal buffer for relaying payloads. When the buffer is full the oldest payload is dropped.
type Relay ¶
type Relay struct {
// contains filtered or unexported fields
}
Relay is an HTTP handler that receives JSON payloads and relays them over a channel for subsequent processing. It is designed to be used in a webhook server to receive webhook payloads and relay them to another http handler that is used as a long polling endpoint for a client to receive the payloads. The Webhook endpoint will accept POST requests with JSON payloads and the Wait endpoint will accept GET requests and will block until a payload is received. When the internal buffer is full the oldest webhook is dropped to make room for the new one.
func NewRelay ¶
NewRelay creates a new Relay with the provided Validator and options. ctx governs the lifetime of the internal FIFO goroutine; cancel it or call Stop to shut down cleanly.
func (*Relay) DeliveryHandler ¶
DeliveryHandler returns an http.Handler that serves the webhook endpoint for receiving payloads.
func (*Relay) Handler ¶
func (r *Relay) Handler(deliveryPath, relayPath string) func(w http.ResponseWriter, req *http.Request)
Handler returns an http.HandlerFunc that routes requests to the appropriate handler based on the URL path. It expects the webhook endpoint to be at deliveryPath and the wait endpoint to be at relayPath. Requests to other paths will receive a 404 Not Found response.
func (*Relay) PollingHandler ¶
PollingHandler returns an http.Handler that serves the wait endpoint for long polling clients to receive payloads.
func (*Relay) ServeWebhook ¶
func (r *Relay) ServeWebhook(w http.ResponseWriter, req *http.Request)
ServeWebhook handles incoming webhook requests, validates them using the provided Validator, and relays the payload to the FIFO for processing. If the internal buffer is full the oldest payload is dropped to make room. It responds with appropriate HTTP status codes based on the validation outcome.
func (*Relay) Stop ¶
Stop shuts down the internal FIFO goroutine. It blocks until the goroutine exits or ctx is cancelled.
func (*Relay) WaitForWebhook ¶
func (r *Relay) WaitForWebhook(w http.ResponseWriter, req *http.Request)
WaitForWebhook waits for a payload to be received on the FIFO and responds with the payload as JSON. It is intended to support long polling by blocking until a webhook payload is available. If the request context is cancelled while waiting, it logs the cancellation and returns without responding. When exclusive reads are configured (see WithExclusiveReads) and another reader is already waiting, it responds immediately with 409 Conflict.
type SecretsConfig ¶
type SecretsConfig struct {
Secrets map[string][]string `` /* 176-byte string literal not displayed */
SecretSpecs []keys.KeySpec `yaml:"-" doc:"-"`
}
SecretsConfig represents the secrets used to validate incoming webhooks. Keys are users (e.g. a GitHub username or email address) and values are lists of secret IDs that identify entries in the key store. SecretSpecs is populated automatically during unmarshal and must not be set directly.
YAML format (the node itself is the map — no wrapper key):
alice@example.com: - secret-id-1 - secret-id-2 bob@example.com: - other-secret
Note that SecretsConfig cannot be inlined by a parent YAML struct, it must always be a named field.
func (SecretsConfig) MarshalYAML ¶
func (sc SecretsConfig) MarshalYAML() (any, error)
func (SecretsConfig) TokensFromContext ¶
func (*SecretsConfig) UnmarshalYAML ¶
func (sc *SecretsConfig) UnmarshalYAML(node *yaml.Node) error
type Validator ¶
Validator is called to validate and extract the webhook payload from an incoming request. It should return the payload as a byte slice and an error if validation fails.
func SignatureValidator ¶
func SignatureValidator(getSignature func(req *http.Request) ([]byte, int), getTokens func(ctx context.Context) ([]keys.Token, error)) (Validator, error)
SignatureValidator returns a Validator that verifies webhook payloads using one of possibly multiple Tokens returned by the getTokens function. The token value is a byte slice that the validator uses to compute the HMAC SHA256 signature of the payload and compare it to the signature provided in the request header as returned by the getSignature function. If a match is found, the payload is considered valid and returned; if none of the returned tokens' secrets match the signature, the payload is rejected and an appropriate HTTP status code is returned to indicate the error. It is the responsibility of the getTokens function to retrieve the tokens from the appropriate source, such as a file or a key store.