diff --git a/api/v1beta1/receiver_types.go b/api/v1beta1/receiver_types.go index a19c5b214..9e23cdc4f 100644 --- a/api/v1beta1/receiver_types.go +++ b/api/v1beta1/receiver_types.go @@ -27,8 +27,7 @@ import ( type ReceiverSpec struct { // Type of webhook sender, used to determine // the validation procedure and payload deserialization. - // +kubebuilder:validation:Enum=generic;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr - // +kubebuilder:validation:Enum=generic;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr;nexus + // +kubebuilder:validation:Enum=generic;generic-hmac;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr;nexus // +required Type string `json:"type"` @@ -64,15 +63,16 @@ type ReceiverStatus struct { } const ( - GenericReceiver string = "generic" - GitHubReceiver string = "github" - GitLabReceiver string = "gitlab" - BitbucketReceiver string = "bitbucket" - HarborReceiver string = "harbor" - DockerHubReceiver string = "dockerhub" - QuayReceiver string = "quay" - GCRReceiver string = "gcr" - NexusReceiver string = "nexus" + GenericReceiver string = "generic" + GenericHMACReceiver string = "generic-hmac" + GitHubReceiver string = "github" + GitLabReceiver string = "gitlab" + BitbucketReceiver string = "bitbucket" + HarborReceiver string = "harbor" + DockerHubReceiver string = "dockerhub" + QuayReceiver string = "quay" + GCRReceiver string = "gcr" + NexusReceiver string = "nexus" ) func ReceiverReady(receiver Receiver, reason, message, url string) Receiver { diff --git a/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml b/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml index 9d9b17900..2a35234d8 100644 --- a/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml +++ b/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml @@ -104,6 +104,7 @@ spec: procedure and payload deserialization. enum: - generic + - generic-hmac - github - gitlab - bitbucket diff --git a/docs/spec/v1beta1/receiver.md b/docs/spec/v1beta1/receiver.md index 8eaaabfa2..1809c414f 100644 --- a/docs/spec/v1beta1/receiver.md +++ b/docs/spec/v1beta1/receiver.md @@ -38,15 +38,16 @@ Receiver types: ```go const ( - GenericReceiver string = "generic" - GitHubReceiver string = "github" - GitLabReceiver string = "gitlab" - BitbucketReceiver string = "bitbucket" - HarborReceiver string = "harbor" - DockerHubReceiver string = "dockerhub" - QuayReceiver string = "quay" - GCRReceiver string = "gcr" - NexusReceiver string = "nexus" + GenericReceiver string = "generic" + GenericHMACReceiver string = "generic-hmac" + GitHubReceiver string = "github" + GitLabReceiver string = "gitlab" + BitbucketReceiver string = "bitbucket" + HarborReceiver string = "harbor" + DockerHubReceiver string = "dockerhub" + QuayReceiver string = "quay" + GCRReceiver string = "gcr" + NexusReceiver string = "nexus" ) ``` @@ -102,6 +103,69 @@ spec: When the receiver type is set to `generic`, the controller will not perform token validation nor event filtering. +### Generic HMAC receiver + +```yaml +apiVersion: notification.toolkit.fluxcd.io/v1beta1 +kind: Receiver +metadata: + name: generic-hmac-receiver + namespace: default +spec: + type: generic-hmac + secretRef: + name: webhook-token + resources: + - kind: GitRepository + name: webapp + namespace: default +``` + +This generic receiver performs token validation. The controller uses the `X-Signature` header to get +the hash signature. The signature should be prefixed with the hash function(`sha1`, `sha256`, or `sha512`) like this: +`=`. + +1. Generate hash using open ssl and sha1 +```sh +echo -n '' | openssl dgst -sha1 -hmac "aHR0cHM6Ly9ob29rcy5zbGFjay5jb20vc2VydmljZXMv" +``` +You can use the flag `sha256` or `sha512` if you want a different hash function + +This would output the hash. + +2. Send a POST request to the webhook url +``` +curl \ +-X POST \ + -H "X-Signature: sha1=" \ +-d '' +``` + +Generate hash signature using Go: + +```go +package main + +import ( + "crypto/hmac" + "fmt" + "crypto/sha1" +) + +// input is the body of the request +// key is your secret token +func GetSignature(input, key string) string { + key_for_sign := []byte(key) + h := hmac.New(sha1.New, key_for_sign) + h.Write([]byte(input)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// Don't forget to set request Headers +// req.Header.Set("X-Signature", fmt.Sprintf("sha1=%s", )) +``` + + ### GitHub receiver ```yaml diff --git a/internal/server/receiver_handlers.go b/internal/server/receiver_handlers.go index 39a429c76..0e5e96e65 100644 --- a/internal/server/receiver_handlers.go +++ b/internal/server/receiver_handlers.go @@ -110,6 +110,21 @@ func (s *ReceiverServer) validate(ctx context.Context, receiver v1beta1.Receiver switch receiver.Spec.Type { case v1beta1.GenericReceiver: return nil + case v1beta1.GenericHMACReceiver: + b, err := ioutil.ReadAll(r.Body) + if err != nil { + return fmt.Errorf("unable to read request body: %s", err) + } + + err = github.ValidateSignature(r.Header.Get("X-Signature"), b, []byte(token)) + if err != nil { + return fmt.Errorf("unable to validate signature: %s", err) + } + + s.logger.Info( + "handling event from generic-hmac wehbook", + "receiver", receiver.Name) + return nil case v1beta1.GitHubReceiver: payload, err := github.ValidatePayload(r, []byte(token)) if err != nil {