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

Pass headers to generic provider through secretRef #317

Merged
merged 2 commits into from
Jan 26, 2022
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
3 changes: 2 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,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
2 changes: 1 addition & 1 deletion config/samples/notification_v1beta1_alert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: alert-sample
spec:
providerRef:
name: provider-sample
name: slack-provider-sample
eventSeverity: info
eventSources:
- kind: GitRepository
Expand Down
20 changes: 19 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,21 @@ 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
stringData:
headers: |
Authorization: token
11 changes: 10 additions & 1 deletion controllers/provider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/yaml"

"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
Expand Down Expand Up @@ -148,6 +149,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 +165,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 +199,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
26 changes: 26 additions & 0 deletions docs/spec/v1beta1/provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,32 @@ The body of the request looks like this:

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

You can add additional headers to the POST request by providing a `headers` field to the secret
referenced by the provider. An example is given below:

```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
stringData:
headers: |
Authorization: token
X-Forwarded-Proto: https
```

### Self-signed certificates

The `certSecretRef` field names a secret with TLS certificate data. This is for the purpose
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ require (
sigs.k8s.io/kustomize/api v0.10.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/yaml v1.3.0
)

// Fix for CVE-2020-29652: https://github.com/golang/crypto/commit/8b5274cf687fd9316b4108863654cc57385531e8
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 @@ -30,6 +30,7 @@ import (
"github.com/fluxcd/pkg/runtime/conditions"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"

"github.com/fluxcd/pkg/runtime/events"

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
3 changes: 2 additions & 1 deletion tests/fuzz/forwarder_fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func FuzzForwarder(data []byte) int {
}))
defer ts.Close()

forwarder, err := NewForwarder(ts.URL, "", nil)
header := make(map[string]string)
forwarder, err := NewForwarder(ts.URL, "", header, nil)
if err != nil {
return 0
}
Expand Down