diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 25a1dcbfe60..96f9ca38b68 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -24,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/manager/installer" + "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/pluginsettings" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" @@ -546,6 +547,14 @@ func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http hs.log.Warn("failed to to unpack JSONData in datasource instance settings", "err", err) } } + + list := contexthandler.AuthHTTPHeaderListFromContext(req.Context()) + if list != nil { + for _, name := range list.Items { + req.Header.Del(name) + } + } + proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies) proxyutil.PrepareProxyRequest(req) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 7471e7f9e0b..3eebd31747c 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web/webtest" ) @@ -275,6 +276,12 @@ func TestMakePluginResourceRequest(t *testing.T) { pluginClient: pluginClient, } req := httptest.NewRequest(http.MethodGet, "/", nil) + + const customHeader = "X-CUSTOM" + req.Header.Set(customHeader, "val") + ctx := contexthandler.WithAuthHTTPHeader(req.Context(), customHeader) + req = req.WithContext(ctx) + resp := httptest.NewRecorder() pCtx := backend.PluginContext{} err := hs.makePluginResourceRequest(resp, req, pCtx) @@ -287,6 +294,7 @@ func TestMakePluginResourceRequest(t *testing.T) { } require.Equal(t, "sandbox", resp.Header().Get("Content-Security-Policy")) + require.Empty(t, req.Header.Get(customHeader)) } func callGetPluginAsset(sc *scenarioContext) { diff --git a/pkg/middleware/middleware_basic_auth_test.go b/pkg/middleware/middleware_basic_auth_test.go index 5836a0d315b..bfe2cd7306a 100644 --- a/pkg/middleware/middleware_basic_auth_test.go +++ b/pkg/middleware/middleware_basic_auth_test.go @@ -36,6 +36,9 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.True(t, sc.context.IsSignedIn) assert.Equal(t, orgID, sc.context.OrgId) assert.Equal(t, models.ROLE_EDITOR, sc.context.OrgRole) + list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context()) + require.NotNil(t, list) + require.EqualValues(t, []string{"Authorization"}, list.Items) }, configure) middlewareScenario(t, "Handle auth", func(t *testing.T, sc *scenarioContext) { @@ -69,6 +72,9 @@ func TestMiddlewareBasicAuth(t *testing.T) { assert.True(t, sc.context.IsSignedIn) assert.Equal(t, id, sc.context.UserId) + list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context()) + require.NotNil(t, list) + require.EqualValues(t, []string{"Authorization"}, list.Items) }, configure) middlewareScenario(t, "Should return error if user is not found", func(t *testing.T, sc *scenarioContext) { diff --git a/pkg/middleware/middleware_jwt_auth_test.go b/pkg/middleware/middleware_jwt_auth_test.go index 31403464d69..e7dde3dde21 100644 --- a/pkg/middleware/middleware_jwt_auth_test.go +++ b/pkg/middleware/middleware_jwt_auth_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/contexthandler" @@ -54,6 +55,9 @@ func TestMiddlewareJWTAuth(t *testing.T) { assert.Equal(t, orgID, sc.context.OrgId) assert.Equal(t, id, sc.context.UserId) assert.Equal(t, myUsername, sc.context.Login) + list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context()) + require.NotNil(t, list) + require.EqualValues(t, []string{sc.cfg.JWTAuthHeaderName}, list.Items) }, configure, configureUsernameClaim) middlewareScenario(t, "Valid token with valid email claim", func(t *testing.T, sc *scenarioContext) { diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 76dbd87b1be..98c26d1a7cc 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -395,6 +395,11 @@ func TestMiddlewareContext(t *testing.T) { assert.True(t, sc.context.IsSignedIn) assert.Equal(t, userID, sc.context.UserId) assert.Equal(t, orgID, sc.context.OrgId) + list := contexthandler.AuthHTTPHeaderListFromContext(sc.context.Req.Context()) + require.NotNil(t, list) + require.Contains(t, list.Items, sc.cfg.AuthProxyHeaderName) + require.Contains(t, list.Items, "X-WEBAUTH-GROUPS") + require.Contains(t, list.Items, "X-WEBAUTH-ROLE") }, func(cfg *setting.Cfg) { configure(cfg) cfg.LDAPEnabled = false diff --git a/pkg/services/contexthandler/auth_jwt.go b/pkg/services/contexthandler/auth_jwt.go index e61f009d882..300026d7c5f 100644 --- a/pkg/services/contexthandler/auth_jwt.go +++ b/pkg/services/contexthandler/auth_jwt.go @@ -93,6 +93,9 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64) return true } + newCtx := WithAuthHTTPHeader(ctx.Req.Context(), h.Cfg.JWTAuthHeaderName) + *ctx.Req = *ctx.Req.WithContext(newCtx) + ctx.SignedInUser = query.Result ctx.IsSignedIn = true diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 0557f711976..3267e89d87f 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -211,6 +211,9 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo return true } + ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization") + *reqContext.Req = *reqContext.Req.WithContext(ctx) + // fetch key keyQuery := models.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId} if err := h.SQLStore.GetApiKeyByName(reqContext.Req.Context(), &keyQuery); err != nil { @@ -287,7 +290,7 @@ func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, return false } - ctx, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithBasicAuth") + _, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithBasicAuth") defer span.End() username, password, err := util.DecodeBasicAuthHeader(header) @@ -296,12 +299,15 @@ func (h *ContextHandler) initContextWithBasicAuth(reqContext *models.ReqContext, return true } + ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization") + *reqContext.Req = *reqContext.Req.WithContext(ctx) + authQuery := models.LoginUserQuery{ Username: username, Password: password, Cfg: h.Cfg, } - if err := h.authenticator.AuthenticateUser(reqContext.Req.Context(), &authQuery); err != nil { + if err := h.authenticator.AuthenticateUser(ctx, &authQuery); err != nil { reqContext.Logger.Debug( "Failed to authorize the user", "username", username, @@ -523,6 +529,15 @@ func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, logger.Debug("Successfully got user info", "userID", user.UserId, "username", user.Login) + ctx := WithAuthHTTPHeader(reqContext.Req.Context(), h.Cfg.AuthProxyHeaderName) + for _, header := range h.Cfg.AuthProxyHeaders { + if header != "" { + ctx = WithAuthHTTPHeader(ctx, header) + } + } + + *reqContext.Req = *reqContext.Req.WithContext(ctx) + // Add user info to context reqContext.SignedInUser = user reqContext.IsSignedIn = true @@ -542,3 +557,38 @@ func (h *ContextHandler) initContextWithAuthProxy(reqContext *models.ReqContext, return true } + +type authHTTPHeaderListContextKey struct{} + +var authHTTPHeaderListKey = authHTTPHeaderListContextKey{} + +// AuthHTTPHeaderList used to record HTTP headers that being when verifying authentication +// of an incoming HTTP request. +type AuthHTTPHeaderList struct { + Items []string +} + +// WithAuthHTTPHeader returns a copy of parent in which the named HTTP header will be included +// and later retrievable by AuthHTTPHeaderListFromContext. +func WithAuthHTTPHeader(parent context.Context, name string) context.Context { + list := AuthHTTPHeaderListFromContext(parent) + + if list == nil { + list = &AuthHTTPHeaderList{ + Items: []string{}, + } + } + + list.Items = append(list.Items, name) + + return context.WithValue(parent, authHTTPHeaderListKey, list) +} + +// AuthHTTPHeaderListFromContext returns the AuthHTTPHeaderList in a context.Context, if any, +// and will include any HTTP headers used when verifying authentication of an incoming HTTP request. +func AuthHTTPHeaderListFromContext(c context.Context) *AuthHTTPHeaderList { + if list, ok := c.Value(authHTTPHeaderListKey).(*AuthHTTPHeaderList); ok { + return list + } + return nil +} diff --git a/pkg/util/proxyutil/reverse_proxy.go b/pkg/util/proxyutil/reverse_proxy.go index 3ce5d3b4011..7006d197cde 100644 --- a/pkg/util/proxyutil/reverse_proxy.go +++ b/pkg/util/proxyutil/reverse_proxy.go @@ -10,6 +10,7 @@ import ( "time" glog "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/contexthandler" ) // StatusClientClosedRequest A non-standard status code introduced by nginx @@ -66,6 +67,13 @@ func NewReverseProxy(logger glog.Logger, director func(*http.Request), opts ...R // wrapDirector wraps a director and adds additional functionality. func wrapDirector(d func(*http.Request)) func(req *http.Request) { return func(req *http.Request) { + list := contexthandler.AuthHTTPHeaderListFromContext(req.Context()) + if list != nil { + for _, name := range list.Items { + req.Header.Del(name) + } + } + d(req) PrepareProxyRequest(req) diff --git a/pkg/util/proxyutil/reverse_proxy_test.go b/pkg/util/proxyutil/reverse_proxy_test.go index 19c7db9144d..b5dfee0c3e9 100644 --- a/pkg/util/proxyutil/reverse_proxy_test.go +++ b/pkg/util/proxyutil/reverse_proxy_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/stretchr/testify/require" ) @@ -30,6 +31,11 @@ func TestReverseProxy(t *testing.T) { req.Header.Set("Referer", "https://test.com/api") req.RemoteAddr = "10.0.0.1" + const customHeader = "X-CUSTOM" + req.Header.Set(customHeader, "val") + ctx := contexthandler.WithAuthHTTPHeader(req.Context(), customHeader) + req = req.WithContext(ctx) + rp := NewReverseProxy(log.New("test"), func(req *http.Request) { req.Header.Set("X-KEY", "value") }) @@ -49,6 +55,7 @@ func TestReverseProxy(t *testing.T) { require.Empty(t, resp.Cookies()) require.Equal(t, "sandbox", resp.Header.Get("Content-Security-Policy")) require.NoError(t, resp.Body.Close()) + require.Empty(t, actualReq.Header.Get(customHeader)) }) t.Run("When proxying a request using WithModifyResponse should call it before default ModifyResponse func", func(t *testing.T) {