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:
Eric Leijonmarck
2023-10-17 11:23:54 +01:00
committed by GitHub
co-authored by Victor Cinaglia Alexander Zobnin
parent 7d9b2c73c7
commit be5ba68132
14 changed files with 425 additions and 4 deletions
+33 -4
View File
@@ -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)
}
+41
View File
@@ -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"
+9
View File
@@ -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 {