mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
Team LBAC: Fix backend validation (#77612)
* Team LBAC: Fix backend validation * more tests * use slices.ContainsFunc()
This commit is contained in:
parent
d06abedca2
commit
225a69ba02
@ -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,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.
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range jsonData.MustMap() {
|
if cfg.AuthProxyEnabled {
|
||||||
if strings.HasPrefix(key, datasources.CustomHeaderName) {
|
for key, value := range jsonData.MustMap() {
|
||||||
header := fmt.Sprint(value)
|
if strings.HasPrefix(key, datasources.CustomHeaderName) {
|
||||||
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) {
|
header := fmt.Sprint(value)
|
||||||
datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key)
|
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) {
|
||||||
return errors.New("validation error, invalid header name specified")
|
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
|
// 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
|
||||||
|
@ -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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user