Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic webhook receiver for HMAC signing #127

Merged
merged 3 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions api/v1beta1/receiver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ spec:
procedure and payload deserialization.
enum:
- generic
- generic-hmac
- github
- gitlab
- bitbucket
Expand Down
82 changes: 73 additions & 9 deletions docs/spec/v1beta1/receiver.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
```

Expand Down Expand Up @@ -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:
`<hash-function>=<hash-signation>`.

1. Generate hash using open ssl and sha1
```sh
echo -n '<body-of-request>' | 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 <webhook-url> \
-X POST \
-H "X-Signature: sha1=<generated-hash>" \
-d '<body-of-request>'
```

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", <returned string>))
```


### GitHub receiver

```yaml
Expand Down
15 changes: 15 additions & 0 deletions internal/server/receiver_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down