// 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, ) }
func (m *fakeRequestContextMapper) Get(req *http.Request) (request.Context, bool) { ctx := request.NewContext() if m.user != nil { ctx = request.WithUser(ctx, m.user) } resolver := newTestRequestInfoResolver() info, err := resolver.NewRequestInfo(req) if err == nil { ctx = request.WithRequestInfo(ctx, info) } return ctx, true }
//TestUserContext validates that a userinfo can be get/set on a context object func TestUserContext(t *testing.T) { ctx := genericapirequest.NewContext() _, ok := genericapirequest.UserFrom(ctx) if ok { t.Fatalf("Should not be ok because there is no user.Info on the context") } ctx = genericapirequest.WithUser( ctx, &user.DefaultInfo{ Name: "bob", UID: "123", Groups: []string{"group1"}, Extra: map[string][]string{"foo": {"bar"}}, }, ) result, ok := genericapirequest.UserFrom(ctx) if !ok { t.Fatalf("Error getting user info") } expectedName := "bob" if result.GetName() != expectedName { t.Fatalf("Get user name error, Expected: %s, Actual: %s", expectedName, result.GetName()) } expectedUID := "123" if result.GetUID() != expectedUID { t.Fatalf("Get UID error, Expected: %s, Actual: %s", expectedUID, result.GetName()) } expectedGroup := "group1" actualGroup := result.GetGroups() if len(actualGroup) != 1 { t.Fatalf("Get user group number error, Expected: 1, Actual: %d", len(actualGroup)) } else if actualGroup[0] != expectedGroup { t.Fatalf("Get user group error, Expected: %s, Actual: %s", expectedGroup, actualGroup[0]) } expectedExtraKey := "foo" expectedExtraValue := "bar" actualExtra := result.GetExtra() if len(actualExtra[expectedExtraKey]) != 1 { t.Fatalf("Get user extra map number error, Expected: 1, Actual: %d", len(actualExtra[expectedExtraKey])) } else if actualExtra[expectedExtraKey][0] != expectedExtraValue { t.Fatalf("Get user extra map value error, Expected: %s, Actual: %s", expectedExtraValue, actualExtra[expectedExtraKey]) } }
func (m *fakeRequestContextMapper) Get(req *http.Request) (genericapirequest.Context, bool) { ctx := genericapirequest.NewContext() if m.user != nil { ctx = genericapirequest.WithUser(ctx, m.user) } resolver := &genericapirequest.RequestInfoFactory{ APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api"), } info, err := resolver.NewRequestInfo(req) if err == nil { ctx = genericapirequest.WithRequestInfo(ctx, info) } return ctx, true }
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 TestStrategyCreate(t *testing.T) { tests := map[string]struct { ctx genericapirequest.Context obj runtime.Object expectedObj runtime.Object }{ "no user in context, no user in obj": { ctx: genericapirequest.NewContext(), obj: &certapi.CertificateSigningRequest{}, expectedObj: &certapi.CertificateSigningRequest{ Status: certapi.CertificateSigningRequestStatus{Conditions: []certapi.CertificateSigningRequestCondition{}}, }, }, "user in context, no user in obj": { ctx: genericapirequest.WithUser( genericapirequest.NewContext(), &user.DefaultInfo{ Name: "bob", UID: "123", Groups: []string{"group1"}, Extra: map[string][]string{"foo": {"bar"}}, }, ), obj: &certapi.CertificateSigningRequest{}, expectedObj: &certapi.CertificateSigningRequest{ Spec: certapi.CertificateSigningRequestSpec{ Username: "******", UID: "123", Groups: []string{"group1"}, }, Status: certapi.CertificateSigningRequestStatus{Conditions: []certapi.CertificateSigningRequestCondition{}}, }, }, "no user in context, user in obj": { ctx: genericapirequest.NewContext(), obj: &certapi.CertificateSigningRequest{ Spec: certapi.CertificateSigningRequestSpec{ Username: "******", UID: "123", Groups: []string{"group1"}, }, }, expectedObj: &certapi.CertificateSigningRequest{ Status: certapi.CertificateSigningRequestStatus{Conditions: []certapi.CertificateSigningRequestCondition{}}, }, }, "user in context, user in obj": { ctx: genericapirequest.WithUser( genericapirequest.NewContext(), &user.DefaultInfo{ Name: "alice", UID: "234", }, ), obj: &certapi.CertificateSigningRequest{ Spec: certapi.CertificateSigningRequestSpec{ Username: "******", UID: "123", Groups: []string{"group1"}, }, }, expectedObj: &certapi.CertificateSigningRequest{ Spec: certapi.CertificateSigningRequestSpec{ Username: "******", UID: "234", Groups: nil, }, Status: certapi.CertificateSigningRequestStatus{Conditions: []certapi.CertificateSigningRequestCondition{}}, }, }, "pre-approved status": { ctx: genericapirequest.NewContext(), obj: &certapi.CertificateSigningRequest{ Status: certapi.CertificateSigningRequestStatus{ Conditions: []certapi.CertificateSigningRequestCondition{ {Type: certapi.CertificateApproved}, }, }, }, expectedObj: &certapi.CertificateSigningRequest{ Status: certapi.CertificateSigningRequestStatus{Conditions: []certapi.CertificateSigningRequestCondition{}}, }}, } for k, tc := range tests { obj := tc.obj Strategy.PrepareForCreate(tc.ctx, obj) if !reflect.DeepEqual(obj, tc.expectedObj) { t.Errorf("%s: object diff: %s", k, diff.ObjectDiff(obj, tc.expectedObj)) } } }
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests func WithImpersonation(handler http.Handler, requestContextMapper request.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { impersonationRequests, err := buildImpersonationRequests(req.Header) if err != nil { glog.V(4).Infof("%v", err) responsewriters.InternalError(w, req, err) return } if len(impersonationRequests) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } requestor, exists := request.UserFrom(ctx) if !exists { responsewriters.InternalError(w, req, errors.New("no user found for request")) return } // if groups are not specified, then we need to look them up differently depending on the type of user // if they are specified, then they are the authority groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0 // make sure we're allowed to impersonate each thing we're requesting. While we're iterating through, start building username // and group information username := "" groups := []string{} userExtra := map[string][]string{} for _, impersonationRequest := range impersonationRequests { actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: impersonationRequest.GetObjectKind().GroupVersionKind().Group, Namespace: impersonationRequest.Namespace, Name: impersonationRequest.Name, ResourceRequest: true, } switch impersonationRequest.GetObjectKind().GroupVersionKind().GroupKind() { case api.Kind("ServiceAccount"): actingAsAttributes.Resource = "serviceaccounts" username = serviceaccount.MakeUsername(impersonationRequest.Namespace, impersonationRequest.Name) if !groupsSpecified { // if groups aren't specified for a service account, we know the groups because its a fixed mapping. Add them groups = serviceaccount.MakeGroupNames(impersonationRequest.Namespace, impersonationRequest.Name) } case api.Kind("User"): actingAsAttributes.Resource = "users" username = impersonationRequest.Name case api.Kind("Group"): actingAsAttributes.Resource = "groups" groups = append(groups, impersonationRequest.Name) case authenticationapi.Kind("UserExtra"): extraKey := impersonationRequest.FieldPath extraValue := impersonationRequest.Name actingAsAttributes.Resource = "userextras" actingAsAttributes.Subresource = extraKey userExtra[extraKey] = append(userExtra[extraKey], extraValue) default: glog.V(4).Infof("unknown impersonation request type: %v", impersonationRequest) responsewriters.Forbidden(actingAsAttributes, w, req, fmt.Sprintf("unknown impersonation request type: %v", impersonationRequest)) return } allowed, reason, err := a.Authorize(actingAsAttributes) if err != nil || !allowed { glog.V(4).Infof("Forbidden: %#v, Reason: %s, Error: %v", req.RequestURI, reason, err) responsewriters.Forbidden(actingAsAttributes, w, req, reason) return } } newUser := &user.DefaultInfo{ Name: username, Groups: groups, Extra: userExtra, } requestContextMapper.Update(req, request.WithUser(ctx, newUser)) oldUser, _ := request.UserFrom(ctx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) // clear all the impersonation headers from the request req.Header.Del(authenticationapi.ImpersonateUserHeader) req.Header.Del(authenticationapi.ImpersonateGroupHeader) for headerName := range req.Header { if strings.HasPrefix(headerName, authenticationapi.ImpersonateUserExtraHeaderPrefix) { req.Header.Del(headerName) } } handler.ServeHTTP(w, req) }) }