// WithAuthentication creates an http handler that tries to authenticate the given request as a user, and then // stores any such user found onto the provided context for the request. If authentication fails or returns an error // the failed handler is used. On success, "Authorization" header is removed from the request and handler // is invoked to serve the request. func WithAuthentication(handler http.Handler, mapper genericapirequest.RequestContextMapper, auth authenticator.Request, failed http.Handler) http.Handler { if auth == nil { glog.Warningf("Authentication is disabled") return handler } return genericapirequest.WithRequestContext( http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { user, ok, err := auth.AuthenticateRequest(req) if err != nil || !ok { if err != nil { glog.Errorf("Unable to authenticate the request due to an error: %v", err) } failed.ServeHTTP(w, req) return } // authorization header is not required anymore in case of a successful authentication. req.Header.Del("Authorization") if ctx, ok := mapper.Get(req); ok { mapper.Update(req, genericapirequest.WithUser(ctx, user)) } authenticatedUserCounter.WithLabelValues(compressUsername(user.GetName())).Inc() handler.ServeHTTP(w, req) }), mapper, ) }
// handlerChain is a method to build the handler chain for this API server. We need a custom handler chain so that we // can have custom handling for `/apis`, since we're hosting discovery differently from anyone else and we're hosting // the endpoints differently, since we're proxying all groups except for apiregistration.k8s.io. func (h *handlerChainConfig) handlerChain(apiHandler http.Handler, c *genericapiserver.Config) (secure, insecure http.Handler) { // add this as a filter so that we never collide with "already registered" failures on `/apis` handler := WithAPIs(apiHandler, h.informers.Apiregistration().InternalVersion().APIServices(), h.serviceLister, h.endpointsLister) handler = genericapifilters.WithAuthorization(handler, c.RequestContextMapper, c.Authorizer) // this mux is NOT protected by authorization, but DOES have authentication information // this is so that everyone can hit the proxy and we can properly identify the user. The backing // API server will deal with authorization handler = WithProxyMux(handler, h.proxyMux) handler = genericapifilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer) // audit to stdout to help with debugging as we get this started handler = genericapifilters.WithAudit(handler, c.RequestContextMapper, os.Stdout) handler = genericapifilters.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, genericapifilters.Unauthorized(c.SupportsBasicAuth)) handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper) handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc) handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc) handler = genericapifilters.WithRequestInfo(handler, genericapiserver.NewRequestInfoResolver(c), c.RequestContextMapper) handler = genericapirequest.WithRequestContext(handler, c.RequestContextMapper) return handler, nil }
func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) (secure, insecure http.Handler) { generic := func(handler http.Handler) http.Handler { handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") handler = genericfilters.WithPanicRecovery(handler, c.RequestContextMapper) handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc) handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.RequestContextMapper, c.LongRunningFunc) handler = genericapifilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper) handler = apirequest.WithRequestContext(handler, c.RequestContextMapper) return handler } audit := func(handler http.Handler) http.Handler { return genericapifilters.WithAudit(handler, c.RequestContextMapper, c.AuditWriter) } protect := func(handler http.Handler) http.Handler { handler = genericapifilters.WithAuthorization(handler, c.RequestContextMapper, c.Authorizer) handler = genericapifilters.WithImpersonation(handler, c.RequestContextMapper, c.Authorizer) handler = audit(handler) // before impersonation to read original user handler = genericapifilters.WithAuthentication(handler, c.RequestContextMapper, c.Authenticator, genericapifilters.Unauthorized(c.SupportsBasicAuth)) return handler } return generic(protect(apiHandler)), generic(audit(apiHandler)) }
func TestImpersonationFilter(t *testing.T) { testCases := []struct { name string user user.Info impersonationUser string impersonationGroups []string impersonationUserExtras map[string][]string expectedUser user.Info expectedCode int }{ { name: "not-impersonating", user: &user.DefaultInfo{ Name: "tester", }, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusOK, }, { name: "impersonating-error", user: &user.DefaultInfo{ Name: "tester", }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusForbidden, }, { name: "impersonating-group-without-user", user: &user.DefaultInfo{ Name: "tester", }, impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusInternalServerError, }, { name: "impersonating-extra-without-user", user: &user.DefaultInfo{ Name: "tester", }, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "tester", }, expectedCode: http.StatusInternalServerError, }, { name: "disallowed-group", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-group", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "group-impersonater"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, expectedUser: &user.DefaultInfo{ Name: "system:admin", Groups: []string{"some-group"}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, { name: "disallowed-userextra-1", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel"}, }, expectedCode: http.StatusForbidden, }, { name: "disallowed-userextra-2", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-project"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-project"}, }, expectedCode: http.StatusForbidden, }, { name: "disallowed-userextra-3", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-particular-scopes"}, }, impersonationUser: "******", impersonationGroups: []string{"some-group"}, impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}}, expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-particular-scopes"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-userextras", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"wheel", "extra-setter-scopes"}, }, impersonationUser: "******", impersonationUserExtras: map[string][]string{"scopes": {"scope-a", "scope-b"}}, expectedUser: &user.DefaultInfo{ Name: "system:admin", Groups: []string{}, Extra: map[string][]string{"scopes": {"scope-a", "scope-b"}}, }, expectedCode: http.StatusOK, }, { name: "allowed-users-impersonation", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"regular-impersonater"}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "tester", Groups: []string{}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, { name: "disallowed-impersonating", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, }, expectedCode: http.StatusForbidden, }, { name: "allowed-sa-impersonating", user: &user.DefaultInfo{ Name: "dev", Groups: []string{"sa-impersonater"}, Extra: map[string][]string{}, }, impersonationUser: "******", expectedUser: &user.DefaultInfo{ Name: "system:serviceaccount:foo:default", Groups: []string{"system:serviceaccounts", "system:serviceaccounts:foo"}, Extra: map[string][]string{}, }, expectedCode: http.StatusOK, }, } requestContextMapper := request.NewRequestContextMapper() var ctx request.Context var actualUser user.Info var lock sync.Mutex doNothingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { currentCtx, _ := requestContextMapper.Get(req) user, exists := request.UserFrom(currentCtx) if !exists { actualUser = nil return } actualUser = user }) handler := func(delegate http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { if r := recover(); r != nil { t.Errorf("Recovered %v", r) } }() lock.Lock() defer lock.Unlock() requestContextMapper.Update(req, ctx) currentCtx, _ := requestContextMapper.Get(req) user, exists := request.UserFrom(currentCtx) if !exists { actualUser = nil return } else { actualUser = user } delegate.ServeHTTP(w, req) }) }(WithImpersonation(doNothingHandler, requestContextMapper, impersonateAuthorizer{})) handler = request.WithRequestContext(handler, requestContextMapper) server := httptest.NewServer(handler) defer server.Close() for _, tc := range testCases { func() { lock.Lock() defer lock.Unlock() ctx = request.WithUser(request.NewContext(), tc.user) }() req, err := http.NewRequest("GET", server.URL, nil) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) continue } req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationUser) for _, group := range tc.impersonationGroups { req.Header.Add(authenticationapi.ImpersonateGroupHeader, group) } for extraKey, values := range tc.impersonationUserExtras { for _, value := range values { req.Header.Add(authenticationapi.ImpersonateUserExtraHeaderPrefix+extraKey, value) } } resp, err := http.DefaultClient.Do(req) if err != nil { t.Errorf("%s: unexpected error: %v", tc.name, err) continue } if resp.StatusCode != tc.expectedCode { t.Errorf("%s: expected %v, actual %v", tc.name, tc.expectedCode, resp.StatusCode) continue } if !reflect.DeepEqual(actualUser, tc.expectedUser) { t.Errorf("%s: expected %#v, actual %#v", tc.name, tc.expectedUser, actualUser) continue } } }
func TestGetAuthorizerAttributes(t *testing.T) { mapper := request.NewRequestContextMapper() testcases := map[string]struct { Verb string Path string ExpectedAttributes *authorizer.AttributesRecord }{ "non-resource root": { Verb: "POST", Path: "/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "post", Path: "/", }, }, "non-resource api prefix": { Verb: "GET", Path: "/api/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "get", Path: "/api/", }, }, "non-resource group api prefix": { Verb: "GET", Path: "/apis/extensions/", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "get", Path: "/apis/extensions/", }, }, "resource": { Verb: "POST", Path: "/api/v1/nodes/mynode", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "create", Path: "/api/v1/nodes/mynode", ResourceRequest: true, Resource: "nodes", APIVersion: "v1", Name: "mynode", }, }, "namespaced resource": { Verb: "PUT", Path: "/api/v1/namespaces/myns/pods/mypod", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "update", Path: "/api/v1/namespaces/myns/pods/mypod", ResourceRequest: true, Namespace: "myns", Resource: "pods", APIVersion: "v1", Name: "mypod", }, }, "API group resource": { Verb: "GET", Path: "/apis/batch/v1/namespaces/myns/jobs", ExpectedAttributes: &authorizer.AttributesRecord{ Verb: "list", Path: "/apis/batch/v1/namespaces/myns/jobs", ResourceRequest: true, APIGroup: batch.GroupName, APIVersion: "v1", Namespace: "myns", Resource: "jobs", }, }, } for k, tc := range testcases { req, _ := http.NewRequest(tc.Verb, tc.Path, nil) req.RemoteAddr = "127.0.0.1" var attribs authorizer.Attributes var err error var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, ok := mapper.Get(req) if !ok { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } attribs, err = GetAuthorizerAttributes(ctx) }) handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper) handler = request.WithRequestContext(handler, mapper) handler.ServeHTTP(httptest.NewRecorder(), req) if err != nil { t.Errorf("%s: unexpected error: %v", k, err) } else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) { t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs) } } }