mirror of
https://github.com/grafana/grafana.git
synced 2025-01-13 09:32:12 -06:00
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:
parent
756df61a88
commit
17fe1d3fc7
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user