From 3dd667f3b3c407ab3aebe859acd7519aa5a1afd9 Mon Sep 17 00:00:00 2001 From: Ricardo Lorenzo Date: Wed, 21 Sep 2022 16:42:53 +0100 Subject: [PATCH] Slack bot token authentication Signed-off-by: Ricardo Lorenzo --- cmd/flagger/main.go | 5 ++++- docs/gitbook/usage/alerting.md | 4 ++++ pkg/controller/events.go | 10 +++++++++- pkg/notifier/client.go | 6 +++++- pkg/notifier/client_test.go | 2 +- pkg/notifier/discord.go | 2 +- pkg/notifier/factory.go | 6 ++++-- pkg/notifier/gchat.go | 2 +- pkg/notifier/rocket.go | 2 +- pkg/notifier/slack.go | 12 +++++++----- pkg/notifier/slack_test.go | 2 +- pkg/notifier/teams.go | 2 +- 12 files changed, 39 insertions(+), 16 deletions(-) diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go index fe8571e6..89825f0a 100644 --- a/cmd/flagger/main.go +++ b/cmd/flagger/main.go @@ -66,6 +66,7 @@ var ( msteamsProxyURL string includeLabelPrefix string slackURL string + slackToken string slackProxyURL string slackUser string slackChannel string @@ -97,6 +98,7 @@ func init() { flag.StringVar(&logLevel, "log-level", "debug", "Log level can be: debug, info, warning, error.") flag.StringVar(&port, "port", "8080", "Port to listen on.") flag.StringVar(&slackURL, "slack-url", "", "Slack hook URL.") + flag.StringVar(&slackToken, "slack-token", "", "Slack bot token.") flag.StringVar(&slackProxyURL, "slack-proxy-url", "", "Slack proxy URL.") flag.StringVar(&slackUser, "slack-user", "flagger", "Slack user name.") flag.StringVar(&slackChannel, "slack-channel", "", "Slack channel.") @@ -365,6 +367,7 @@ func startLeaderElection(ctx context.Context, run func(), ns string, kubeClient func initNotifier(logger *zap.SugaredLogger) (client notifier.Interface) { provider := "slack" + token := fromEnv("SLACK_TOKEN", slackToken) notifierURL := fromEnv("SLACK_URL", slackURL) notifierProxyURL := fromEnv("SLACK_PROXY_URL", slackProxyURL) if msteamsURL != "" || os.Getenv("MSTEAMS_URL") != "" { @@ -372,7 +375,7 @@ func initNotifier(logger *zap.SugaredLogger) (client notifier.Interface) { notifierURL = fromEnv("MSTEAMS_URL", msteamsURL) notifierProxyURL = fromEnv("MSTEAMS_PROXY_URL", msteamsProxyURL) } - notifierFactory := notifier.NewFactory(notifierURL, notifierProxyURL, slackUser, slackChannel) + notifierFactory := notifier.NewFactory(notifierURL, token, notifierProxyURL, slackUser, slackChannel) var err error client, err = notifierFactory.Notifier(provider) diff --git a/docs/gitbook/usage/alerting.md b/docs/gitbook/usage/alerting.md index 0e46a4b0..23ebc1a6 100644 --- a/docs/gitbook/usage/alerting.md +++ b/docs/gitbook/usage/alerting.md @@ -37,6 +37,8 @@ or if the analysis reached the maximum number of failed checks: ![Slack Notifications](https://raw.githubusercontent.com/fluxcd/flagger/main/docs/screens/slack-canary-failed.png) +For using a Slack bot token, you should add `token` to a secret and use **secretRef**. + ### Microsoft Teams Flagger can be configured to send notifications to Microsoft Teams: @@ -73,6 +75,7 @@ spec: channel: on-call-alerts username: flagger # webhook address (ignored if secretRef is specified) + # or https://slack.com/api/chat.postMessage if you use token in the secret address: https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK # optional http/s proxy proxy: http://my-http-proxy.com @@ -87,6 +90,7 @@ metadata: namespace: flagger data: address: + token: ``` The alert provider **type** can be: `slack`, `msteams`, `rocket` or `discord`. When set to `discord`, diff --git a/pkg/controller/events.go b/pkg/controller/events.go index 59b99266..ed843995 100644 --- a/pkg/controller/events.go +++ b/pkg/controller/events.go @@ -123,6 +123,10 @@ func (c *Controller) alert(canary *flaggerv1.Canary, message string, metadata bo // set hook URL address url := provider.Spec.Address + // set the token which will be sent in the header + // https://datatracker.ietf.org/doc/html/rfc6750 + token := "" + // extract address from secret if provider.Spec.SecretRef != nil { secret, err := c.kubeClient.CoreV1().Secrets(providerNamespace).Get(context.TODO(), provider.Spec.SecretRef.Name, metav1.GetOptions{}) @@ -138,6 +142,10 @@ func (c *Controller) alert(canary *flaggerv1.Canary, message string, metadata bo Errorf("alert provider %s.%s secret does not contain an address", alert.ProviderRef.Name, providerNamespace) continue } + + if tokenFromSecret, ok := secret.Data["token"]; ok { + token = string(tokenFromSecret) + } } // set defaults @@ -155,7 +163,7 @@ func (c *Controller) alert(canary *flaggerv1.Canary, message string, metadata bo } // create notifier based on provider type - f := notifier.NewFactory(url, proxy, username, channel) + f := notifier.NewFactory(url, token, proxy, username, channel) n, err := f.Notifier(provider.Spec.Type) if err != nil { c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)). diff --git a/pkg/notifier/client.go b/pkg/notifier/client.go index 5499534a..1e6d622c 100644 --- a/pkg/notifier/client.go +++ b/pkg/notifier/client.go @@ -29,7 +29,7 @@ import ( "time" ) -func postMessage(address string, proxy string, payload interface{}) error { +func postMessage(address, token, proxy string, payload interface{}) error { var httpClient = &http.Client{} if proxy != "" { @@ -64,6 +64,10 @@ func postMessage(address string, proxy string, payload interface{}) error { } req.Header.Set("Content-type", "application/json") + if token != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + } + ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second) defer cancel() diff --git a/pkg/notifier/client_test.go b/pkg/notifier/client_test.go index b3df8737..f0c1c948 100644 --- a/pkg/notifier/client_test.go +++ b/pkg/notifier/client_test.go @@ -39,6 +39,6 @@ func Test_postMessage(t *testing.T) { })) defer ts.Close() - err := postMessage(ts.URL, "", map[string]string{"status": "success"}) + err := postMessage(ts.URL, "", "", map[string]string{"status": "success"}) require.NoError(t, err) } diff --git a/pkg/notifier/discord.go b/pkg/notifier/discord.go index 9a8be1a9..35cdac22 100644 --- a/pkg/notifier/discord.go +++ b/pkg/notifier/discord.go @@ -90,7 +90,7 @@ func (s *Discord) Post(workload string, namespace string, message string, fields payload.Attachments = []SlackAttachment{a} - err := postMessage(s.URL, s.ProxyURL, payload) + err := postMessage(s.URL, "", s.ProxyURL, payload) if err != nil { return fmt.Errorf("postMessage failed: %w", err) } diff --git a/pkg/notifier/factory.go b/pkg/notifier/factory.go index 32c029bb..5b7a05ef 100644 --- a/pkg/notifier/factory.go +++ b/pkg/notifier/factory.go @@ -22,14 +22,16 @@ import ( type Factory struct { URL string + Token string ProxyURL string Username string Channel string } -func NewFactory(url string, proxy string, username string, channel string) *Factory { +func NewFactory(url, token, proxy, username, channel string) *Factory { return &Factory{ URL: url, + Token: token, ProxyURL: proxy, Channel: channel, Username: username, @@ -45,7 +47,7 @@ func (f Factory) Notifier(provider string) (Interface, error) { var err error switch provider { case "slack": - n, err = NewSlack(f.URL, f.ProxyURL, f.Username, f.Channel) + n, err = NewSlack(f.URL, f.Token, f.ProxyURL, f.Username, f.Channel) case "discord": n, err = NewDiscord(f.URL, f.ProxyURL, f.Username, f.Channel) case "rocket": diff --git a/pkg/notifier/gchat.go b/pkg/notifier/gchat.go index f5906bde..36cb9540 100644 --- a/pkg/notifier/gchat.go +++ b/pkg/notifier/gchat.go @@ -111,7 +111,7 @@ func (s *GChat) Post(workload string, namespace string, message string, fields [ }, } - err := postMessage(s.URL, s.ProxyURL, payload) + err := postMessage(s.URL, "", s.ProxyURL, payload) if err != nil { return fmt.Errorf("postMessage failed: %w", err) } diff --git a/pkg/notifier/rocket.go b/pkg/notifier/rocket.go index f9d0a6c5..ac89ccd4 100644 --- a/pkg/notifier/rocket.go +++ b/pkg/notifier/rocket.go @@ -81,7 +81,7 @@ func (s *Rocket) Post(workload string, namespace string, message string, fields payload.Attachments = []SlackAttachment{a} - err := postMessage(s.URL, s.ProxyURL, payload) + err := postMessage(s.URL, "", s.ProxyURL, payload) if err != nil { return fmt.Errorf("postMessage failed: %w", err) } diff --git a/pkg/notifier/slack.go b/pkg/notifier/slack.go index c5ca8878..51596247 100644 --- a/pkg/notifier/slack.go +++ b/pkg/notifier/slack.go @@ -25,6 +25,7 @@ import ( // Slack holds the hook URL type Slack struct { URL string + Token string ProxyURL string Username string Channel string @@ -56,10 +57,10 @@ type SlackField struct { } // NewSlack validates the Slack URL and returns a Slack object -func NewSlack(hookURL string, proxyURL string, username string, channel string) (*Slack, error) { - _, err := url.ParseRequestURI(hookURL) +func NewSlack(address, token, proxyURL, username, channel string) (*Slack, error) { + _, err := url.ParseRequestURI(address) if err != nil { - return nil, fmt.Errorf("invalid Slack hook URL %s", hookURL) + return nil, fmt.Errorf("invalid Slack hook URL %s", address) } if username == "" { @@ -72,7 +73,8 @@ func NewSlack(hookURL string, proxyURL string, username string, channel string) return &Slack{ Channel: channel, - URL: hookURL, + URL: address, + Token: token, ProxyURL: proxyURL, Username: username, }, nil @@ -106,7 +108,7 @@ func (s *Slack) Post(workload string, namespace string, message string, fields [ payload.Attachments = []SlackAttachment{a} - err := postMessage(s.URL, s.ProxyURL, payload) + err := postMessage(s.URL, s.Token, s.ProxyURL, payload) if err != nil { return fmt.Errorf("postMessage failed: %w", err) } diff --git a/pkg/notifier/slack_test.go b/pkg/notifier/slack_test.go index 1d9c7022..5c6ff4e4 100644 --- a/pkg/notifier/slack_test.go +++ b/pkg/notifier/slack_test.go @@ -44,7 +44,7 @@ func TestSlack_Post(t *testing.T) { })) defer ts.Close() - slack, err := NewSlack(ts.URL, "", "test", "test") + slack, err := NewSlack(ts.URL, "", "", "test", "test") require.NoError(t, err) err = slack.Post("podinfo", "test", "test", fields, "error") diff --git a/pkg/notifier/teams.go b/pkg/notifier/teams.go index 77701b7f..d06a2a11 100644 --- a/pkg/notifier/teams.go +++ b/pkg/notifier/teams.go @@ -86,7 +86,7 @@ func (s *MSTeams) Post(workload string, namespace string, message string, fields payload.ThemeColor = "FF0000" } - err := postMessage(s.URL, s.ProxyURL, payload) + err := postMessage(s.URL, "", s.ProxyURL, payload) if err != nil { return fmt.Errorf("postMessage failed: %w", err) }