From 44b11d3228a6b9c39a7c2930b4415465a0acf40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Wed, 18 Jan 2023 20:24:40 +0100 Subject: [PATCH] Alerting: support basic auth for the state history loki client (#61696) --- pkg/services/ngalert/ngalert.go | 7 +++- pkg/services/ngalert/state/historian/loki.go | 5 ++- .../ngalert/state/historian/loki_http.go | 28 +++++++++++---- .../ngalert/state/historian/loki_http_test.go | 36 +++++++++++++++++++ pkg/setting/setting_unified_alerting.go | 14 ++++++-- 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 pkg/services/ngalert/state/historian/loki_http_test.go diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index fa9622b919a..652f74d3d1f 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -391,7 +391,12 @@ func configureHistorianBackend(cfg setting.UnifiedAlertingStateHistorySettings, if err != nil { return nil, fmt.Errorf("failed to parse remote loki URL: %w", err) } - backend := historian.NewRemoteLokiBackend(baseURL) + backend := historian.NewRemoteLokiBackend(historian.LokiConfig{ + Url: baseURL, + BasicAuthUser: cfg.LokiBasicAuthUsername, + BasicAuthPassword: cfg.LokiBasicAuthPassword, + TenantID: cfg.LokiTenantID, + }) if err := backend.TestConnection(); err != nil { return nil, fmt.Errorf("failed to ping the remote loki historian: %w", err) } diff --git a/pkg/services/ngalert/state/historian/loki.go b/pkg/services/ngalert/state/historian/loki.go index 038f4e98065..33cf92cd89c 100644 --- a/pkg/services/ngalert/state/historian/loki.go +++ b/pkg/services/ngalert/state/historian/loki.go @@ -2,7 +2,6 @@ package historian import ( "context" - "net/url" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/models" @@ -18,10 +17,10 @@ type RemoteLokiBackend struct { log log.Logger } -func NewRemoteLokiBackend(url *url.URL) *RemoteLokiBackend { +func NewRemoteLokiBackend(cfg LokiConfig) *RemoteLokiBackend { logger := log.New("ngalert.state.historian", "backend", "loki") return &RemoteLokiBackend{ - client: newLokiClient(url, logger), + client: newLokiClient(cfg, logger), log: logger, } } diff --git a/pkg/services/ngalert/state/historian/loki_http.go b/pkg/services/ngalert/state/historian/loki_http.go index 78232dde025..bea1693b907 100644 --- a/pkg/services/ngalert/state/historian/loki_http.go +++ b/pkg/services/ngalert/state/historian/loki_http.go @@ -11,25 +11,41 @@ import ( const defaultClientTimeout = 30 * time.Second +type LokiConfig struct { + Url *url.URL + BasicAuthUser string + BasicAuthPassword string + TenantID string +} + type httpLokiClient struct { client http.Client - url *url.URL + cfg LokiConfig log log.Logger } -func newLokiClient(u *url.URL, logger log.Logger) *httpLokiClient { +func newLokiClient(cfg LokiConfig, logger log.Logger) *httpLokiClient { return &httpLokiClient{ client: http.Client{ Timeout: defaultClientTimeout, }, - url: u, + cfg: cfg, log: logger.New("protocol", "http"), } } func (c *httpLokiClient) ping() error { - uri := c.url.JoinPath("/loki/api/v1/status/buildinfo") + uri := c.cfg.Url.JoinPath("/loki/api/v1/labels") req, err := http.NewRequest(http.MethodGet, uri.String(), nil) + + if c.cfg.BasicAuthUser != "" || c.cfg.BasicAuthPassword != "" { + req.SetBasicAuth(c.cfg.BasicAuthUser, c.cfg.BasicAuthPassword) + } + + if c.cfg.TenantID != "" { + req.Header.Add("X-Scope-OrgID", c.cfg.TenantID) + } + if err != nil { return fmt.Errorf("error creating request: %w", err) } @@ -47,8 +63,8 @@ func (c *httpLokiClient) ping() error { } if res.StatusCode < 200 || res.StatusCode >= 300 { - return fmt.Errorf("request to the loki buildinfo endpoint returned a non-200 status code: %d", res.StatusCode) + return fmt.Errorf("ping request to loki endpoint returned a non-200 status code: %d", res.StatusCode) } - c.log.Debug("Request to Loki buildinfo endpoint succeeded", "status", res.StatusCode) + c.log.Debug("Ping request to Loki endpoint succeeded", "status", res.StatusCode) return nil } diff --git a/pkg/services/ngalert/state/historian/loki_http_test.go b/pkg/services/ngalert/state/historian/loki_http_test.go new file mode 100644 index 00000000000..4a76b2e65f4 --- /dev/null +++ b/pkg/services/ngalert/state/historian/loki_http_test.go @@ -0,0 +1,36 @@ +package historian + +import ( + "net/url" + "testing" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/stretchr/testify/require" +) + +// This function can be used for local testing, just remove the skip call. +func TestLokiHTTPClient(t *testing.T) { + t.Skip() + + url, err := url.Parse("https://logs-prod-eu-west-0.grafana.net") + require.NoError(t, err) + + client := newLokiClient(LokiConfig{ + Url: url, + }, log.NewNopLogger()) + + // Unauthorized request should fail against Grafana Cloud. + err = client.ping() + require.Error(t, err) + + client.cfg.BasicAuthUser = "" + client.cfg.BasicAuthPassword = "" + + // When running on prem, you might need to set the tenant id, + // so the x-scope-orgid header is set. + // client.cfg.TenantID = "" + + // Authorized request should fail against Grafana Cloud. + err = client.ping() + require.NoError(t, err) +} diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 9663b44426d..d81511fa3e4 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -104,6 +104,11 @@ type UnifiedAlertingStateHistorySettings struct { Enabled bool Backend string LokiRemoteURL string + LokiTenantID string + // LokiBasicAuthUsername and LokiBasicAuthPassword are used for basic auth + // if one of them is set. + LokiBasicAuthPassword string + LokiBasicAuthUsername string } // IsEnabled returns true if UnifiedAlertingSettings.Enabled is either nil or true. @@ -314,9 +319,12 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { stateHistory := iniFile.Section("unified_alerting.state_history") uaCfgStateHistory := UnifiedAlertingStateHistorySettings{ - Enabled: stateHistory.Key("enabled").MustBool(stateHistoryDefaultEnabled), - Backend: stateHistory.Key("backend").MustString("annotations"), - LokiRemoteURL: stateHistory.Key("loki_remote_url").MustString(""), + Enabled: stateHistory.Key("enabled").MustBool(stateHistoryDefaultEnabled), + Backend: stateHistory.Key("backend").MustString("annotations"), + LokiRemoteURL: stateHistory.Key("loki_remote_url").MustString(""), + LokiTenantID: stateHistory.Key("loki_tenant_id").MustString(""), + LokiBasicAuthUsername: stateHistory.Key("loki_basic_auth_username").MustString(""), + LokiBasicAuthPassword: stateHistory.Key("loki_basic_auth_password").MustString(""), } uaCfg.StateHistory = uaCfgStateHistory