Team LBAC: Fix backend validation (#77612)

* Team LBAC: Fix backend validation

* more tests

* use slices.ContainsFunc()
This commit is contained in:
Alexander Zobnin 2023-11-03 15:02:57 +01:00 committed by GitHub
parent d06abedca2
commit 225a69ba02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 32 deletions

View File

@ -11,8 +11,10 @@ import (
"strconv" "strconv"
"strings" "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/datasource"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
@ -336,10 +338,11 @@ func validateURL(cmdType string, url string) response.Response {
// This is done to prevent data source proxy from being used to circumvent auth proxy. // 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 // 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 { 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 return nil
} }
if cfg.AuthProxyEnabled {
for key, value := range jsonData.MustMap() { for key, value := range jsonData.MustMap() {
if strings.HasPrefix(key, datasources.CustomHeaderName) { if strings.HasPrefix(key, datasources.CustomHeaderName) {
header := fmt.Sprint(value) header := fmt.Sprint(value)
@ -349,6 +352,7 @@ func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setti
} }
} }
} }
}
// Prevent adding a data source team header with a name that matches the auth proxy header name // Prevent adding a data source team header with a name that matches the auth proxy header name
if features.IsEnabled(featuremgmt.FlagTeamHttpHeaders) { if features.IsEnabled(featuremgmt.FlagTeamHttpHeaders) {
@ -374,11 +378,13 @@ func validateTeamHTTPHeaderJSON(jsonData *simplejson.Json) error {
// each teams headers // each teams headers
for _, teamheaders := range teamHTTPHeadersJSON { for _, teamheaders := range teamHTTPHeadersJSON {
for _, header := range teamheaders { 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) 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") 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) 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") return errors.New("validation error, invalid header value syntax")
} }
@ -387,27 +393,20 @@ func validateTeamHTTPHeaderJSON(jsonData *simplejson.Json) error {
return nil return nil
} }
func contains(slice []string, value string) bool { // validateLBACHeader returns true if the header value matches the syntax
for _, v := range slice { // 1234:{ name!="value",foo!~"bar" }
if http.CanonicalHeaderKey(v) == http.CanonicalHeaderKey(value) { func validateLBACHeader(headervalue string) bool {
return true exp := `^\d+:(.+)`
} pattern, err := regexp.Compile(exp)
}
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)
if err != nil { if err != nil {
return false 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 // swagger:route POST /datasources datasources addDataSource

View File

@ -481,18 +481,22 @@ func TestAPI_datasources_AccessControl(t *testing.T) {
} }
} }
// TeamHTTPHeaderValueRegexMatch returns a regex that can be used to check func TestValidateLBACHeader(t *testing.T) {
func TestTeamHTTPHeaderValueRegexMatch(t *testing.T) {
testcases := []struct { testcases := []struct {
desc string desc string
teamHeaderValue string teamHeaderValue string
want bool want bool
}{ }{
{ {
desc: "Should be valid regex match for team headervalue", desc: "Should allow valid header",
teamHeaderValue: `1234:{ name!="value",foo!~"bar" }`, teamHeaderValue: `1234:{ name!="value",foo!~"bar" }`,
want: true, 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", desc: "Should return false for incorrect header value",
teamHeaderValue: `1234:!="value",foo!~"bar" }`, teamHeaderValue: `1234:!="value",foo!~"bar" }`,
@ -501,7 +505,7 @@ func TestTeamHTTPHeaderValueRegexMatch(t *testing.T) {
} }
for _, tc := range testcases { for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
assert.Equal(t, tc.want, teamHTTPHeaderValueRegexMatch(tc.teamHeaderValue)) assert.Equal(t, tc.want, validateLBACHeader(tc.teamHeaderValue))
}) })
} }
} }