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

Feature: Add option to pass additional headers via secretRef to the generic notification provider #291

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ jobs:
- name: Run smoke tests
run: |
kubectl -n notification-system apply -f ./config/samples
kubectl -n notification-system wait provider/provider-sample --for=condition=ready --timeout=1m
kubectl -n notification-system wait provider/slack-provider-sample --for=condition=ready --timeout=1m
kubectl -n notification-system wait provider/generic-provider-sample --for=condition=ready --timeout=1m
kubectl -n notification-system wait alert/alert-sample --for=condition=ready --timeout=1m
kubectl -n notification-system wait receiver/receiver-sample --for=condition=ready --timeout=1m
- name: Logs
Expand Down
19 changes: 18 additions & 1 deletion config/samples/notification_v1beta1_provider.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
name: provider-sample
name: slack-provider-sample
spec:
type: slack
channel: general
Expand All @@ -14,3 +14,20 @@ metadata:
name: slack-url
data:
address: aHR0cHM6Ly9ob29rcy5zbGFjay5jb20vc2VydmljZXMv
---
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
name: generic-provider-sample
spec:
type: generic
address: https://api.github.com/repos/fluxcd/notification-controller/dispatches
secretRef:
name: generic-secret
---
apiVersion: v1
kind: Secret
metadata:
name: generic-secret
data:
headers: QXV0aG9yaXphdGlvbjogdG9rZW4KWC1Gb3J3YXJkZWQtUHJvdG86IGh0dHBz
12 changes: 11 additions & 1 deletion controllers/provider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"gopkg.in/yaml.v3"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this dependency and use sigs.k8s.io/yaml


