diff --git a/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware.go index f8282cd27e5..7637c8b0513 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware.go @@ -5,7 +5,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "path" + "net/url" "github.com/google/uuid" @@ -49,17 +49,22 @@ func (m *HostedGrafanaACHeaderMiddleware) applyGrafanaRequestIDHeader(ctx contex } // Check if the request is for a datasource that is allowed to have the header - target := pCtx.DataSourceInstanceSettings.URL - + dsURL := pCtx.DataSourceInstanceSettings.URL + dsBaseURL, err := url.Parse(dsURL) + if err != nil { + m.log.Debug("Failed to parse data source URL", "error", err) + return + } foundMatch := false for _, allowedURL := range m.cfg.IPRangeACAllowedURLs { - if path.Clean(allowedURL) == path.Clean(target) { + // Only look at the scheme and host, ignore the path + if allowedURL.Host == dsBaseURL.Host && allowedURL.Scheme == dsBaseURL.Scheme { foundMatch = true break } } if !foundMatch { - m.log.Debug("Data source URL not among the allow-listed URLs", "url", target) + m.log.Debug("Data source URL not among the allow-listed URLs", "url", dsBaseURL.String()) return } diff --git a/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware_test.go b/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware_test.go index 8204c70d4af..3c389edea1d 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware_test.go +++ b/pkg/services/pluginsintegration/clientmiddleware/grafana_request_id_header_middleware_test.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "encoding/hex" "net/http" + "net/url" "testing" "github.com/stretchr/testify/require" @@ -22,7 +23,8 @@ import ( func Test_HostedGrafanaACHeaderMiddleware(t *testing.T) { t.Run("Should set Grafana request ID headers if the data source URL is in the allow list", func(t *testing.T) { cfg := setting.NewCfg() - cfg.IPRangeACAllowedURLs = []string{"https://logs.grafana.net"} + allowedURL := &url.URL{Scheme: "https", Host: "logs.grafana.net"} + cfg.IPRangeACAllowedURLs = []*url.URL{allowedURL} cfg.IPRangeACSecretKey = "secret" cdt := clienttest.NewClientDecoratorTest(t, clienttest.WithMiddlewares(NewHostedGrafanaACHeaderMiddleware(cfg))) @@ -63,7 +65,8 @@ func Test_HostedGrafanaACHeaderMiddleware(t *testing.T) { t.Run("Should not set Grafana request ID headers if the data source URL is not in the allow list", func(t *testing.T) { cfg := setting.NewCfg() - cfg.IPRangeACAllowedURLs = []string{"https://logs.grafana.net"} + allowedURL := &url.URL{Scheme: "https", Host: "logs.grafana.net"} + cfg.IPRangeACAllowedURLs = []*url.URL{allowedURL} cfg.IPRangeACSecretKey = "secret" cdt := clienttest.NewClientDecoratorTest(t, clienttest.WithMiddlewares(NewHostedGrafanaACHeaderMiddleware(cfg))) @@ -85,9 +88,10 @@ func Test_HostedGrafanaACHeaderMiddleware(t *testing.T) { require.Len(t, cdt.CallResourceReq.Headers[GrafanaSignedRequestID], 0) }) - t.Run("Should set Grafana request ID headers if a sanitized data source URL is in the allow list", func(t *testing.T) { + t.Run("Should set Grafana request ID headers if URL scheme and host match a URL from the allow list", func(t *testing.T) { cfg := setting.NewCfg() - cfg.IPRangeACAllowedURLs = []string{"https://logs.GRAFANA.net/"} + allowedURL := &url.URL{Scheme: "https", Host: "logs.grafana.net"} + cfg.IPRangeACAllowedURLs = []*url.URL{allowedURL} cfg.IPRangeACSecretKey = "secret" cdt := clienttest.NewClientDecoratorTest(t, clienttest.WithMiddlewares(NewHostedGrafanaACHeaderMiddleware(cfg))) @@ -99,19 +103,20 @@ func Test_HostedGrafanaACHeaderMiddleware(t *testing.T) { err := cdt.Decorator.CallResource(ctx, &backend.CallResourceRequest{ PluginContext: backend.PluginContext{ DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ - URL: "https://logs.grafana.net/abc/../", + URL: "https://logs.grafana.net/abc/../some/path", }, }, }, nopCallResourceSender) require.NoError(t, err) - require.Len(t, cdt.CallResourceReq.Headers[GrafanaRequestID], 0) - require.Len(t, cdt.CallResourceReq.Headers[GrafanaSignedRequestID], 0) + require.Len(t, cdt.CallResourceReq.Headers[GrafanaRequestID], 1) + require.Len(t, cdt.CallResourceReq.Headers[GrafanaSignedRequestID], 1) }) t.Run("Should set Grafana internal request header if the request is internal (doesn't have X-Real-IP header set)", func(t *testing.T) { cfg := setting.NewCfg() - cfg.IPRangeACAllowedURLs = []string{"https://logs.grafana.net"} + allowedURL := &url.URL{Scheme: "https", Host: "logs.grafana.net"} + cfg.IPRangeACAllowedURLs = []*url.URL{allowedURL} cfg.IPRangeACSecretKey = "secret" cdt := clienttest.NewClientDecoratorTest(t, clienttest.WithMiddlewares(NewHostedGrafanaACHeaderMiddleware(cfg))) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 95a28baafa5..c00b5e50091 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -339,7 +339,7 @@ type Cfg struct { // IP range access control IPRangeACEnabled bool - IPRangeACAllowedURLs []string + IPRangeACAllowedURLs []*url.URL IPRangeACSecretKey string // SQL Data sources @@ -1949,7 +1949,15 @@ func (cfg *Cfg) readDataSourceSecuritySettings() { cfg.IPRangeACEnabled = datasources.Key("enabled").MustBool(false) cfg.IPRangeACSecretKey = datasources.Key("secret_key").MustString("") allowedURLString := datasources.Key("allow_list").MustString("") - cfg.IPRangeACAllowedURLs = util.SplitString(allowedURLString) + for _, urlString := range util.SplitString(allowedURLString) { + allowedURL, err := url.Parse(urlString) + if err != nil { + cfg.Logger.Error("Error parsing allowed URL for IP range access control", "error", err) + continue + } else { + cfg.IPRangeACAllowedURLs = append(cfg.IPRangeACAllowedURLs, allowedURL) + } + } } func (cfg *Cfg) readSqlDataSourceSettings() {