func parseRequestedSubject(requestedSubject string) (unversioned.GroupResource, string, string, error) { subjects := authorizationapi.BuildSubjects([]string{requestedSubject}, nil, // validates whether the usernames are regular users or system users uservalidation.ValidateUserName, // validates group names, but we never pass any groups func(s string, b bool) (bool, string) { return true, "" }) if len(subjects) == 0 { return unversioned.GroupResource{}, "", "", fmt.Errorf("subject must be in the form of a username, not %v", requestedSubject) } resource := unversioned.GroupResource{} switch subjects[0].GetObjectKind().GroupVersionKind().GroupKind() { case userapi.Kind(authorizationapi.UserKind): resource = userapi.Resource(authorizationapi.UserResource) case userapi.Kind(authorizationapi.SystemUserKind): resource = userapi.Resource(authorizationapi.SystemUserResource) case kapi.Kind(authorizationapi.ServiceAccountKind): resource = kapi.Resource(authorizationapi.ServiceAccountResource) default: return unversioned.GroupResource{}, "", "", fmt.Errorf("unknown subject type: %v", subjects[0]) } return resource, subjects[0].Namespace, subjects[0].Name, nil }
// NewREST returns a RESTStorage object that will work against groups func NewREST(s storage.Interface) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Group{} }, NewListFunc: func() runtime.Object { return &api.GroupList{} }, KeyRootFunc: func(ctx kapi.Context) string { return EtcdPrefix }, KeyFunc: func(ctx kapi.Context, name string) (string, error) { return util.NoNamespaceKeyFunc(ctx, EtcdPrefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Group).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return group.Matcher(label, field) }, QualifiedResource: api.Resource("groups"), CreateStrategy: group.Strategy, UpdateStrategy: group.Strategy, Storage: s, } return &REST{store} }
// NewREST returns a RESTStorage object that will work against users func NewREST(optsGetter restoptions.Getter) (*REST, error) { store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.User{} }, NewListFunc: func() runtime.Object { return &api.UserList{} }, KeyRootFunc: func(ctx kapi.Context) string { return EtcdPrefix }, KeyFunc: func(ctx kapi.Context, name string) (string, error) { return util.NoNamespaceKeyFunc(ctx, EtcdPrefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.User).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return user.Matcher(label, field) }, QualifiedResource: api.Resource("users"), CreateStrategy: user.Strategy, UpdateStrategy: user.Strategy, } if err := restoptions.ApplyOptions(optsGetter, store, EtcdPrefix); err != nil { return nil, err } return &REST{*store}, nil }
func (r *UserRegistry) GetUser(ctx kapi.Context, name string) (*api.User, error) { *r.Actions = append(*r.Actions, Action{"GetUser", name}) if user, ok := r.Get[name]; ok { return user, nil } if err, ok := r.GetErr[name]; ok { return nil, err } return nil, kerrs.NewNotFound(api.Resource("user"), name) }
func (r *IdentityRegistry) GetIdentity(ctx kapi.Context, name string) (*api.Identity, error) { *r.Actions = append(*r.Actions, Action{"GetIdentity", name}) if identity, ok := r.Get[name]; ok { return identity, nil } if err, ok := r.GetErr[name]; ok { return nil, err } return nil, kerrs.NewNotFound(api.Resource("identity"), name) }
func (p *provisioningIdentityMapper) getMapping(ctx kapi.Context, identity *userapi.Identity) (kuser.Info, error) { if len(identity.User.Name) == 0 { return nil, kerrs.NewNotFound(userapi.Resource("useridentitymapping"), identity.Name) } u, err := p.user.GetUser(ctx, identity.User.Name) if err != nil { return nil, err } if u.UID != identity.User.UID { glog.Errorf("identity.user.uid (%s) and user.uid (%s) do not match for identity %s", identity.User.UID, u.UID, identity.Name) return nil, kerrs.NewNotFound(userapi.Resource("useridentitymapping"), identity.Name) } if !sets.NewString(u.Identities...).Has(identity.Name) { glog.Errorf("user.identities (%#v) does not include identity (%s)", u, identity.Name) return nil, kerrs.NewNotFound(userapi.Resource("useridentitymapping"), identity.Name) } return &kuser.DefaultInfo{ Name: u.Name, UID: string(u.UID), Groups: u.Groups, }, nil }
// getRelatedObjects returns the identity, user, and mapping for the named identity // a nil mappingErr means all objects were retrieved without errors, and correctly reference each other func (s *REST) getRelatedObjects(ctx kapi.Context, name string) ( identity *api.Identity, identityErr error, user *api.User, userErr error, mapping *api.UserIdentityMapping, mappingErr error, ) { // Initialize errors to NotFound identityErr = kerrs.NewNotFound(api.Resource("identity"), name) userErr = kerrs.NewNotFound(api.Resource("user"), "") mappingErr = kerrs.NewNotFound(api.Resource("useridentitymapping"), name) // Get identity identity, identityErr = s.identityRegistry.GetIdentity(ctx, name) if identityErr != nil { return } if !hasUserMapping(identity) { return } // Get user user, userErr = s.userRegistry.GetUser(ctx, identity.User.Name) if userErr != nil { return } // Ensure relational integrity if !identityReferencesUser(identity, user) { return } if !userReferencesIdentity(user, identity) { return } mapping, mappingErr = mappingFor(user, identity) return }
// NewREST returns a RESTStorage object that will work against groups func NewREST(optsGetter restoptions.Getter) (*REST, error) { store := ®istry.Store{ NewFunc: func() runtime.Object { return &api.Group{} }, NewListFunc: func() runtime.Object { return &api.GroupList{} }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Group).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) storage.SelectionPredicate { return group.Matcher(label, field) }, QualifiedResource: api.Resource("groups"), CreateStrategy: group.Strategy, UpdateStrategy: group.Strategy, } if err := restoptions.ApplyOptions(optsGetter, store, false, storage.NoTriggerPublisher); err != nil { return nil, err } return &REST{store}, nil }
// Get retrieves the item from etcd. func (r *REST) Get(ctx kapi.Context, name string) (runtime.Object, error) { // "~" means the currently authenticated user if name == "~" { user, ok := kapi.UserFrom(ctx) if !ok || user.GetName() == "" { return nil, kerrs.NewForbidden(api.Resource("user"), "~", errors.New("requests to ~ must be authenticated")) } name = user.GetName() // remove the known virtual groups from the list if they are present contextGroups := sets.NewString(user.GetGroups()...) contextGroups.Delete(bootstrappolicy.UnauthenticatedGroup, bootstrappolicy.AuthenticatedGroup) if ok, _ := validation.ValidateUserName(name, false); !ok { // The user the authentication layer has identified cannot possibly be a persisted user // Return an API representation of the virtual user return &api.User{ObjectMeta: kapi.ObjectMeta{Name: name}, Groups: contextGroups.List()}, nil } obj, err := r.Store.Get(ctx, name) if err == nil { return obj, nil } if !kerrs.IsNotFound(err) { return nil, err } return &api.User{ObjectMeta: kapi.ObjectMeta{Name: name}, Groups: contextGroups.List()}, nil } if ok, details := validation.ValidateUserName(name, false); !ok { return nil, field.Invalid(field.NewPath("metadata", "name"), name, details) } return r.Store.Get(ctx, name) }
func (s *REST) createOrUpdate(ctx kapi.Context, obj runtime.Object, forceCreate bool) (runtime.Object, bool, error) { mapping := obj.(*api.UserIdentityMapping) identity, identityErr, oldUser, oldUserErr, oldMapping, oldMappingErr := s.getRelatedObjects(ctx, mapping.Name) // Ensure we didn't get any errors other than NotFound errors if !(oldMappingErr == nil || kerrs.IsNotFound(oldMappingErr)) { return nil, false, oldMappingErr } if !(identityErr == nil || kerrs.IsNotFound(identityErr)) { return nil, false, identityErr } if !(oldUserErr == nil || kerrs.IsNotFound(oldUserErr)) { return nil, false, oldUserErr } // If we expect to be creating, fail if the mapping already existed if forceCreate && oldMappingErr == nil { return nil, false, kerrs.NewAlreadyExists(api.Resource("useridentitymapping"), oldMapping.Name) } // Allow update to create if missing creating := forceCreate || kerrs.IsNotFound(oldMappingErr) if creating { // Pre-create checks with no access to oldMapping if err := rest.BeforeCreate(Strategy, ctx, mapping); err != nil { return nil, false, err } // Ensure resource version is not specified if len(mapping.ResourceVersion) > 0 { return nil, false, kerrs.NewNotFound(api.Resource("useridentitymapping"), mapping.Name) } } else { // Pre-update checks with access to oldMapping if err := rest.BeforeUpdate(Strategy, ctx, mapping, oldMapping); err != nil { return nil, false, err } // Ensure resource versions match if len(mapping.ResourceVersion) > 0 && mapping.ResourceVersion != oldMapping.ResourceVersion { return nil, false, kerrs.NewConflict(api.Resource("useridentitymapping"), mapping.Name, fmt.Errorf("the resource was updated to %s", oldMapping.ResourceVersion)) } // If we're "updating" to the user we're already pointing to, we're already done if mapping.User.Name == oldMapping.User.Name { return oldMapping, false, nil } } // Validate identity if kerrs.IsNotFound(identityErr) { errs := field.ErrorList{field.Invalid(field.NewPath("identity", "name"), mapping.Identity.Name, "referenced identity does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } // Get new user newUser, err := s.userRegistry.GetUser(ctx, mapping.User.Name) if kerrs.IsNotFound(err) { errs := field.ErrorList{field.Invalid(field.NewPath("user", "name"), mapping.User.Name, "referenced user does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } if err != nil { return nil, false, err } // Update the new user to point at the identity. If this fails, Update is re-entrant if addIdentityToUser(identity, newUser) { if _, err := s.userRegistry.UpdateUser(ctx, newUser); err != nil { return nil, false, err } } // Update the identity to point at the new user. If this fails. Update is re-entrant if setIdentityUser(identity, newUser) { if updatedIdentity, err := s.identityRegistry.UpdateIdentity(ctx, identity); err != nil { return nil, false, err } else { identity = updatedIdentity } } // At this point, the mapping for the identity has been updated to the new user // Everything past this point is cleanup // Update the old user to no longer point at the identity. // If this fails, log the error, but continue, because Update is no longer re-entrant if oldUser != nil && removeIdentityFromUser(identity, oldUser) { if _, err := s.userRegistry.UpdateUser(ctx, oldUser); err != nil { utilruntime.HandleError(fmt.Errorf("error removing identity reference %s from user %s: %v", identity.Name, oldUser.Name, err)) } } updatedMapping, err := mappingFor(newUser, identity) return updatedMapping, creating, err }
func TestUserInitialization(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } optsGetter := originrest.StorageOptions(*masterConfig) userStorage, err := useretcd.NewREST(optsGetter) if err != nil { t.Fatalf("unexpected error: %v", err) } userRegistry := userregistry.NewRegistry(userStorage) identityStorage, err := identityetcd.NewREST(optsGetter) if err != nil { t.Fatalf("unexpected error: %v", err) } identityRegistry := identityregistry.NewRegistry(identityStorage) lookup, err := identitymapper.NewIdentityUserMapper(identityRegistry, userRegistry, identitymapper.MappingMethodLookup) if err != nil { t.Fatalf("unexpected error: %v", err) } generate, err := identitymapper.NewIdentityUserMapper(identityRegistry, userRegistry, identitymapper.MappingMethodGenerate) if err != nil { t.Fatalf("unexpected error: %v", err) } add, err := identitymapper.NewIdentityUserMapper(identityRegistry, userRegistry, identitymapper.MappingMethodAdd) if err != nil { t.Fatalf("unexpected error: %v", err) } claim, err := identitymapper.NewIdentityUserMapper(identityRegistry, userRegistry, identitymapper.MappingMethodClaim) if err != nil { t.Fatalf("unexpected error: %v", err) } testcases := map[string]struct { Identity authapi.UserIdentityInfo Mapper authapi.UserIdentityMapper CreateIdentity *api.Identity CreateUser *api.User CreateMapping *api.UserIdentityMapping UpdateUser *api.User ExpectedErr error ExpectedUserName string ExpectedFullName string ExpectedIdentities []string }{ "lookup missing identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: lookup, ExpectedErr: identitymapper.NewLookupError(makeIdentityInfo("idp", "bob", nil), kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob")), }, "lookup existing identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: lookup, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "generate missing identity and user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "generate missing identity and user with preferred username and display name": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityDisplayNameKey: "Bob, Sr.", authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: generate, ExpectedUserName: "******", ExpectedFullName: "Bob, Sr.", ExpectedIdentities: []string{"idp:bob"}, }, "generate missing identity for existing user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateUser: makeUser("bob", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "generate missing identity with conflicting user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateUser: makeUser("bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "generate missing identity with conflicting user and preferred username": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: generate, CreateUser: makeUser("admin"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "generate with existing unmapped identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateIdentity: makeIdentity("idp", "bob"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "generate with existing mapped identity with invalid user UID": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentityWithUserReference("idp", "bob", "mappeduser", "invalidUID"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), ExpectedIdentities: []string{"idp:bob"}, }, "generate with existing mapped identity without user backreference": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), // Update user to a version which does not reference the identity UpdateUser: makeUser("mappeduser"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "generate returns existing mapping": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: generate, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "add missing identity and user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "add missing identity and user with preferred username and display name": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityDisplayNameKey: "Bob, Sr.", authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: add, ExpectedUserName: "******", ExpectedFullName: "Bob, Sr.", ExpectedIdentities: []string{"idp:bob"}, }, "add missing identity for existing user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateUser: makeUser("bob", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "add missing identity with conflicting user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateUser: makeUser("bob", "otheridp:otheruser"), ExpectedUserName: "******", ExpectedIdentities: []string{"otheridp:otheruser", "idp:bob"}, }, "add missing identity with conflicting user and preferred username": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: add, CreateUser: makeUser("admin", "otheridp:otheruser"), ExpectedUserName: "******", ExpectedIdentities: []string{"otheridp:otheruser", "idp:bob"}, }, "add with existing unmapped identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateIdentity: makeIdentity("idp", "bob"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "add with existing mapped identity with invalid user UID": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentityWithUserReference("idp", "bob", "mappeduser", "invalidUID"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "add with existing mapped identity without user backreference": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), // Update user to a version which does not reference the identity UpdateUser: makeUser("mappeduser"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "add returns existing mapping": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: add, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "claim missing identity and user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "claim missing identity and user with preferred username and display name": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityDisplayNameKey: "Bob, Sr.", authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: claim, ExpectedUserName: "******", ExpectedFullName: "Bob, Sr.", ExpectedIdentities: []string{"idp:bob"}, }, "claim missing identity for existing user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("bob", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "claim missing identity with existing available user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, "claim missing identity with conflicting user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("bob", "otheridp:otheruser"), ExpectedErr: identitymapper.NewClaimError(makeUser("bob", "otheridp:otheruser"), makeIdentity("idp", "bob")), }, "claim missing identity with conflicting user and preferred username": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: claim, CreateUser: makeUser("admin", "otheridp:otheruser"), ExpectedErr: identitymapper.NewClaimError(makeUser("admin", "otheridp:otheruser"), makeIdentity("idp", "bob")), }, "claim with existing unmapped identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateIdentity: makeIdentity("idp", "bob"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "claim with existing mapped identity with invalid user UID": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentityWithUserReference("idp", "bob", "mappeduser", "invalidUID"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "claim with existing mapped identity without user backreference": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), // Update user to a version which does not reference the identity UpdateUser: makeUser("mappeduser"), ExpectedErr: kerrs.NewNotFound(api.Resource("useridentitymapping"), "idp:bob"), }, "claim returns existing mapping": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: claim, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", ExpectedIdentities: []string{"idp:bob"}, }, } oldEtcdClient, err := etcd.MakeNewEtcdClient(masterConfig.EtcdClientInfo) if err != nil { t.Fatalf("unexpected error: %v", err) } etcdClient := etcdclient.NewKeysAPI(oldEtcdClient) for k, testcase := range testcases { // Cleanup if _, err := etcdClient.Delete(context.Background(), path.Join(masterConfig.EtcdStorageConfig.OpenShiftStoragePrefix, "/users"), &etcdclient.DeleteOptions{Recursive: true}); err != nil && !etcdutil.IsEtcdNotFound(err) { t.Fatalf("Could not clean up users: %v", err) } if _, err := etcdClient.Delete(context.Background(), path.Join(masterConfig.EtcdStorageConfig.OpenShiftStoragePrefix, "/identities"), &etcdclient.DeleteOptions{Recursive: true}); err != nil && !etcdutil.IsEtcdNotFound(err) { t.Fatalf("Could not clean up identities: %v", err) } // Pre-create items if testcase.CreateUser != nil { _, err := clusterAdminClient.Users().Create(testcase.CreateUser) if err != nil { t.Errorf("%s: Could not create user: %v", k, err) continue } } if testcase.CreateIdentity != nil { _, err := clusterAdminClient.Identities().Create(testcase.CreateIdentity) if err != nil { t.Errorf("%s: Could not create identity: %v", k, err) continue } } if testcase.CreateMapping != nil { _, err := clusterAdminClient.UserIdentityMappings().Update(testcase.CreateMapping) if err != nil { t.Errorf("%s: Could not create mapping: %v", k, err) continue } } if testcase.UpdateUser != nil { if testcase.UpdateUser.ResourceVersion == "" { existingUser, err := clusterAdminClient.Users().Get(testcase.UpdateUser.Name) if err != nil { t.Errorf("%s: Could not get user to update: %v", k, err) continue } testcase.UpdateUser.ResourceVersion = existingUser.ResourceVersion } _, err := clusterAdminClient.Users().Update(testcase.UpdateUser) if err != nil { t.Errorf("%s: Could not update user: %v", k, err) continue } } // Spawn 5 simultaneous mappers to test race conditions var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() userInfo, err := testcase.Mapper.UserFor(testcase.Identity) if err != nil { if testcase.ExpectedErr == nil { t.Errorf("%s: Expected success, got error '%v'", k, err) } else if err.Error() != testcase.ExpectedErr.Error() { t.Errorf("%s: Expected error %v, got '%v'", k, testcase.ExpectedErr.Error(), err) } return } if err == nil && testcase.ExpectedErr != nil { t.Errorf("%s: Expected error '%v', got none", k, testcase.ExpectedErr) return } if userInfo.GetName() != testcase.ExpectedUserName { t.Errorf("%s: Expected username %s, got %s", k, testcase.ExpectedUserName, userInfo.GetName()) return } user, err := clusterAdminClient.Users().Get(userInfo.GetName()) if err != nil { t.Errorf("%s: Error getting user: %v", k, err) } if user.FullName != testcase.ExpectedFullName { t.Errorf("%s: Expected full name %s, got %s", k, testcase.ExpectedFullName, user.FullName) } if !reflect.DeepEqual(user.Identities, testcase.ExpectedIdentities) { t.Errorf("%s: Expected identities %v, got %v", k, testcase.ExpectedIdentities, user.Identities) } }() } wg.Wait() } }
func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } resource, namespace, name, err := parseRequestedSubject(requestedSubject) if err != nil { forbidden(err.Error(), nil, w, req) return } ctx, exists := c.RequestContextMapper.Get(req) if !exists { forbidden("context not found", nil, w, req) return } actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{ Verb: "impersonate", APIGroup: resource.Group, Resource: resource.Resource, ResourceName: name, } authCheckCtx := kapi.WithNamespace(ctx, namespace) allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes) if err != nil { forbidden(err.Error(), actingAsAttributes, w, req) return } if !allowed { forbidden(reason, actingAsAttributes, w, req) return } var extra map[string][]string if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok { extra = map[string][]string{authorizationapi.ScopesKey: requestScopes} } switch resource { case kapi.Resource(authorizationapi.ServiceAccountResource): newUser := &user.DefaultInfo{ Name: serviceaccount.MakeUsername(namespace, name), Groups: serviceaccount.MakeGroupNames(namespace, name), Extra: extra, } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.UserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } groups, err := c.GroupCache.GroupsFor(name) if err == nil { for _, group := range groups { newUser.Groups = append(newUser.Groups, group.Name) } } newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup) c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) case userapi.Resource(authorizationapi.SystemUserResource): newUser := &user.DefaultInfo{ Name: name, Extra: extra, } if name == bootstrappolicy.UnauthenticatedUsername { newUser.Groups = append(newUser.Groups, bootstrappolicy.UnauthenticatedGroup) } else { newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup) } c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser)) default: forbidden(fmt.Sprintf("%v is an unhandled resource for acting-as", resource), nil, w, req) return } newCtx, _ := c.RequestContextMapper.Get(req) oldUser, _ := kapi.UserFrom(ctx) newUser, _ := kapi.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
func TestProvision(t *testing.T) { testcases := map[string]struct { ProviderName string ProviderUserName string ExistingIdentity *userapi.Identity ExistingUser *userapi.User NewIdentityGetterResponses []interface{} ExpectedActions []test.Action ExpectedError bool ExpectedUserName string }{ "no identity, create user succeeds": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, alreadyexists error retries": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewAlreadyExists(userapi.Resource("User"), "bob"), makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, conflict error retries": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewConflict(userapi.Resource("User"), "bob", fmt.Errorf("conflict")), makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, only retries 3 times": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewConflict(userapi.Resource("User"), "bob", fmt.Errorf("conflict")), kerrs.NewConflict(userapi.Resource("User"), "bob", fmt.Errorf("conflict")), kerrs.NewConflict(userapi.Resource("User"), "bob", fmt.Errorf("conflict")), kerrs.NewConflict(userapi.Resource("User"), "bob", fmt.Errorf("conflict")), }, ExpectedActions: []test.Action{ // original attempt {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #1 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #2 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #3 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error }, ExpectedError: true, }, "no identity, unknown error does not retry": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ fmt.Errorf("other error"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error }, ExpectedError: true, }, "existing identity, no user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "", ""), ExistingUser: nil, NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, }, ExpectedError: true, }, "existing identity, missing user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: nil, NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, invalid user UID reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUIDInvalid", "bob"), ExistingUser: makeUser("bobUserUID", "bob", "idp:bob"), NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, user reference without identity backreference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: makeUser("bobUserUID", "bob" /*, "idp:bob"*/), NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: makeUser("bobUserUID", "bob", "idp:bob"), NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedUserName: "******", }, } for k, tc := range testcases { actions := []test.Action{} identityRegistry := &test.IdentityRegistry{ Get: map[string]*api.Identity{}, Actions: &actions, } userRegistry := &test.UserRegistry{ Get: map[string]*api.User{}, Actions: &actions, } if tc.ExistingIdentity != nil { identityRegistry.Get[tc.ExistingIdentity.Name] = tc.ExistingIdentity } if tc.ExistingUser != nil { userRegistry.Get[tc.ExistingUser.Name] = tc.ExistingUser } newIdentityUserGetter := &testNewIdentityGetter{responses: tc.NewIdentityGetterResponses} provisionMapper := &provisioningIdentityMapper{ identity: identityRegistry, user: userRegistry, provisioningStrategy: newIdentityUserGetter, } identity := authapi.NewDefaultUserIdentityInfo(tc.ProviderName, tc.ProviderUserName) user, err := provisionMapper.UserFor(identity) if tc.ExpectedError != (err != nil) { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedError, err) continue } if !tc.ExpectedError && user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username %v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } if newIdentityUserGetter.called != len(tc.NewIdentityGetterResponses) { t.Errorf("%s: Expected %d calls to UserForNewIdentity, got %d", k, len(tc.NewIdentityGetterResponses), newIdentityUserGetter.called) } for i, action := range actions { if len(tc.ExpectedActions) <= i { t.Fatalf("%s: expected %d actions, got extras: %#v", k, len(tc.ExpectedActions), actions[i:]) continue } expectedAction := tc.ExpectedActions[i] if !reflect.DeepEqual(expectedAction, action) { t.Fatalf("%s: expected\n\t%s %#v\nGot\n\t%s %#v", k, expectedAction.Name, expectedAction.Object, action.Name, action.Object) continue } } if len(actions) < len(tc.ExpectedActions) { t.Errorf("Missing %d additional actions:\n\t%#v", len(tc.ExpectedActions)-len(actions), tc.ExpectedActions[len(actions):]) } } }