func TestMatchingSecurityContextConstraints(t *testing.T) { sccs := []*kapi.SecurityContextConstraints{ { ObjectMeta: kapi.ObjectMeta{ Name: "match group", }, Groups: []string{"group"}, }, { ObjectMeta: kapi.ObjectMeta{ Name: "match user", }, Users: []string{"user"}, }, } cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range sccs { cache.Add(scc) } // single match cases testCases := map[string]struct { userInfo user.Info expectedSCC string }{ "find none": { userInfo: &user.DefaultInfo{ Name: "foo", Groups: []string{"bar"}, }, }, "find user": { userInfo: &user.DefaultInfo{ Name: "user", Groups: []string{"bar"}, }, expectedSCC: "match user", }, "find group": { userInfo: &user.DefaultInfo{ Name: "foo", Groups: []string{"group"}, }, expectedSCC: "match group", }, } for k, v := range testCases { sccMatcher := oscc.NewDefaultSCCMatcher(cache) sccs, err := sccMatcher.FindApplicableSCCs(v.userInfo) if err != nil { t.Errorf("%s received error %v", k, err) continue } if v.expectedSCC == "" { if len(sccs) > 0 { t.Errorf("%s expected to match 0 sccs but found %d: %#v", k, len(sccs), sccs) } } if v.expectedSCC != "" { if len(sccs) != 1 { t.Errorf("%s returned more than one scc, use case can not validate: %#v", k, sccs) continue } if v.expectedSCC != sccs[0].Name { t.Errorf("%s expected to match %s but found %s", k, v.expectedSCC, sccs[0].Name) } } } // check that we can match many at once userInfo := &user.DefaultInfo{ Name: "user", Groups: []string{"group"}, } sccMatcher := oscc.NewDefaultSCCMatcher(cache) sccs, err := sccMatcher.FindApplicableSCCs(userInfo) if err != nil { t.Fatalf("matching many sccs returned error %v", err) } if len(sccs) != 2 { t.Errorf("matching many sccs expected to match 2 sccs but found %d: %#v", len(sccs), sccs) } }
func (c *MasterConfig) GetRestStorage() map[string]rest.Storage { kubeletClient, err := kubeletclient.NewStaticKubeletClient(c.KubeletClientConfig) if err != nil { glog.Fatalf("Unable to configure Kubelet client: %v", err) } // TODO: allow the system CAs and the local CAs to be joined together. importTransport, err := restclient.TransportFor(&restclient.Config{}) if err != nil { glog.Fatalf("Unable to configure a default transport for importing: %v", err) } insecureImportTransport, err := restclient.TransportFor(&restclient.Config{Insecure: true}) if err != nil { glog.Fatalf("Unable to configure a default transport for importing: %v", err) } buildStorage, buildDetailsStorage, err := buildetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) buildRegistry := buildregistry.NewRegistry(buildStorage) buildConfigStorage, err := buildconfigetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) buildConfigRegistry := buildconfigregistry.NewRegistry(buildConfigStorage) deployConfigStorage, deployConfigStatusStorage, deployConfigScaleStorage, err := deployconfigetcd.NewREST(c.RESTOptionsGetter) dcInstantiateOriginClient, dcInstantiateKubeClient := c.DeploymentConfigInstantiateClients() dcInstantiateStorage := deployconfiginstantiate.NewREST( *deployConfigStorage.Store, dcInstantiateOriginClient, dcInstantiateKubeClient, c.ExternalVersionCodec, c.AdmissionControl, ) checkStorageErr(err) deployConfigRegistry := deployconfigregistry.NewRegistry(deployConfigStorage) routeAllocator := c.RouteAllocator() routeStorage, routeStatusStorage, err := routeetcd.NewREST(c.RESTOptionsGetter, routeAllocator) checkStorageErr(err) hostSubnetStorage, err := hostsubnetetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) netNamespaceStorage, err := netnamespaceetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) clusterNetworkStorage, err := clusternetworketcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) egressNetworkPolicyStorage, err := egressnetworkpolicyetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) userStorage, err := useretcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) userRegistry := userregistry.NewRegistry(userStorage) identityStorage, err := identityetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) identityRegistry := identityregistry.NewRegistry(identityStorage) userIdentityMappingStorage := useridentitymapping.NewREST(userRegistry, identityRegistry) groupStorage, err := groupetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) policyStorage, err := policyetcd.NewStorage(c.RESTOptionsGetter) checkStorageErr(err) policyRegistry := policyregistry.NewRegistry(policyStorage) policyBindingStorage, err := policybindingetcd.NewStorage(c.RESTOptionsGetter) checkStorageErr(err) policyBindingRegistry := policybindingregistry.NewRegistry(policyBindingStorage) clusterPolicyStorage, err := clusterpolicystorage.NewStorage(c.RESTOptionsGetter) checkStorageErr(err) clusterPolicyRegistry := clusterpolicyregistry.NewRegistry(clusterPolicyStorage) clusterPolicyBindingStorage, err := clusterpolicybindingstorage.NewStorage(c.RESTOptionsGetter) checkStorageErr(err) clusterPolicyBindingRegistry := clusterpolicybindingregistry.NewRegistry(clusterPolicyBindingStorage) selfSubjectRulesReviewStorage := selfsubjectrulesreview.NewREST(c.RuleResolver, c.Informers.ClusterPolicies().Lister().ClusterPolicies()) subjectRulesReviewStorage := subjectrulesreview.NewREST(c.RuleResolver, c.Informers.ClusterPolicies().Lister().ClusterPolicies()) roleStorage := rolestorage.NewVirtualStorage(policyRegistry, c.RuleResolver) roleBindingStorage := rolebindingstorage.NewVirtualStorage(policyBindingRegistry, c.RuleResolver) clusterRoleStorage := clusterrolestorage.NewClusterRoleStorage(clusterPolicyRegistry, clusterPolicyBindingRegistry) clusterRoleBindingStorage := clusterrolebindingstorage.NewClusterRoleBindingStorage(clusterPolicyRegistry, clusterPolicyBindingRegistry) subjectAccessReviewStorage := subjectaccessreview.NewREST(c.Authorizer) subjectAccessReviewRegistry := subjectaccessreview.NewRegistry(subjectAccessReviewStorage) localSubjectAccessReviewStorage := localsubjectaccessreview.NewREST(subjectAccessReviewRegistry) resourceAccessReviewStorage := resourceaccessreview.NewREST(c.Authorizer) resourceAccessReviewRegistry := resourceaccessreview.NewRegistry(resourceAccessReviewStorage) localResourceAccessReviewStorage := localresourceaccessreview.NewREST(resourceAccessReviewRegistry) podSecurityPolicyReviewStorage := podsecuritypolicyreview.NewREST(oscc.NewDefaultSCCMatcher(c.Informers.SecurityContextConstraints().Lister()), clientadapter.FromUnversionedClient(c.PrivilegedLoopbackKubernetesClient)) podSecurityPolicySubjectStorage := podsecuritypolicysubjectreview.NewREST(oscc.NewDefaultSCCMatcher(c.Informers.SecurityContextConstraints().Lister()), clientadapter.FromUnversionedClient(c.PrivilegedLoopbackKubernetesClient)) podSecurityPolicySelfSubjectReviewStorage := podsecuritypolicyselfsubjectreview.NewREST(oscc.NewDefaultSCCMatcher(c.Informers.SecurityContextConstraints().Lister()), clientadapter.FromUnversionedClient(c.PrivilegedLoopbackKubernetesClient)) imageStorage, err := imageetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) imageRegistry := image.NewRegistry(imageStorage) imageSignatureStorage := imagesignature.NewREST(c.PrivilegedLoopbackOpenShiftClient.Images()) imageStreamSecretsStorage := imagesecret.NewREST(c.ImageStreamSecretClient()) imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage, err := imagestreametcd.NewREST(c.RESTOptionsGetter, c.RegistryNameFn, subjectAccessReviewRegistry, c.LimitVerifier) checkStorageErr(err) imageStreamRegistry := imagestream.NewRegistry(imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage) imageStreamMappingStorage := imagestreammapping.NewREST(imageRegistry, imageStreamRegistry, c.RegistryNameFn) imageStreamTagStorage := imagestreamtag.NewREST(imageRegistry, imageStreamRegistry) imageStreamTagRegistry := imagestreamtag.NewRegistry(imageStreamTagStorage) importerFn := func(r importer.RepositoryRetriever) imageimporter.Interface { return imageimporter.NewImageStreamImporter(r, c.Options.ImagePolicyConfig.MaxImagesBulkImportedPerRepository, flowcontrol.NewTokenBucketRateLimiter(2.0, 3)) } importerDockerClientFn := func() dockerregistry.Client { return dockerregistry.NewClient(20*time.Second, false) } imageStreamImportStorage := imagestreamimport.NewREST(importerFn, imageStreamRegistry, internalImageStreamStorage, imageStorage, c.ImageStreamImportSecretClient(), importTransport, insecureImportTransport, importerDockerClientFn) imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageStreamRegistry) imageStreamImageRegistry := imagestreamimage.NewRegistry(imageStreamImageStorage) buildGenerator := &buildgenerator.BuildGenerator{ Client: buildgenerator.Client{ GetBuildConfigFunc: buildConfigRegistry.GetBuildConfig, UpdateBuildConfigFunc: buildConfigRegistry.UpdateBuildConfig, GetBuildFunc: buildRegistry.GetBuild, CreateBuildFunc: buildRegistry.CreateBuild, GetImageStreamFunc: imageStreamRegistry.GetImageStream, GetImageStreamImageFunc: imageStreamImageRegistry.GetImageStreamImage, GetImageStreamTagFunc: imageStreamTagRegistry.GetImageStreamTag, }, ServiceAccounts: c.KubeClient(), Secrets: c.KubeClient(), } // TODO: with sharding, this needs to be changed deployConfigGenerator := &deployconfiggenerator.DeploymentConfigGenerator{ Client: deployconfiggenerator.Client{ DCFn: deployConfigRegistry.GetDeploymentConfig, ISFn: imageStreamRegistry.GetImageStream, LISFn2: imageStreamRegistry.ListImageStreams, }, } configClient, kclient := c.DeploymentConfigClients() deployRollbackClient := deployrollback.Client{ DCFn: deployConfigRegistry.GetDeploymentConfig, RCFn: clientDeploymentInterface{kclient}.GetDeployment, GRFn: deployrollback.NewRollbackGenerator().GenerateRollback, } deployConfigRollbackStorage := deployrollback.NewREST(configClient, kclient, c.ExternalVersionCodec) projectStorage := projectproxy.NewREST(c.PrivilegedLoopbackKubernetesClient.Namespaces(), c.ProjectAuthorizationCache, c.ProjectAuthorizationCache, c.ProjectCache) namespace, templateName, err := configapi.ParseNamespaceAndName(c.Options.ProjectConfig.ProjectRequestTemplate) if err != nil { glog.Errorf("Error parsing project request template value: %v", err) // we can continue on, the storage that gets created will be valid, it simply won't work properly. There's no reason to kill the master } projectRequestStorage := projectrequeststorage.NewREST(c.Options.ProjectConfig.ProjectRequestMessage, namespace, templateName, c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient, c.Informers.PolicyBindings().Lister()) bcClient := c.BuildConfigWebHookClient() buildConfigWebHooks := buildconfigregistry.NewWebHookREST( buildConfigRegistry, buildclient.NewOSClientBuildConfigInstantiatorClient(bcClient), map[string]webhook.Plugin{ "generic": generic.New(), "github": github.New(), }, ) clientStorage, err := clientetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) clientRegistry := clientregistry.NewRegistry(clientStorage) // If OAuth is disabled, set the strategy to Deny saAccountGrantMethod := oauthapi.GrantHandlerDeny if c.Options.OAuthConfig != nil { // Otherwise, take the value provided in master-config.yaml saAccountGrantMethod = oauthapi.GrantHandlerType(c.Options.OAuthConfig.GrantConfig.ServiceAccountMethod) } combinedOAuthClientGetter := saoauth.NewServiceAccountOAuthClientGetter(c.KubeClient(), c.KubeClient(), clientRegistry, saAccountGrantMethod) authorizeTokenStorage, err := authorizetokenetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter) checkStorageErr(err) accessTokenStorage, err := accesstokenetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter) checkStorageErr(err) clientAuthorizationStorage, err := clientauthetcd.NewREST(c.RESTOptionsGetter, combinedOAuthClientGetter) checkStorageErr(err) templateStorage, err := templateetcd.NewREST(c.RESTOptionsGetter) checkStorageErr(err) storage := map[string]rest.Storage{ "images": imageStorage, "imagesignatures": imageSignatureStorage, "imageStreams/secrets": imageStreamSecretsStorage, "imageStreams": imageStreamStorage, "imageStreams/status": imageStreamStatusStorage, "imageStreamImports": imageStreamImportStorage, "imageStreamImages": imageStreamImageStorage, "imageStreamMappings": imageStreamMappingStorage, "imageStreamTags": imageStreamTagStorage, "deploymentConfigs": deployConfigStorage, "deploymentConfigs/scale": deployConfigScaleStorage, "deploymentConfigs/status": deployConfigStatusStorage, "deploymentConfigs/rollback": deployConfigRollbackStorage, "deploymentConfigs/log": deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient), "deploymentConfigs/instantiate": dcInstantiateStorage, // TODO: Deprecate these "generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, c.ExternalVersionCodec), "deploymentConfigRollbacks": deployrollback.NewDeprecatedREST(deployRollbackClient, c.ExternalVersionCodec), "processedTemplates": templateregistry.NewREST(), "templates": templateStorage, "routes": routeStorage, "routes/status": routeStatusStorage, "projects": projectStorage, "projectRequests": projectRequestStorage, "hostSubnets": hostSubnetStorage, "netNamespaces": netNamespaceStorage, "clusterNetworks": clusterNetworkStorage, "egressNetworkPolicies": egressNetworkPolicyStorage, "users": userStorage, "groups": groupStorage, "identities": identityStorage, "userIdentityMappings": userIdentityMappingStorage, "oAuthAuthorizeTokens": authorizeTokenStorage, "oAuthAccessTokens": accessTokenStorage, "oAuthClients": clientStorage, "oAuthClientAuthorizations": clientAuthorizationStorage, "resourceAccessReviews": resourceAccessReviewStorage, "subjectAccessReviews": subjectAccessReviewStorage, "localSubjectAccessReviews": localSubjectAccessReviewStorage, "localResourceAccessReviews": localResourceAccessReviewStorage, "selfSubjectRulesReviews": selfSubjectRulesReviewStorage, "subjectRulesReviews": subjectRulesReviewStorage, "podSecurityPolicyReviews": podSecurityPolicyReviewStorage, "podSecurityPolicySubjectReviews": podSecurityPolicySubjectStorage, "podSecurityPolicySelfSubjectReviews": podSecurityPolicySelfSubjectReviewStorage, "policies": policyStorage, "policyBindings": policyBindingStorage, "roles": roleStorage, "roleBindings": roleBindingStorage, "clusterPolicies": clusterPolicyStorage, "clusterPolicyBindings": clusterPolicyBindingStorage, "clusterRoleBindings": clusterRoleBindingStorage, "clusterRoles": clusterRoleStorage, "clusterResourceQuotas": restInPeace(clusterresourcequotaregistry.NewStorage(c.RESTOptionsGetter)), "clusterResourceQuotas/status": updateInPeace(clusterresourcequotaregistry.NewStatusStorage(c.RESTOptionsGetter)), "appliedClusterResourceQuotas": appliedclusterresourcequotaregistry.NewREST( c.ClusterQuotaMappingController.GetClusterQuotaMapper(), c.Informers.ClusterResourceQuotas().Lister(), c.Informers.Namespaces().Lister()), } if configapi.IsBuildEnabled(&c.Options) { storage["builds"] = buildStorage storage["buildConfigs"] = buildConfigStorage storage["buildConfigs/webhooks"] = buildConfigWebHooks storage["builds/clone"] = buildclone.NewStorage(buildGenerator) storage["buildConfigs/instantiate"] = buildconfiginstantiate.NewStorage(buildGenerator) storage["buildConfigs/instantiatebinary"] = buildconfiginstantiate.NewBinaryStorage(buildGenerator, buildStorage, c.BuildLogClient(), kubeletClient) storage["builds/log"] = buildlogregistry.NewREST(buildStorage, buildStorage, c.BuildLogClient(), kubeletClient) storage["builds/details"] = buildDetailsStorage } return storage }
func TestAllowed(t *testing.T) { testcases := map[string]struct { sccs []*kapi.SecurityContextConstraints // patch function modify nominal PodSecurityPolicySubjectReview request patch func(p *securityapi.PodSecurityPolicySubjectReview) check func(p *securityapi.PodSecurityPolicySubjectReview) (bool, string) }{ "nominal case": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), }, check: func(p *securityapi.PodSecurityPolicySubjectReview) (bool, string) { // must be different due defaulting return p.Status.Template.Spec.SecurityContext != nil, "Status.Template should be defaulted" }, }, // if PodTemplateSpec.Spec.ServiceAccountName is empty it will not be defaulted "empty service account name": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), }, patch: func(p *securityapi.PodSecurityPolicySubjectReview) { p.Spec.Template.Spec.ServiceAccountName = "" // empty SA in podSpec }, check: func(p *securityapi.PodSecurityPolicySubjectReview) (bool, string) { return p.Status.Template.Spec.SecurityContext == nil, "Status.PodTemplateSpec should not be defaulted" }, }, // If you specify "User" but not "Group", then is it interpreted as "What if User were not a member of any groups. "user - no group": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), }, patch: func(p *securityapi.PodSecurityPolicySubjectReview) { p.Spec.Groups = nil }, }, // If User and Groups are empty, then the check is performed using *only* the ServiceAccountName in the PodTemplateSpec. "no user - no group": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), saSCC(), }, patch: func(p *securityapi.PodSecurityPolicySubjectReview) { p.Spec.Groups = nil p.Spec.User = "" }, }, } namespace := admissionttesting.CreateNamespaceForTest() for testName, testcase := range testcases { serviceAccount := admissionttesting.CreateSAForTest() reviewRequest := &securityapi.PodSecurityPolicySubjectReview{ Spec: securityapi.PodSecurityPolicySubjectReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, User: "******", Groups: []string{"bar", "baz"}, }, } if testcase.patch != nil { testcase.patch(reviewRequest) // local modification of the nominal case } cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } csf := clientsetfake.NewSimpleClientset(namespace, serviceAccount) storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceAll) obj, err := storage.Create(ctx, reviewRequest) if err != nil { t.Errorf("%s - Unexpected error: %v", testName, err) continue } pspsr, ok := obj.(*securityapi.PodSecurityPolicySubjectReview) if !ok { t.Errorf("%s - Unable to convert created runtime.Object to PodSecurityPolicySubjectReview", testName) continue } if testcase.check != nil { if ok, message := testcase.check(pspsr); !ok { t.Errorf("testcase '%s' is failing: %s", testName, message) } } if pspsr.Status.AllowedBy == nil { t.Errorf("testcase '%s' is failing AllowedBy shoult be not nil\n", testName) } } }
// Admit determines if the pod should be admitted based on the requested security context // and the available SCCs. // // 1. Find SCCs for the user. // 2. Find SCCs for the SA. If there is an error retrieving SA SCCs it is not fatal. // 3. Remove duplicates between the user/SA SCCs. // 4. Create the providers, includes setting pre-allocated values if necessary. // 5. Try to generate and validate an SCC with providers. If we find one then admit the pod // with the validated SCC. If we don't find any reject the pod and give all errors from the // failed attempts. // On updates, the BeforeUpdate of the pod strategy only zeroes out the status. That means that // any change that claims the pod is no longer privileged will be removed. That should hold until // we get a true old/new set of objects in. func (c *constraint) Admit(a kadmission.Attributes) error { if a.GetResource().GroupResource() != kapi.Resource("pods") { return nil } if len(a.GetSubresource()) != 0 { return nil } pod, ok := a.GetObject().(*kapi.Pod) // if we can't convert then we don't handle this object so just return if !ok { return nil } // get all constraints that are usable by the user glog.V(4).Infof("getting security context constraints for pod %s (generate: %s) in namespace %s with user info %v", pod.Name, pod.GenerateName, a.GetNamespace(), a.GetUserInfo()) sccMatcher := oscc.NewDefaultSCCMatcher(c.sccLister) matchedConstraints, err := sccMatcher.FindApplicableSCCs(a.GetUserInfo()) if err != nil { return kadmission.NewForbidden(a, err) } // get all constraints that are usable by the SA if len(pod.Spec.ServiceAccountName) > 0 { userInfo := serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") glog.V(4).Infof("getting security context constraints for pod %s (generate: %s) with service account info %v", pod.Name, pod.GenerateName, userInfo) saConstraints, err := sccMatcher.FindApplicableSCCs(userInfo) if err != nil { return kadmission.NewForbidden(a, err) } matchedConstraints = append(matchedConstraints, saConstraints...) } // remove duplicate constraints and sort matchedConstraints = deduplicateSecurityContextConstraints(matchedConstraints) sort.Sort(ByPriority(matchedConstraints)) providers, errs := c.createProvidersFromConstraints(a.GetNamespace(), matchedConstraints) logProviders(pod, providers, errs) if len(providers) == 0 { return kadmission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) } // all containers in a single pod must validate under a single provider or we will reject the request validationErrs := field.ErrorList{} for _, provider := range providers { if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetSCCName()))); len(errs) > 0 { validationErrs = append(validationErrs, errs...) continue } // the entire pod validated, annotate and accept the pod glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetSCCName()) if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } pod.ObjectMeta.Annotations[allocator.ValidatedSCCAnnotation] = provider.GetSCCName() return nil } // we didn't validate against any security context constraint provider, reject the pod and give the errors for each attempt glog.V(4).Infof("unable to validate pod %s (generate: %s) against any security context constraint: %v", pod.Name, pod.GenerateName, validationErrs) return kadmission.NewForbidden(a, fmt.Errorf("unable to validate against any security context constraint: %v", validationErrs)) }
func TestRequests(t *testing.T) { testcases := map[string]struct { request *securityapi.PodSecurityPolicySubjectReview sccs []*kapi.SecurityContextConstraints serviceAccount *kapi.ServiceAccount errorMessage string }{ "invalid request": { request: &securityapi.PodSecurityPolicySubjectReview{ Spec: securityapi.PodSecurityPolicySubjectReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "A.B.C.D", }, }, User: "******", Groups: []string{"bar", "baz"}, }, }, errorMessage: `PodSecurityPolicySubjectReview "" is invalid: spec.template.spec.serviceAccountName: Invalid value: "A.B.C.D": must match the regex [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* (e.g. 'example.com')`, }, "no provider": { request: &securityapi.PodSecurityPolicySubjectReview{ Spec: securityapi.PodSecurityPolicySubjectReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, }, }, // no errorMessage only pspr empty }, "container capability": { request: &securityapi.PodSecurityPolicySubjectReview{ Spec: securityapi.PodSecurityPolicySubjectReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: &kapi.SecurityContext{ Capabilities: &kapi.Capabilities{ Add: []kapi.Capability{"foo"}, }, }, }, }, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, User: "******", }, }, sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), }, // no errorMessage }, } namespace := admissionttesting.CreateNamespaceForTest() serviceAccount := admissionttesting.CreateSAForTest() for testName, testcase := range testcases { cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } csf := clientsetfake.NewSimpleClientset(namespace, serviceAccount) storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceAll) _, err := storage.Create(ctx, testcase.request) switch { case err == nil && len(testcase.errorMessage) == 0: continue case err == nil && len(testcase.errorMessage) > 0: t.Errorf("%s - Expected error %q. No error found", testName, testcase.errorMessage) continue case err.Error() != testcase.errorMessage: t.Errorf("%s - Expected error %q. But got %q", testName, testcase.errorMessage, err.Error()) } } }
func TestSpecificSAs(t *testing.T) { testcases := map[string]struct { request *securityapi.PodSecurityPolicyReview sccs []*kapi.SecurityContextConstraints errorMessage string serviceAccounts []*kapi.ServiceAccount }{ "SAs in PSPR": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", }, }, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, ServiceAccountNames: []string{"my-sa", "yours-sa"}, }, }, sccs: []*kapi.SecurityContextConstraints{ { ObjectMeta: kapi.ObjectMeta{ SelfLink: "/api/version/securitycontextconstraints/myscc", Name: "myscc", }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, Groups: []string{"system:serviceaccounts"}, }, }, serviceAccounts: []*kapi.ServiceAccount{ { ObjectMeta: kapi.ObjectMeta{ Name: "my-sa", Namespace: "default", }, }, { ObjectMeta: kapi.ObjectMeta{ Name: "yours-sa", Namespace: "default", }, }, { ObjectMeta: kapi.ObjectMeta{ Name: "our-sa", Namespace: "default", }, }, }, errorMessage: "", }, "bad SAs in PSPR": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", }, }, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, ServiceAccountNames: []string{"bad-sa"}, }, }, sccs: []*kapi.SecurityContextConstraints{ { ObjectMeta: kapi.ObjectMeta{ SelfLink: "/api/version/securitycontextconstraints/myscc", Name: "myscc", }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, Groups: []string{"system:serviceaccounts"}, }, }, serviceAccounts: []*kapi.ServiceAccount{ { ObjectMeta: kapi.ObjectMeta{ Name: "my-sa", Namespace: "default", }, }, }, errorMessage: `unable to retrieve ServiceAccount bad-sa: ServiceAccount "bad-sa" not found`, }, } for testName, testcase := range testcases { cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } objects := []runtime.Object{} namespace := admissionttesting.CreateNamespaceForTest() objects = append(objects, namespace) for i := range testcase.serviceAccounts { objects = append(objects, testcase.serviceAccounts[i]) } csf := clientsetfake.NewSimpleClientset(objects...) storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithNamespace(kapi.NewContext(), namespace.Name) _, err := storage.Create(ctx, testcase.request) switch { case err == nil && len(testcase.errorMessage) == 0: continue case err == nil && len(testcase.errorMessage) > 0: t.Errorf("%s - Expected error %q. No error found", testName, testcase.errorMessage) continue case err.Error() != testcase.errorMessage: t.Errorf("%s - Expected error %q. But got %#v", testName, testcase.errorMessage, err) } } }
func TestNoErrors(t *testing.T) { var uid int64 = 999 testcases := map[string]struct { request *securityapi.PodSecurityPolicyReview sccs []*kapi.SecurityContextConstraints allowedSAs []string }{ "default in pod": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, }, }, sccs: []*kapi.SecurityContextConstraints{ { ObjectMeta: kapi.ObjectMeta{ SelfLink: "/api/version/securitycontextconstraints/scc-sa", Name: "scc-sa", }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, Groups: []string{"system:serviceaccounts"}, }, }, allowedSAs: []string{"default"}, }, "failure creating provider": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: &kapi.SecurityContext{ Capabilities: &kapi.Capabilities{ Add: []kapi.Capability{"foo"}, }, }, }, }, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, }, }, sccs: []*kapi.SecurityContextConstraints{ { ObjectMeta: kapi.ObjectMeta{ SelfLink: "/api/version/securitycontextconstraints/restrictive", Name: "restrictive", }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, UID: &uid, }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, SELinuxOptions: &kapi.SELinuxOptions{ Level: "s9:z0,z1", }, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, Ranges: []kapi.IDRange{ {Min: 999, Max: 999}, }, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, Ranges: []kapi.IDRange{ {Min: 999, Max: 999}, }, }, Groups: []string{"system:serviceaccounts"}, }, }, allowedSAs: nil, }, } for testName, testcase := range testcases { cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } namespace := admissionttesting.CreateNamespaceForTest() serviceAccount := admissionttesting.CreateSAForTest() serviceAccount.Namespace = namespace.Name csf := clientsetfake.NewSimpleClientset(namespace, serviceAccount) storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithNamespace(kapi.NewContext(), namespace.Name) obj, err := storage.Create(ctx, testcase.request) if err != nil { t.Errorf("%s - Unexpected error: %v", testName, err) continue } pspsr, ok := obj.(*securityapi.PodSecurityPolicyReview) if !ok { t.Errorf("%s - unable to convert cretated runtime.Object to PodSecurityPolicyReview", testName) continue } var allowedSas []string for _, sa := range pspsr.Status.AllowedServiceAccounts { allowedSas = append(allowedSas, sa.Name) } if !reflect.DeepEqual(allowedSas, testcase.allowedSAs) { t.Errorf("%s - expected allowed ServiceAccout names %v got %v", testName, testcase.allowedSAs, allowedSas) } } }
func TestErrors(t *testing.T) { testcases := map[string]struct { request *securityapi.PodSecurityPolicyReview sccs []*kapi.SecurityContextConstraints serviceAccount *kapi.ServiceAccount errorMessage string }{ "invalid PSPR": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "A.B.C.D.E", }, }, }, }, serviceAccount: admissionttesting.CreateSAForTest(), errorMessage: `PodSecurityPolicyReview "" is invalid: spec.template.spec.serviceAccountName: Invalid value: "A.B.C.D.E": must match the regex [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* (e.g. 'example.com')`, }, "no SA": { request: &securityapi.PodSecurityPolicyReview{ Spec: securityapi.PodSecurityPolicyReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, }, }, errorMessage: `unable to retrieve ServiceAccount default: ServiceAccount "default" not found`, }, } for testName, testcase := range testcases { cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } namespace := admissionttesting.CreateNamespaceForTest() var csf clientset.Interface if testcase.serviceAccount != nil { testcase.serviceAccount.Namespace = namespace.Name csf = clientsetfake.NewSimpleClientset(namespace, testcase.serviceAccount) } else { csf = clientsetfake.NewSimpleClientset(namespace) } storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithNamespace(kapi.NewContext(), namespace.Name) _, err := storage.Create(ctx, testcase.request) if err == nil { t.Errorf("%s - Expected error", testName) continue } if err.Error() != testcase.errorMessage { t.Errorf("%s - Bad error. Expected %q got %q", testName, testcase.errorMessage, err.Error()) } } }
func TestPodSecurityPolicySelfSubjectReview(t *testing.T) { testcases := map[string]struct { sccs []*kapi.SecurityContextConstraints check func(p *securityapi.PodSecurityPolicySelfSubjectReview) (bool, string) }{ "user foo": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), admissionttesting.UserScc("foo"), }, check: func(p *securityapi.PodSecurityPolicySelfSubjectReview) (bool, string) { fmt.Printf("-> Is %q", p.Status.AllowedBy.Name) return p.Status.AllowedBy.Name == "foo", "SCC should be foo" }, }, "user bar ": { sccs: []*kapi.SecurityContextConstraints{ admissionttesting.UserScc("bar"), }, check: func(p *securityapi.PodSecurityPolicySelfSubjectReview) (bool, string) { return p.Status.AllowedBy == nil, "Allowed by should be nil" }, }, } for testName, testcase := range testcases { namespace := admissionttesting.CreateNamespaceForTest() serviceAccount := admissionttesting.CreateSAForTest() reviewRequest := &securityapi.PodSecurityPolicySelfSubjectReview{ Spec: securityapi.PodSecurityPolicySelfSubjectReviewSpec{ Template: kapi.PodTemplateSpec{ Spec: kapi.PodSpec{ Containers: []kapi.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, RestartPolicy: kapi.RestartPolicyAlways, SecurityContext: &kapi.PodSecurityContext{}, DNSPolicy: kapi.DNSClusterFirst, ServiceAccountName: "default", }, }, }, } cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range testcase.sccs { if err := cache.Add(scc); err != nil { t.Fatalf("error adding sccs to store: %v", err) } } csf := clientsetfake.NewSimpleClientset(namespace, serviceAccount) storage := REST{oscc.NewDefaultSCCMatcher(cache), csf} ctx := kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), kapi.NamespaceAll), &user.DefaultInfo{Name: "foo", Groups: []string{"bar", "baz"}}) obj, err := storage.Create(ctx, reviewRequest) if err != nil { t.Errorf("%s - Unexpected error", testName) } pspssr, ok := obj.(*securityapi.PodSecurityPolicySelfSubjectReview) if !ok { t.Errorf("%s - Unable to convert created runtime.Object to PodSecurityPolicySelfSubjectReview", testName) continue } if ok, message := testcase.check(pspssr); !ok { t.Errorf("%s - %s", testName, message) } } }