"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
helper "github.com/fluxcd/pkg/runtime/controller"
Expand Down Expand Up @@ -148,6 +150,7 @@ func (r *ProviderReconciler) reconcile(ctx context.Context, obj *v1beta1.Provide
func (r *ProviderReconciler) validate(ctx context.Context, provider *v1beta1.Provider) error {
address := provider.Spec.Address
token := ""
headers := make(map[string]string)
if provider.Spec.SecretRef != nil {
var secret corev1.Secret
secretName := types.NamespacedName{Namespace: provider.Namespace, Name: provider.Spec.SecretRef.Name}
Expand All @@ -163,6 +166,13 @@ func (r *ProviderReconciler) validate(ctx context.Context, provider *v1beta1.Pro
if t, ok := secret.Data["token"]; ok {
token = string(t)
}

if h, ok := secret.Data["headers"]; ok {
err := yaml.Unmarshal(h, headers)
if err != nil {
return fmt.Errorf("failed to read headers from secret, error: %w", err)
}
}
}

if address == "" {
Expand Down Expand Up @@ -190,7 +200,7 @@ func (r *ProviderReconciler) validate(ctx context.Context, provider *v1beta1.Pro
}
}

factory := notifier.NewFactory(address, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, certPool)
factory := notifier.NewFactory(address, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, headers, certPool)
if _, err := factory.Notifier(provider.Spec.Type); err != nil {
return fmt.Errorf("failed to initialize provider, error: %w", err)
}
Expand Down
22 changes: 22 additions & 0 deletions docs/spec/v1beta1/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ The body of the request looks like this:

The `involvedObject` key contains the object that triggered the event.

```yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta1
kind: Provider
metadata:
name: generic
namespace: default
spec:
type: generic
address: https://api.github.com/repos/owner/repo/dispatches
secretRef:
name: generic-secret
---
apiVersion: v1
kind: Secret
metadata:
name: generic-secret
namespace: default
data:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use stringData e.g.

stringData:
  headers: |
     Authorization: token
     X-Forwarded-Proto: https

Also explain how the headers are used.

# 'Authorization: token\nX-Forwarded-Proto: https'
headers: QXV0aG9yaXphdGlvbjogdG9rZW4KWC1Gb3J3YXJkZWQtUHJvdG86IGh0dHBz
```

### Self-signed certificates

The `certSecretRef` field names a secret with TLS certificate data. This is for the purpose
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/whilp/git-urls v1.0.0
github.com/xanzy/go-gitlab v0.50.4
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/client-go v0.22.2
Expand Down
6 changes: 4 additions & 2 deletions internal/notifier/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ type Factory struct {
Username string
Channel string
Token string
Headers map[string]string
CertPool *x509.CertPool
}

func NewFactory(url string, proxy string, username string, channel string, token string, certPool *x509.CertPool) *Factory {
func NewFactory(url string, proxy string, username string, channel string, token string, headers map[string]string, certPool *x509.CertPool) *Factory {
return &Factory{
URL: url,
ProxyURL: proxy,
Channel: channel,
Username: username,
Token: token,
Headers: headers,
CertPool: certPool,
}
}
Expand All @@ -52,7 +54,7 @@ func (f Factory) Notifier(provider string) (Interface, error) {
var err error
switch provider {
case v1beta1.GenericProvider:
n, err = NewForwarder(f.URL, f.ProxyURL, f.CertPool)
n, err = NewForwarder(f.URL, f.ProxyURL, f.Headers, f.CertPool)
case v1beta1.SlackProvider:
n, err = NewSlack(f.URL, f.ProxyURL, f.Token, f.CertPool, f.Username, f.Channel)
case v1beta1.DiscordProvider:
Expand Down
7 changes: 6 additions & 1 deletion internal/notifier/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,29 @@ const NotificationHeader = "gotk-component"
type Forwarder struct {
URL string
ProxyURL string
Headers map[string]string
CertPool *x509.CertPool
}

func NewForwarder(hookURL string, proxyURL string, certPool *x509.CertPool) (*Forwarder, error) {
func NewForwarder(hookURL string, proxyURL string, headers map[string]string, certPool *x509.CertPool) (*Forwarder, error) {
if _, err := url.ParseRequestURI(hookURL); err != nil {
return nil, fmt.Errorf("invalid hook URL %s: %w", hookURL, err)
}

return &Forwarder{
URL: hookURL,
ProxyURL: proxyURL,
Headers: headers,
CertPool: certPool,
}, nil
}

func (f *Forwarder) Post(event events.Event) error {
err := postMessage(f.URL, f.ProxyURL, f.CertPool, event, func(req *retryablehttp.Request) {
req.Header.Set(NotificationHeader, event.ReportingController)
for key, val := range f.Headers {
req.Header.Set(key, val)
}
})

if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion internal/notifier/forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func TestForwarder_Post(t *testing.T) {
require.NoError(t, err)

require.Equal(t, "source-controller", r.Header.Get("gotk-component"))
require.Equal(t, "token", r.Header.Get("Authorization"))
var payload = events.Event{}
err = json.Unmarshal(b, &payload)
require.NoError(t, err)
Expand All @@ -42,7 +43,9 @@ func TestForwarder_Post(t *testing.T) {
}))
defer ts.Close()

forwarder, err := NewForwarder(ts.URL, "", nil)
headers := make(map[string]string)
headers["Authorization"] = "token"
forwarder, err := NewForwarder(ts.URL, "", headers, nil)
require.NoError(t, err)

err = forwarder.Post(testEvent())
Expand Down
15 changes: 14 additions & 1 deletion internal/server/event_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"encoding/json"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"net/http"
"regexp"
Expand Down Expand Up @@ -139,6 +140,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)

webhook := provider.Spec.Address
token := ""
headers := make(map[string]string)
if provider.Spec.SecretRef != nil {
var secret corev1.Secret
secretName := types.NamespacedName{Namespace: alert.Namespace, Name: provider.Spec.SecretRef.Name}
Expand All @@ -159,6 +161,17 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
if t, ok := secret.Data["token"]; ok {
token = string(t)
}

if h, ok := secret.Data["headers"]; ok {
err := yaml.Unmarshal(h, headers)
if err != nil {
s.logger.Error(err, "failed to read headers from secret",
"reconciler kind", v1beta1.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
}
}
}

var certPool *x509.CertPool
Expand Down Expand Up @@ -203,7 +216,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
continue
}

factory := notifier.NewFactory(webhook, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, certPool)
factory := notifier.NewFactory(webhook, provider.Spec.Proxy, provider.Spec.Username, provider.Spec.Channel, token, headers, certPool)
sender, err := factory.Notifier(provider.Spec.Type)
if err != nil {
s.logger.Error(err, "failed to initialize provider",
Expand Down