func TestUnprivilegedNewProjectDenied(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, 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) } role, err := clusterAdminClient.ClusterRoles().Get(bootstrappolicy.SelfProvisionerRoleName) if err != nil { t.Fatalf("unexpected error: %v", err) } role.Rules = []authorizationapi.PolicyRule{} if _, err := clusterAdminClient.ClusterRoles().Update(role); err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } valerieClientConfig := *clusterAdminClientConfig valerieClientConfig.Username = "" valerieClientConfig.Password = "" valerieClientConfig.BearerToken = "" valerieClientConfig.CertFile = "" valerieClientConfig.KeyFile = "" valerieClientConfig.CertData = nil valerieClientConfig.KeyData = nil accessToken, err := tokencmd.RequestToken(&valerieClientConfig, nil, "valerie", "security!") if err != nil { t.Fatalf("unexpected error: %v", err) } valerieClientConfig.BearerToken = accessToken valerieOpenshiftClient, err := client.New(&valerieClientConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } if err := testutil.WaitForClusterPolicyUpdate(valerieOpenshiftClient, "create", projectapi.Resource("projectrequests"), false); err != nil { t.Fatalf("unexpected error: %v", err) } // confirm that we have access to request the project _, err = valerieOpenshiftClient.ProjectRequests().List(kapi.ListOptions{}) if err == nil { t.Fatalf("expected error: %v", err) } expectedError := `You may not request a new project via this API.` if (err != nil) && (err.Error() != expectedError) { t.Fatalf("expected\n\t%v\ngot\n\t%v", expectedError, err.Error()) } }
// Get retrieves the Project from the index for a given name. func (s *projectLister) Get(name string) (*v1.Project, error) { key := &v1.Project{ObjectMeta: api_v1.ObjectMeta{Name: name}} obj, exists, err := s.indexer.Get(key) if err != nil { return nil, err } if !exists { return nil, errors.NewNotFound(api.Resource("project"), name) } return obj.(*v1.Project), nil }
// List retrieves a list of Projects that match label. func (s *REST) List(ctx kapi.Context, options *kapi.ListOptions) (runtime.Object, error) { user, ok := kapi.UserFrom(ctx) if !ok { return nil, kerrors.NewForbidden(projectapi.Resource("project"), "", fmt.Errorf("unable to list projects without a user on the context")) } namespaceList, err := s.lister.List(user) if err != nil { return nil, err } m := nsregistry.MatchNamespace(oapi.ListOptionsToSelectors(options)) list, err := filterList(namespaceList, m, nil) if err != nil { return nil, err } return convertNamespaceList(list.(*kapi.NamespaceList)), nil }
// Admit ensures that only a configured number of projects can be requested by a particular user. func (o *projectRequestLimit) Admit(a admission.Attributes) (err error) { if a.GetResource() != projectapi.Resource("projectrequests") { return nil } if _, isProjectRequest := a.GetObject().(*projectapi.ProjectRequest); !isProjectRequest { return nil } userName := a.GetUserInfo().GetName() projectCount, err := o.projectCountByRequester(userName) if err != nil { return err } maxProjects, hasLimit, err := o.maxProjectsByRequester(userName) if err != nil { return err } if hasLimit && projectCount >= maxProjects { return admission.NewForbidden(a, fmt.Errorf("user %s cannot create more than %d project(s).", userName, maxProjects)) } return nil }
func (r *REST) List(ctx kapi.Context, options *kapi.ListOptions) (runtime.Object, error) { userInfo, exists := kapi.UserFrom(ctx) if !exists { return nil, errors.New("a user must be provided") } // the caller might not have permission to run a subject access review (he has it by default, but it could have been removed). // So we'll escalate for the subject access review to determine rights accessReview := &authorizationapi.SubjectAccessReview{ Action: authorizationapi.AuthorizationAttributes{ Verb: "create", Group: projectapi.GroupName, Resource: "projectrequests", }, User: userInfo.GetName(), Groups: sets.NewString(userInfo.GetGroups()...), } accessReviewResponse, err := r.openshiftClient.SubjectAccessReviews().Create(accessReview) if err != nil { return nil, err } if accessReviewResponse.Allowed { return &unversioned.Status{Status: unversioned.StatusSuccess}, nil } forbiddenError, _ := kapierror.NewForbidden(projectapi.Resource("projectrequest"), "", errors.New("you may not request a new project via this API.")).(*kapierror.StatusError) if len(r.message) > 0 { forbiddenError.ErrStatus.Message = r.message forbiddenError.ErrStatus.Details = &unversioned.StatusDetails{ Kind: "ProjectRequest", Causes: []unversioned.StatusCause{ {Message: r.message}, }, } } else { forbiddenError.ErrStatus.Message = "You may not request a new project via this API." } return nil, forbiddenError }
func TestAdmit(t *testing.T) { tests := []struct { config *requestlimitapi.ProjectRequestLimitConfig user string expectForbidden bool }{ { config: multiLevelConfig(), user: "******", }, { config: multiLevelConfig(), user: "******", expectForbidden: true, }, { config: multiLevelConfig(), user: "******", }, { config: multiLevelConfig(), user: "******", expectForbidden: true, }, { config: emptyConfig(), user: "******", }, { config: singleDefaultConfig(), user: "******", expectForbidden: true, }, { config: singleDefaultConfig(), user: "******", }, { config: nil, user: "******", }, } for _, tc := range tests { pCache := fakeProjectCache(map[string]projectCount{ "user1": {0, 1}, "user2": {2, 2}, "user3": {5, 3}, "user4": {1, 0}, }) client := &testclient.Fake{} client.AddReactor("get", "users", userFn(map[string]labels.Set{ "user2": {"bronze": "yes"}, "user3": {"platinum": "yes"}, "user4": {"unknown": "yes"}, })) reqLimit, err := NewProjectRequestLimit(tc.config) if err != nil { t.Fatalf("Unexpected error: %v", err) } reqLimit.(oadmission.WantsOpenshiftClient).SetOpenshiftClient(client) reqLimit.(oadmission.WantsProjectCache).SetProjectCache(pCache) if err = reqLimit.(oadmission.Validator).Validate(); err != nil { t.Fatalf("validation error: %v", err) } err = reqLimit.Admit(admission.NewAttributesRecord( &projectapi.ProjectRequest{}, nil, projectapi.Kind("ProjectRequest").WithVersion("version"), "foo", "name", projectapi.Resource("projectrequests").WithVersion("version"), "", "CREATE", &user.DefaultInfo{Name: tc.user})) if err != nil && !tc.expectForbidden { t.Errorf("Got unexpected error for user %s: %v", tc.user, err) continue } if !apierrors.IsForbidden(err) && tc.expectForbidden { t.Errorf("Expecting forbidden error for user %s and config %#v. Got: %v", tc.user, tc.config, err) } } }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { if err := rest.BeforeCreate(projectrequestregistry.Strategy, ctx, obj); err != nil { return nil, err } projectRequest := obj.(*projectapi.ProjectRequest) if _, err := r.openshiftClient.Projects().Get(projectRequest.Name); err == nil { return nil, kapierror.NewAlreadyExists(projectapi.Resource("project"), projectRequest.Name) } projectName := projectRequest.Name projectAdmin := "" projectRequester := "" if userInfo, exists := kapi.UserFrom(ctx); exists { projectAdmin = userInfo.GetName() projectRequester = userInfo.GetName() } template, err := r.getTemplate() if err != nil { return nil, err } for i := range template.Parameters { switch template.Parameters[i].Name { case ProjectAdminUserParam: template.Parameters[i].Value = projectAdmin case ProjectDescriptionParam: template.Parameters[i].Value = projectRequest.Description case ProjectDisplayNameParam: template.Parameters[i].Value = projectRequest.DisplayName case ProjectNameParam: template.Parameters[i].Value = projectName case ProjectRequesterParam: template.Parameters[i].Value = projectRequester } } list, err := r.openshiftClient.TemplateConfigs(kapi.NamespaceDefault).Create(template) if err != nil { return nil, err } if err := utilerrors.NewAggregate(runtime.DecodeList(list.Objects, kapi.Codecs.UniversalDecoder())); err != nil { return nil, kapierror.NewInternalError(err) } // one of the items in this list should be the project. We are going to locate it, remove it from the list, create it separately var projectFromTemplate *projectapi.Project var lastRoleBinding *authorizationapi.RoleBinding objectsToCreate := &kapi.List{} for i := range list.Objects { if templateProject, ok := list.Objects[i].(*projectapi.Project); ok { projectFromTemplate = templateProject // don't add this to the list to create. We'll create the project separately. continue } if roleBinding, ok := list.Objects[i].(*authorizationapi.RoleBinding); ok { // keep track of the rolebinding, but still add it to the list lastRoleBinding = roleBinding } objectsToCreate.Items = append(objectsToCreate.Items, list.Objects[i]) } if projectFromTemplate == nil { return nil, kapierror.NewInternalError(fmt.Errorf("the project template (%s/%s) is not correctly configured: must contain a project resource", r.templateNamespace, r.templateName)) } // we split out project creation separately so that in a case of racers for the same project, only one will win and create the rest of their template objects createdProject, err := r.openshiftClient.Projects().Create(projectFromTemplate) if err != nil { // log errors other than AlreadyExists and Forbidden if !kapierror.IsAlreadyExists(err) && !kapierror.IsForbidden(err) { utilruntime.HandleError(fmt.Errorf("error creating requested project %#v: %v", projectFromTemplate, err)) } return nil, err } // Stop on the first error, since we have to delete the whole project if any item in the template fails stopOnErr := configcmd.AfterFunc(func(_ *resource.Info, err error) bool { return err != nil }) bulk := configcmd.Bulk{ Mapper: &resource.Mapper{ RESTMapper: client.DefaultMultiRESTMapper(), ObjectTyper: kapi.Scheme, ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { return r.openshiftClient, nil } return r.kubeClient, nil }), }, After: stopOnErr, Op: configcmd.Create, } if err := utilerrors.NewAggregate(bulk.Run(objectsToCreate, projectName)); err != nil { utilruntime.HandleError(fmt.Errorf("error creating items in requested project %q: %v", createdProject.Name, err)) // We have to clean up the project if any part of the project request template fails if deleteErr := r.openshiftClient.Projects().Delete(createdProject.Name); deleteErr != nil { utilruntime.HandleError(fmt.Errorf("error cleaning up requested project %q: %v", createdProject.Name, deleteErr)) } return nil, kapierror.NewInternalError(err) } // wait for a rolebinding if we created one if lastRoleBinding != nil { r.waitForRoleBinding(projectName, lastRoleBinding.Name) } return r.openshiftClient.Projects().Get(projectName) }
func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { if err := rest.BeforeCreate(projectrequestregistry.Strategy, ctx, obj); err != nil { return nil, err } projectRequest := obj.(*projectapi.ProjectRequest) if _, err := r.openshiftClient.Projects().Get(projectRequest.Name); err == nil { return nil, kapierror.NewAlreadyExists(projectapi.Resource("project"), projectRequest.Name) } projectName := projectRequest.Name projectAdmin := "" projectRequester := "" if userInfo, exists := kapi.UserFrom(ctx); exists { projectAdmin = userInfo.GetName() projectRequester = userInfo.GetName() } template, err := r.getTemplate() if err != nil { return nil, err } for i := range template.Parameters { switch template.Parameters[i].Name { case ProjectAdminUserParam: template.Parameters[i].Value = projectAdmin case ProjectDescriptionParam: template.Parameters[i].Value = projectRequest.Description case ProjectDisplayNameParam: template.Parameters[i].Value = projectRequest.DisplayName case ProjectNameParam: template.Parameters[i].Value = projectName case ProjectRequesterParam: template.Parameters[i].Value = projectRequester } } list, err := r.openshiftClient.TemplateConfigs(kapi.NamespaceDefault).Create(template) if err != nil { return nil, err } if err := utilerrors.NewAggregate(runtime.DecodeList(list.Objects, kapi.Codecs.UniversalDecoder())); err != nil { return nil, kapierror.NewInternalError(err) } // one of the items in this list should be the project. We are going to locate it, remove it from the list, create it separately var projectFromTemplate *projectapi.Project objectsToCreate := &kapi.List{} for i := range list.Objects { if templateProject, ok := list.Objects[i].(*projectapi.Project); ok { projectFromTemplate = templateProject if len(list.Objects) > (i + 1) { objectsToCreate.Items = append(objectsToCreate.Items, list.Objects[i+1:]...) } break } objectsToCreate.Items = append(objectsToCreate.Items, list.Objects[i]) } if projectFromTemplate == nil { return nil, kapierror.NewInternalError(fmt.Errorf("the project template (%s/%s) is not correctly configured: must contain a project resource", r.templateNamespace, r.templateName)) } // we split out project creation separately so that in a case of racers for the same project, only one will win and create the rest of their template objects if _, err := r.openshiftClient.Projects().Create(projectFromTemplate); err != nil { return nil, err } var restMapper meta.MultiRESTMapper seenGroups := sets.String{} for _, gv := range registered.EnabledVersions() { if seenGroups.Has(gv.Group) { continue } seenGroups.Insert(gv.Group) groupMeta, err := registered.Group(gv.Group) if err != nil { continue } restMapper = meta.MultiRESTMapper(append(restMapper, groupMeta.RESTMapper)) } bulk := configcmd.Bulk{ Mapper: restMapper, Typer: kapi.Scheme, RESTClientFactory: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { return r.openshiftClient, nil } return r.kubeClient, nil }, } if err := utilerrors.NewAggregate(bulk.Create(objectsToCreate, projectName)); err != nil { return nil, kapierror.NewInternalError(err) } return r.openshiftClient.Projects().Get(projectName) }