Team LBAC: Refactor to use only the teamHeader json part (#76756)

* refactor: to check for feature toggle and for checking for jsonData field

* fix tests

* whitelisting of X-Prom-Label-Policy Header
This commit is contained in:
Eric Leijonmarck 2023-10-18 16:09:22 +01:00 committed by GitHub
parent 756df61a88
commit 17fe1d3fc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 30 additions and 55 deletions

View File

@ -18,9 +18,9 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/contexthandler"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@ -334,7 +334,7 @@ func validateURL(cmdType string, url string) response.Response {
// validateJSONData prevents the user from adding a custom header with name that matches the auth proxy header name.
// 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) error {
func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setting.Cfg, features *featuremgmt.FeatureManager) error {
if jsonData == nil || !cfg.AuthProxyEnabled {
return nil
}
@ -350,25 +350,19 @@ 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
list := contexthandler.AuthHTTPHeaderListFromContext(ctx)
if list == nil {
return nil
}
teamHTTPHeadersJSON := datasources.TeamHTTPHeadersJSONData{}
if jsonData != nil {
jsonData, err := jsonData.MarshalJSON()
if features.IsEnabled(featuremgmt.FlagTeamHttpHeaders) {
teamHTTPHeadersJSON, err := datasources.GetTeamHTTPHeaders(jsonData)
if err != nil {
return err
datasourcesLogger.Error("Unable to marshal TeamHTTPHeaders")
return errors.New("validation error, invalid format of TeamHTTPHeaders")
}
err = json.Unmarshal(jsonData, &teamHTTPHeadersJSON)
if err != nil {
return err
}
for _, headers := range teamHTTPHeadersJSON.TeamHTTPHeaders {
// whitelisting X-Prom-Label-Policy
for _, headers := range teamHTTPHeadersJSON {
for _, header := range headers {
for _, name := range list.Items {
if http.CanonicalHeaderKey(header.Header) == http.CanonicalHeaderKey(name) {
datasourcesLogger.Error("Cannot add a data source team header with a used by our proxy header", "headerName", header.Header)
// TODO: currently we only allow for X-Prom-Label-Policy header to be used by our proxy
for _, name := range []string{"X-Prom-Label-Policy"} {
if http.CanonicalHeaderKey(header.Header) != http.CanonicalHeaderKey(name) {
datasourcesLogger.Error("Cannot add a data source team header that is different than", "headerName", name)
return errors.New("validation error, invalid header name specified")
}
}
@ -416,7 +410,7 @@ func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Respons
return resp
}
}
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg, hs.Features); err != nil {
return response.Error(http.StatusBadRequest, "Failed to add datasource", err)
}
@ -481,7 +475,7 @@ func (hs *HTTPServer) UpdateDataSourceByID(c *contextmodel.ReqContext) response.
if resp := validateURL(cmd.Type, cmd.URL); resp != nil {
return resp
}
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg, hs.Features); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}
@ -521,7 +515,7 @@ func (hs *HTTPServer) UpdateDataSourceByUID(c *contextmodel.ReqContext) response
if resp := validateURL(cmd.Type, cmd.URL); resp != nil {
return resp
}
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg, hs.Features); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}

View File

@ -21,6 +21,7 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/guardian"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
@ -226,6 +227,7 @@ func TestUpdateDataSourceTeamHTTPHeaders_InvalidJSONData(t *testing.T) {
hs := &HTTPServer{
DataSourcesService: &dataSourcesServiceMock{},
Cfg: setting.NewCfg(),
Features: featuremgmt.WithFeatures(featuremgmt.FlagTeamHttpHeaders),
}
sc := setupScenarioContext(t, "/api/datasources/1234")
@ -243,7 +245,7 @@ func TestUpdateDataSourceTeamHTTPHeaders_InvalidJSONData(t *testing.T) {
hs.Cfg.AuthProxyEnabled = true
jsonData := simplejson.New()
jsonData.Set("teamHTTPHeaders", data)
jsonData.Set("teamHttpHeaders", data)
sc.m.Put(sc.url, routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{

View File

@ -78,9 +78,13 @@ type TeamHTTPHeader struct {
}
func (ds DataSource) TeamHTTPHeaders() (TeamHTTPHeaders, error) {
teamHTTPHeadersJSON := TeamHTTPHeadersJSONData{}
if ds.JsonData != nil {
jsonData, err := ds.JsonData.MarshalJSON()
return GetTeamHTTPHeaders(ds.JsonData)
}
func GetTeamHTTPHeaders(jsonData *simplejson.Json) (TeamHTTPHeaders, error) {
teamHTTPHeadersJSON := TeamHTTPHeaders{}
if jsonData != nil && jsonData.Get("teamHttpHeaders") != nil {
jsonData, err := jsonData.Get("teamHttpHeaders").MarshalJSON()
if err != nil {
return nil, err
}
@ -90,7 +94,7 @@ func (ds DataSource) TeamHTTPHeaders() (TeamHTTPHeaders, error) {
}
}
return teamHTTPHeadersJSON.TeamHTTPHeaders, nil
return teamHTTPHeadersJSON, nil
}
// AllowedCookies parses the jsondata.keepCookies and returns a list of

View File

@ -207,14 +207,15 @@ func TestApplyUserHeader(t *testing.T) {
}
func TestApplyteamHTTPHeaders(t *testing.T) {
t.Run("Should not apply team headers for users that are not part of the teams", func(t *testing.T) {
t.Run("Should apply team headers for users teams", func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "/", nil)
require.NoError(t, err)
ds := &datasources.DataSource{
JsonData: simplejson.New(),
}
userTeams := []int64{1, 2}
// add team headers
ds.JsonData.Set("teamHTTPHeaders", map[string]interface{}{
ds.JsonData.Set("teamHttpHeaders", map[string]interface{}{
"1": []map[string]interface{}{
{
"header": "X-Team-Header",
@ -236,36 +237,10 @@ func TestApplyteamHTTPHeaders(t *testing.T) {
},
})
err = ApplyTeamHTTPHeaders(req, ds, []int64{1, 2})
err = ApplyTeamHTTPHeaders(req, ds, userTeams)
require.NoError(t, err)
require.Contains(t, req.Header, "X-Team-Header")
require.Contains(t, req.Header, "X-Prom-Label-Policy")
require.NotContains(t, req.Header, "X-Custom-Label-Policy")
})
t.Run("Should apply team headers", func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "/", nil)
require.NoError(t, err)
ds := &datasources.DataSource{
JsonData: simplejson.New(),
}
ds.JsonData.Set("teamHTTPHeaders", map[string]interface{}{
"1": []map[string]interface{}{
{
"header": "X-Team-Header",
"value": "1",
},
},
"2": []map[string]interface{}{
{
"header": "X-Prom-Label-Policy",
"value": "2",
},
},
})
err = ApplyTeamHTTPHeaders(req, ds, []int64{1, 2})
require.NoError(t, err)
require.Contains(t, req.Header, "X-Team-Header")
require.Contains(t, req.Header, "X-Prom-Label-Policy")
})
}