diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index d4fb40a7394..028ef7cccbe 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -11,8 +11,10 @@ import ( "strconv" "strings" - "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/prometheus/prometheus/promql/parser" + "golang.org/x/exp/slices" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -336,16 +338,18 @@ func validateURL(cmdType string, url string) response.Response { // This is done to prevent data source proxy from being used to circumvent auth proxy. // For more context take a look at CVE-2022-35957 func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setting.Cfg, features *featuremgmt.FeatureManager) error { - if jsonData == nil || !cfg.AuthProxyEnabled { + if jsonData == nil { return nil } - for key, value := range jsonData.MustMap() { - if strings.HasPrefix(key, datasources.CustomHeaderName) { - header := fmt.Sprint(value) - if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) { - datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key) - return errors.New("validation error, invalid header name specified") + if cfg.AuthProxyEnabled { + for key, value := range jsonData.MustMap() { + if strings.HasPrefix(key, datasources.CustomHeaderName) { + header := fmt.Sprint(value) + if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) { + datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key) + return errors.New("validation error, invalid header name specified") + } } } } @@ -374,11 +378,13 @@ func validateTeamHTTPHeaderJSON(jsonData *simplejson.Json) error { // each teams headers for _, teamheaders := range teamHTTPHeadersJSON { for _, header := range teamheaders { - if !contains(validHeaders, header.Header) { + if !slices.ContainsFunc(validHeaders, func(v string) bool { + return http.CanonicalHeaderKey(v) == http.CanonicalHeaderKey(header.Header) + }) { datasourcesLogger.Error("Cannot add a data source team header that is different than", "headerName", header.Header) return errors.New("validation error, invalid header name specified") } - if !teamHTTPHeaderValueRegexMatch(header.Value) { + if !validateLBACHeader(header.Value) { datasourcesLogger.Error("Cannot add a data source team header value with invalid value", "headerValue", header.Value) return errors.New("validation error, invalid header value syntax") } @@ -387,27 +393,20 @@ func validateTeamHTTPHeaderJSON(jsonData *simplejson.Json) error { return nil } -func contains(slice []string, value string) bool { - for _, v := range slice { - if http.CanonicalHeaderKey(v) == http.CanonicalHeaderKey(value) { - return true - } - } - return false -} - -// teamHTTPHeaderValueRegexMatch returns true if the header value matches the regex -// words separated by special characters -// namespace!="auth", env="prod", env!~"dev" -func teamHTTPHeaderValueRegexMatch(headervalue string) bool { - // link to regex: https://regex101.com/r/I8KhZz/1 - // 1234:{ name!="value",foo!~"bar" } - exp := `^\d+:{(?:\s*\w+\s*(?:=|!=|=~|!~)\s*\"\w+\"\s*,*)+}$` - reg, err := regexp.Compile(exp) +// validateLBACHeader returns true if the header value matches the syntax +// 1234:{ name!="value",foo!~"bar" } +func validateLBACHeader(headervalue string) bool { + exp := `^\d+:(.+)` + pattern, err := regexp.Compile(exp) if err != nil { return false } - return reg.Match([]byte(strings.TrimSpace(headervalue))) + match := pattern.FindSubmatch([]byte(strings.TrimSpace(headervalue))) + if match == nil || len(match) < 2 { + return false + } + _, err = parser.ParseMetricSelector(string(match[1])) + return err == nil } // swagger:route POST /datasources datasources addDataSource diff --git a/pkg/api/datasources_test.go b/pkg/api/datasources_test.go index f3b9ee53b3b..9599e33505c 100644 --- a/pkg/api/datasources_test.go +++ b/pkg/api/datasources_test.go @@ -481,18 +481,22 @@ func TestAPI_datasources_AccessControl(t *testing.T) { } } -// TeamHTTPHeaderValueRegexMatch returns a regex that can be used to check -func TestTeamHTTPHeaderValueRegexMatch(t *testing.T) { +func TestValidateLBACHeader(t *testing.T) { testcases := []struct { desc string teamHeaderValue string want bool }{ { - desc: "Should be valid regex match for team headervalue", + desc: "Should allow valid header", teamHeaderValue: `1234:{ name!="value",foo!~"bar" }`, want: true, }, + { + desc: "Should allow valid selector", + teamHeaderValue: `1234:{ name!="value",foo!~"bar/baz.foo" }`, + want: true, + }, { desc: "Should return false for incorrect header value", teamHeaderValue: `1234:!="value",foo!~"bar" }`, @@ -501,7 +505,7 @@ func TestTeamHTTPHeaderValueRegexMatch(t *testing.T) { } for _, tc := range testcases { t.Run(tc.desc, func(t *testing.T) { - assert.Equal(t, tc.want, teamHTTPHeaderValueRegexMatch(tc.teamHeaderValue)) + assert.Equal(t, tc.want, validateLBACHeader(tc.teamHeaderValue)) }) } }