[main] Plugin fixes (#57399)

* Plugins: Remove support for V1 manifests

* Plugins: Make proxy endpoints not leak sensitive HTTP headers

* Security: Fix do not forward login cookie in outgoing requests

(cherry picked from commit 4539c33fce)

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Marcus Efraimsson
2022-10-21 13:54:55 +02:00
committed by GitHub
co-authored by Will Browne
parent af17123b5f
commit 6f8fcae01b
20 changed files with 198 additions and 38 deletions
+3
View File
@@ -142,6 +142,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 = queryResult
ctx.IsSignedIn = true
+52 -2
View File
@@ -267,6 +267,9 @@ func (h *ContextHandler) initContextWithAPIKey(reqContext *models.ReqContext) bo
_, span := h.tracer.Start(reqContext.Req.Context(), "initContextWithAPIKey")
defer span.End()
ctx := WithAuthHTTPHeader(reqContext.Req.Context(), "Authorization")
*reqContext.Req = *reqContext.Req.WithContext(ctx)
var (
apikey *apikey.APIKey
errKey error
@@ -356,7 +359,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)
@@ -365,12 +368,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,
@@ -651,6 +657,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
@@ -670,3 +685,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
}
+2 -2
View File
@@ -192,7 +192,7 @@ func (s *Service) handleQuerySingleDatasource(ctx context.Context, user *user.Si
middlewares := []httpclient.Middleware{}
if parsedReq.httpRequest != nil {
middlewares = append(middlewares,
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies()),
httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), []string{s.cfg.LoginCookieName}),
)
}
@@ -209,7 +209,7 @@ func (s *Service) handleQuerySingleDatasource(ctx context.Context, user *user.Si
}
if parsedReq.httpRequest != nil {
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies())
proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), []string{s.cfg.LoginCookieName})
if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" {
req.Headers["Cookie"] = cookieStr
}
+24 -1
View File
@@ -353,6 +353,29 @@ func TestQueryData(t *testing.T) {
require.Equal(t, map[string]string{"Cookie": "bar=rab; foo=oof"}, tc.pluginContext.req.Headers)
})
t.Run("it doesn't adds cookie header to the request when keepCookies configured with login cookie name", func(t *testing.T) {
tc := setup(t)
tc.queryService.cfg.LoginCookieName = "grafana_session"
json, err := simplejson.NewJson([]byte(`{"keepCookies": [ "grafana_session", "bar" ]}`))
require.NoError(t, err)
tc.dataSourceCache.ds.JsonData = json
metricReq := metricRequest()
httpReq, err := http.NewRequest(http.MethodGet, "/", nil)
require.NoError(t, err)
httpReq.AddCookie(&http.Cookie{Name: "a"})
httpReq.AddCookie(&http.Cookie{Name: "bar", Value: "rab"})
httpReq.AddCookie(&http.Cookie{Name: "b"})
httpReq.AddCookie(&http.Cookie{Name: "foo", Value: "oof"})
httpReq.AddCookie(&http.Cookie{Name: "c"})
httpReq.AddCookie(&http.Cookie{Name: tc.queryService.cfg.LoginCookieName, Value: "val"})
metricReq.HTTPRequest = httpReq
_, err = tc.queryService.QueryData(context.Background(), tc.signedInUser, true, metricReq)
require.NoError(t, err)
require.Equal(t, map[string]string{"Cookie": "bar=rab"}, tc.pluginContext.req.Headers)
})
}
func setup(t *testing.T) *testContext {
@@ -372,7 +395,7 @@ func setup(t *testing.T) *testContext {
SimulatePluginFailure: false,
}
exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, fakeDatasourceService)
queryService := ProvideService(nil, dc, exprService, rv, ds, pc, tc) // provider belonging to this package
queryService := ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc, tc) // provider belonging to this package
return &testContext{
pluginContext: pc,
secretStore: ss,