Team LBAC: Add teamHeaders for datasource proxy requests (#76339)
* Add teamHeaders for datasource proxy requests * adds validation for the teamHeaders * added tests for applying teamHeaders * remove previous implementation * validation for header values being set to authproxy * removed unnecessary checks * newline * Add middleware for injecting headers on the data source backend * renamed feature toggle * Get user teams from context * Fix feature toggle name * added test for validation of the auth headers and fixed evaluation to cover headers * renaming of teamHeaders to teamHTTPHeaders * use of header set for non-existing header and add for existing headers * moves types into datasources * fixed unchecked errors * Refactor * Add tests for data model * Update pkg/api/datasources.go Co-authored-by: Victor Cinaglia <victor@grafana.com> * Update pkg/api/datasources.go Co-authored-by: Victor Cinaglia <victor@grafana.com> --------- Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> Co-authored-by: Victor Cinaglia <victor@grafana.com>
This commit is contained in:
co-authored by
Victor Cinaglia
Alexander Zobnin
parent
7d9b2c73c7
commit
be5ba68132
+33
-4
@@ -18,6 +18,7 @@ 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/setting"
|
||||
@@ -333,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(jsonData *simplejson.Json, cfg *setting.Cfg) error {
|
||||
func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setting.Cfg) error {
|
||||
if jsonData == nil || !cfg.AuthProxyEnabled {
|
||||
return nil
|
||||
}
|
||||
@@ -347,6 +348,34 @@ func validateJSONData(jsonData *simplejson.Json, cfg *setting.Cfg) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(jsonData, &teamHTTPHeadersJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, headers := range teamHTTPHeadersJSON.TeamHTTPHeaders {
|
||||
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)
|
||||
return errors.New("validation error, invalid header name specified")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -387,7 +416,7 @@ func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Respons
|
||||
return resp
|
||||
}
|
||||
}
|
||||
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
|
||||
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to add datasource", err)
|
||||
}
|
||||
|
||||
@@ -452,7 +481,7 @@ func (hs *HTTPServer) UpdateDataSourceByID(c *contextmodel.ReqContext) response.
|
||||
if resp := validateURL(cmd.Type, cmd.URL); resp != nil {
|
||||
return resp
|
||||
}
|
||||
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
|
||||
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
|
||||
}
|
||||
|
||||
@@ -492,7 +521,7 @@ func (hs *HTTPServer) UpdateDataSourceByUID(c *contextmodel.ReqContext) response
|
||||
if resp := validateURL(cmd.Type, cmd.URL); resp != nil {
|
||||
return resp
|
||||
}
|
||||
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
|
||||
if err := validateJSONData(c.Req.Context(), cmd.JsonData, hs.Cfg); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,47 @@ func TestUpdateDataSource_InvalidJSONData(t *testing.T) {
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
}
|
||||
|
||||
// Using a team HTTP header whose name matches the name specified for auth proxy header should fail
|
||||
func TestUpdateDataSourceTeamHTTPHeaders_InvalidJSONData(t *testing.T) {
|
||||
hs := &HTTPServer{
|
||||
DataSourcesService: &dataSourcesServiceMock{},
|
||||
Cfg: setting.NewCfg(),
|
||||
}
|
||||
sc := setupScenarioContext(t, "/api/datasources/1234")
|
||||
|
||||
data := datasources.TeamHTTPHeaders{
|
||||
"1234": []datasources.TeamHTTPHeader{
|
||||
// Authorization is used by the auth proxy
|
||||
// As part of
|
||||
// contexthandler.AuthHTTPHeaderListFromContext(ctx)
|
||||
{
|
||||
Header: "Authorization",
|
||||
Value: "Could be anything",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hs.Cfg.AuthProxyEnabled = true
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set("teamHTTPHeaders", data)
|
||||
|
||||
sc.m.Put(sc.url, routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{
|
||||
Name: "Test",
|
||||
URL: "localhost:5432",
|
||||
Access: "direct",
|
||||
Type: "test",
|
||||
JsonData: jsonData,
|
||||
})
|
||||
c.SignedInUser = authedUserWithPermissions(1, 1, []ac.Permission{})
|
||||
return hs.AddDataSource(c)
|
||||
}))
|
||||
|
||||
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
||||
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
}
|
||||
|
||||
// Updating data sources with URLs not specifying protocol should work.
|
||||
func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
|
||||
const name = "Test"
|
||||
|
||||
@@ -272,6 +272,15 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
|
||||
if proxy.features.IsEnabled(featuremgmt.FlagIdForwarding) {
|
||||
proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser)
|
||||
}
|
||||
|
||||
if proxy.features.IsEnabled(featuremgmt.FlagTeamHttpHeaders) {
|
||||
err := proxyutil.ApplyTeamHTTPHeaders(req, proxy.ds, proxy.ctx.Teams)
|
||||
if err != nil {
|
||||
// NOTE: could downgrade the errors to warnings
|
||||
ctxLogger.Error("Error applying teamHTTPHeaders", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (proxy *DataSourceProxy) validateRequest() error {
|
||||
|
||||
Reference in New Issue
Block a user