From 6e9517e4b50dd36ae0e45c0c42bb25c5b6d03c9e Mon Sep 17 00:00:00 2001 From: Periklis Tsirakidis Date: Tue, 17 May 2022 10:00:36 +0200 Subject: [PATCH] ruler: Add support for alertmanager header authorization (#6136) --- CHANGELOG.md | 1 + docs/sources/configuration/_index.md | 20 ++++++++ pkg/ruler/base/notifier.go | 21 +++++++- pkg/ruler/base/notifier_test.go | 72 ++++++++++++++++++++++++++++ pkg/util/http.go | 18 +++++++ 5 files changed, 130 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1997ed853e2a..16bb0fb7edf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main +* [6136](https://github.com/grafana/loki/pull/6136) **periklis**: Add support for alertmanager header authorization * [6102](https://github.com/grafana/loki/pull/6102) **timchenko-a**: Add multi-tenancy support to lambda-promtail * [5971](https://github.com/grafana/loki/pull/5971) **kavirajk**: Record statistics about metadata queries such as labels and series queries in `metrics.go` as well * [5790](https://github.com/grafana/loki/pull/5790) **chaudum**: Add UDP support for Promtail's syslog target. diff --git a/docs/sources/configuration/_index.md b/docs/sources/configuration/_index.md index 05d29f5e1f44..b91b3f7be1b2 100644 --- a/docs/sources/configuration/_index.md +++ b/docs/sources/configuration/_index.md @@ -636,6 +636,26 @@ wal_cleaner: # CLI flag: -ruler.alertmanager-url [alertmanager_url: | default = ""] + +alertmanager_client: + # Sets the `Authorization` header on every remote write request with the + # configured username and password. + # password and password_file are mutually exclusive. + basic_auth: + [username: ] + [password: ] + + # Optional `Authorization` header configuration. + authorization: + # Sets the authentication type. + [type: | default: Bearer] + # Sets the credentials. It is mutually exclusive with + # `credentials_file`. + [credentials: ] + # Sets the credentials to the credentials read from the configured file. + # It is mutually exclusive with `credentials`. + [credentials_file: ] + # Use DNS SRV records to discover Alertmanager hosts. # CLI flag: -ruler.alertmanager-discovery [enable_alertmanager_discovery: | default = false] diff --git a/pkg/ruler/base/notifier.go b/pkg/ruler/base/notifier.go index 648d5dbb0a44..d9b28bfa8547 100644 --- a/pkg/ruler/base/notifier.go +++ b/pkg/ruler/base/notifier.go @@ -23,13 +23,15 @@ import ( ) type NotifierConfig struct { - TLS tls.ClientConfig `yaml:",inline"` - BasicAuth util.BasicAuth `yaml:",inline"` + TLS tls.ClientConfig `yaml:",inline"` + BasicAuth util.BasicAuth `yaml:",inline"` + HeaderAuth util.HeaderAuth `yaml:",inline"` } func (cfg *NotifierConfig) RegisterFlags(f *flag.FlagSet) { cfg.TLS.RegisterFlagsWithPrefix("ruler.alertmanager-client", f) cfg.BasicAuth.RegisterFlagsWithPrefix("ruler.alertmanager-client.", f) + cfg.HeaderAuth.RegisterFlagsWithPrefix("ruler.alertmanager-client.", f) } // rulerNotifier bundles a notifier.Manager together with an associated @@ -197,5 +199,20 @@ func amConfigFromURL(rulerConfig *Config, url *url.URL, apiVersion config.Alertm } } + if rulerConfig.Notifier.HeaderAuth.IsEnabled() { + if rulerConfig.Notifier.HeaderAuth.Credentials != "" { + amConfig.HTTPClientConfig.Authorization = &config_util.Authorization{ + Type: rulerConfig.Notifier.HeaderAuth.Type, + Credentials: config_util.Secret(rulerConfig.Notifier.HeaderAuth.Credentials), + } + } else if rulerConfig.Notifier.HeaderAuth.CredentialsFile != "" { + amConfig.HTTPClientConfig.Authorization = &config_util.Authorization{ + Type: rulerConfig.Notifier.HeaderAuth.Type, + CredentialsFile: rulerConfig.Notifier.HeaderAuth.CredentialsFile, + } + + } + } + return amConfig } diff --git a/pkg/ruler/base/notifier_test.go b/pkg/ruler/base/notifier_test.go index 1f5b7776b780..eb8959e8b90e 100644 --- a/pkg/ruler/base/notifier_test.go +++ b/pkg/ruler/base/notifier_test.go @@ -221,6 +221,78 @@ func TestBuildNotifierConfig(t *testing.T) { }, }, }, + { + name: "with Header Authorization", + cfg: &Config{ + AlertmanagerURL: "http://alertmanager-0.default.svc.cluster.local/alertmanager", + Notifier: NotifierConfig{ + HeaderAuth: util.HeaderAuth{ + Type: "Bearer", + Credentials: "jacob", + }, + }, + }, + ncfg: &config.Config{ + AlertingConfig: config.AlertingConfig{ + AlertmanagerConfigs: []*config.AlertmanagerConfig{ + { + HTTPClientConfig: config_util.HTTPClientConfig{ + Authorization: &config_util.Authorization{ + Type: "Bearer", + Credentials: config_util.Secret("jacob"), + }, + }, + APIVersion: "v1", + Scheme: "http", + PathPrefix: "/alertmanager", + ServiceDiscoveryConfigs: discovery.Configs{ + discovery.StaticConfig{ + { + Targets: []model.LabelSet{{"__address__": "alertmanager-0.default.svc.cluster.local"}}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "with Header Authorization and credentials file", + cfg: &Config{ + AlertmanagerURL: "http://alertmanager-0.default.svc.cluster.local/alertmanager", + Notifier: NotifierConfig{ + HeaderAuth: util.HeaderAuth{ + Type: "Bearer", + CredentialsFile: "/path/to/secret/file", + }, + }, + }, + ncfg: &config.Config{ + AlertingConfig: config.AlertingConfig{ + AlertmanagerConfigs: []*config.AlertmanagerConfig{ + { + HTTPClientConfig: config_util.HTTPClientConfig{ + Authorization: &config_util.Authorization{ + Type: "Bearer", + CredentialsFile: "/path/to/secret/file", + }, + }, + APIVersion: "v1", + Scheme: "http", + PathPrefix: "/alertmanager", + ServiceDiscoveryConfigs: discovery.Configs{ + discovery.StaticConfig{ + { + Targets: []model.LabelSet{{"__address__": "alertmanager-0.default.svc.cluster.local"}}, + }, + }, + }, + }, + }, + }, + }, + }, { name: "with external labels", cfg: &Config{ diff --git a/pkg/util/http.go b/pkg/util/http.go index 45f8da374186..3305a9410a49 100644 --- a/pkg/util/http.go +++ b/pkg/util/http.go @@ -43,6 +43,24 @@ func (b BasicAuth) IsEnabled() bool { return b.Username != "" || b.Password != "" } +// HeaderAuth condigures header based authorization for HTTP clients. +type HeaderAuth struct { + Type string `yaml:"type,omitempty"` + Credentials string `yaml:"credentials,omitempty"` + CredentialsFile string `yaml:"credentials_file,omitempty"` +} + +func (h *HeaderAuth) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { + f.StringVar(&h.Type, prefix+"type", "Bearer", "HTTP Header authorization type (default: Bearer).") + f.StringVar(&h.Credentials, prefix+"credentials", "", "HTTP Header authorization credentials.") + f.StringVar(&h.CredentialsFile, prefix+"credentials-file", "", "HTTP Header authorization credentials file.") +} + +// IsEnabled returns false if header authorization isn't enabled. +func (h HeaderAuth) IsEnabled() bool { + return h.Credentials != "" || h.CredentialsFile != "" +} + // WriteJSONResponse writes some JSON as a HTTP response. func WriteJSONResponse(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json")