func testSCCAdmit(testCaseName string, sccs []*kapi.SecurityContextConstraints, pod *kapi.Pod, shouldPass bool, t *testing.T) { namespace := createNamespaceForTest() serviceAccount := createSAForTest() tc := clientsetfake.NewSimpleClientset(namespace, serviceAccount) cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range sccs { cache.Add(scc) } plugin := NewTestAdmission(cache, tc) attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) err := plugin.Admit(attrs) if shouldPass && err != nil { t.Errorf("%s expected no errors but received %v", testCaseName, err) } if !shouldPass && err == nil { t.Errorf("%s expected errors but received none", testCaseName) } }
func TestAdmitWithPrioritizedSCC(t *testing.T) { // scc with high priority but very restrictive. restricted := restrictiveSCC() restrictedPriority := int32(100) restricted.Priority = &restrictedPriority // sccs with matching priorities but one will have a higher point score (by the run as user strategy) uidFive := int64(5) matchingPrioritySCCOne := laxSCC() matchingPrioritySCCOne.Name = "matchingPrioritySCCOne" matchingPrioritySCCOne.RunAsUser = kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, UID: &uidFive, } matchingPriority := int32(5) matchingPrioritySCCOne.Priority = &matchingPriority matchingPrioritySCCTwo := laxSCC() matchingPrioritySCCTwo.Name = "matchingPrioritySCCTwo" matchingPrioritySCCTwo.RunAsUser = kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, UIDRangeMin: &uidFive, UIDRangeMax: &uidFive, } matchingPrioritySCCTwo.Priority = &matchingPriority // sccs with matching priorities and scores so should be matched by sorted name uidSix := int64(6) matchingPriorityAndScoreSCCOne := laxSCC() matchingPriorityAndScoreSCCOne.Name = "matchingPriorityAndScoreSCCOne" matchingPriorityAndScoreSCCOne.RunAsUser = kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, UID: &uidSix, } matchingPriorityAndScorePriority := int32(1) matchingPriorityAndScoreSCCOne.Priority = &matchingPriorityAndScorePriority matchingPriorityAndScoreSCCTwo := laxSCC() matchingPriorityAndScoreSCCTwo.Name = "matchingPriorityAndScoreSCCTwo" matchingPriorityAndScoreSCCTwo.RunAsUser = kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, UID: &uidSix, } matchingPriorityAndScoreSCCTwo.Priority = &matchingPriorityAndScorePriority // we will expect these to sort as: expectedSort := []string{"restrictive", "matchingPrioritySCCOne", "matchingPrioritySCCTwo", "matchingPriorityAndScoreSCCOne", "matchingPriorityAndScoreSCCTwo"} sccsToSort := []*kapi.SecurityContextConstraints{matchingPriorityAndScoreSCCTwo, matchingPriorityAndScoreSCCOne, matchingPrioritySCCTwo, matchingPrioritySCCOne, restricted} sort.Sort(oscc.ByPriority(sccsToSort)) for i, scc := range sccsToSort { if scc.Name != expectedSort[i] { t.Fatalf("unexpected sort found %s at element %d but expected %s", scc.Name, i, expectedSort[i]) } } // sorting works as we're expecting // now, to test we will craft some requests that are targeted to validate against specific // SCCs and ensure that they come out with the right annotation. This means admission // is using the sort strategy we expect. namespace := createNamespaceForTest() serviceAccount := createSAForTest() tc := clientsetfake.NewSimpleClientset(namespace, serviceAccount) cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } for _, scc := range sccsToSort { err := cache.Add(scc) if err != nil { t.Fatalf("error adding sccs to store: %v", err) } } // create the admission plugin plugin := NewTestAdmission(cache, tc) // match the restricted SCC testSCCAdmission(goodPod(), plugin, restricted.Name, t) // match matchingPrioritySCCOne by setting RunAsUser to 5 matchingPrioritySCCOnePod := goodPod() matchingPrioritySCCOnePod.Spec.Containers[0].SecurityContext.RunAsUser = &uidFive testSCCAdmission(matchingPrioritySCCOnePod, plugin, matchingPrioritySCCOne.Name, t) // match matchingPriorityAndScoreSCCOne by setting RunAsUser to 6 matchingPriorityAndScoreSCCOnePod := goodPod() matchingPriorityAndScoreSCCOnePod.Spec.Containers[0].SecurityContext.RunAsUser = &uidSix testSCCAdmission(matchingPriorityAndScoreSCCOnePod, plugin, matchingPriorityAndScoreSCCOne.Name, t) }
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 TestAdmit(t *testing.T) { // create the annotated namespace and add it to the fake client namespace := createNamespaceForTest() serviceAccount := createSAForTest() // used for cases where things are preallocated defaultGroup := int64(2) tc := clientsetfake.NewSimpleClientset(namespace, serviceAccount) // create scc that requires allocation retrieval saSCC := &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ 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"}, } // create scc that has specific requirements that shouldn't match but is permissioned to // service accounts to test that even though this has matching priorities (0) and a // lower point value score (which will cause it to be sorted in front of scc-sa) it should not // validate the requests so we should try scc-sa. var exactUID int64 = 999 saExactSCC := &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "scc-sa-exact", }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, UID: &exactUID, }, 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"}, } cache := &oscache.IndexerToSecurityContextConstraintsLister{ Indexer: cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}), } cache.Add(saExactSCC) cache.Add(saSCC) // create the admission plugin p := NewTestAdmission(cache, tc) // setup test data uidNotInRange := goodPod() var uid int64 = 1001 uidNotInRange.Spec.Containers[0].SecurityContext.RunAsUser = &uid invalidMCSLabels := goodPod() invalidMCSLabels.Spec.Containers[0].SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{ Level: "s1:q0,q1", } disallowedPriv := goodPod() var priv bool = true disallowedPriv.Spec.Containers[0].SecurityContext.Privileged = &priv // specifies a UID in the range of the preallocated UID annotation specifyUIDInRange := goodPod() var goodUID int64 = 3 specifyUIDInRange.Spec.Containers[0].SecurityContext.RunAsUser = &goodUID // specifies an mcs label that matches the preallocated mcs annotation specifyLabels := goodPod() specifyLabels.Spec.Containers[0].SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{ Level: "s0:c1,c0", } // specifies an FSGroup in the range of preallocated sup group annotation specifyFSGroupInRange := goodPod() // group in the range of a preallocated fs group which, by default is a single digit range // based on the first value of the ns annotation. goodFSGroup := int64(2) specifyFSGroupInRange.Spec.SecurityContext.FSGroup = &goodFSGroup // specifies a sup group in the range of preallocated sup group annotation specifySupGroup := goodPod() // group is not the default but still in the range specifySupGroup.Spec.SecurityContext.SupplementalGroups = []int64{3} specifyPodLevelSELinux := goodPod() specifyPodLevelSELinux.Spec.SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{ Level: "s0:c1,c0", } requestsHostNetwork := goodPod() requestsHostNetwork.Spec.SecurityContext.HostNetwork = true requestsHostPID := goodPod() requestsHostPID.Spec.SecurityContext.HostPID = true requestsHostIPC := goodPod() requestsHostIPC.Spec.SecurityContext.HostIPC = true requestsHostPorts := goodPod() requestsHostPorts.Spec.Containers[0].Ports = []kapi.ContainerPort{{HostPort: 1}} requestsSupplementalGroup := goodPod() requestsSupplementalGroup.Spec.SecurityContext.SupplementalGroups = []int64{1} requestsFSGroup := goodPod() fsGroup := int64(1) requestsFSGroup.Spec.SecurityContext.FSGroup = &fsGroup requestsPodLevelMCS := goodPod() requestsPodLevelMCS.Spec.SecurityContext.SELinuxOptions = &kapi.SELinuxOptions{ User: "******", Type: "type", Role: "role", Level: "level", } testCases := map[string]struct { pod *kapi.Pod shouldAdmit bool expectedUID int64 expectedLevel string expectedFSGroup int64 expectedSupGroups []int64 expectedPriv bool }{ "uidNotInRange": { pod: uidNotInRange, shouldAdmit: false, }, "invalidMCSLabels": { pod: invalidMCSLabels, shouldAdmit: false, }, "disallowedPriv": { pod: disallowedPriv, shouldAdmit: false, }, "specifyUIDInRange": { pod: specifyUIDInRange, shouldAdmit: true, expectedUID: *specifyUIDInRange.Spec.Containers[0].SecurityContext.RunAsUser, expectedLevel: "s0:c1,c0", expectedFSGroup: defaultGroup, expectedSupGroups: []int64{defaultGroup}, }, "specifyLabels": { pod: specifyLabels, shouldAdmit: true, expectedUID: 1, expectedLevel: specifyLabels.Spec.Containers[0].SecurityContext.SELinuxOptions.Level, expectedFSGroup: defaultGroup, expectedSupGroups: []int64{defaultGroup}, }, "specifyFSGroup": { pod: specifyFSGroupInRange, shouldAdmit: true, expectedUID: 1, expectedLevel: "s0:c1,c0", expectedFSGroup: *specifyFSGroupInRange.Spec.SecurityContext.FSGroup, expectedSupGroups: []int64{defaultGroup}, }, "specifySupGroup": { pod: specifySupGroup, shouldAdmit: true, expectedUID: 1, expectedLevel: "s0:c1,c0", expectedFSGroup: defaultGroup, expectedSupGroups: []int64{specifySupGroup.Spec.SecurityContext.SupplementalGroups[0]}, }, "specifyPodLevelSELinuxLevel": { pod: specifyPodLevelSELinux, shouldAdmit: true, expectedUID: 1, expectedLevel: "s0:c1,c0", expectedFSGroup: defaultGroup, expectedSupGroups: []int64{defaultGroup}, }, "requestsHostNetwork": { pod: requestsHostNetwork, shouldAdmit: false, }, "requestsHostPorts": { pod: requestsHostPorts, shouldAdmit: false, }, "requestsHostPID": { pod: requestsHostPID, shouldAdmit: false, }, "requestsHostIPC": { pod: requestsHostIPC, shouldAdmit: false, }, "requestsSupplementalGroup": { pod: requestsSupplementalGroup, shouldAdmit: false, }, "requestsFSGroup": { pod: requestsFSGroup, shouldAdmit: false, }, "requestsPodLevelMCS": { pod: requestsPodLevelMCS, shouldAdmit: false, }, } for i := 0; i < 2; i++ { for k, v := range testCases { v.pod.Spec.Containers, v.pod.Spec.InitContainers = v.pod.Spec.InitContainers, v.pod.Spec.Containers containers := v.pod.Spec.Containers if i == 0 { containers = v.pod.Spec.InitContainers } attrs := kadmission.NewAttributesRecord(v.pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) err := p.Admit(attrs) if v.shouldAdmit && err != nil { t.Errorf("%s expected no errors but received %v", k, err) } if !v.shouldAdmit && err == nil { t.Errorf("%s expected errors but received none", k) } if v.shouldAdmit { validatedSCC, ok := v.pod.Annotations[allocator.ValidatedSCCAnnotation] if !ok { t.Errorf("%s expected to find the validated annotation on the pod for the scc but found none", k) } if validatedSCC != saSCC.Name { t.Errorf("%s should have validated against %s but found %s", k, saSCC.Name, validatedSCC) } // ensure anything we expected to be defaulted on the container level is set if *containers[0].SecurityContext.RunAsUser != v.expectedUID { t.Errorf("%s expected UID %d but found %d", k, v.expectedUID, *containers[0].SecurityContext.RunAsUser) } if containers[0].SecurityContext.SELinuxOptions.Level != v.expectedLevel { t.Errorf("%s expected Level %s but found %s", k, v.expectedLevel, containers[0].SecurityContext.SELinuxOptions.Level) } // ensure anything we expected to be defaulted on the pod level is set if v.pod.Spec.SecurityContext.SELinuxOptions.Level != v.expectedLevel { t.Errorf("%s expected pod level SELinux Level %s but found %s", k, v.expectedLevel, v.pod.Spec.SecurityContext.SELinuxOptions.Level) } if *v.pod.Spec.SecurityContext.FSGroup != v.expectedFSGroup { t.Errorf("%s expected fsgroup %d but found %d", k, v.expectedFSGroup, *v.pod.Spec.SecurityContext.FSGroup) } if len(v.pod.Spec.SecurityContext.SupplementalGroups) != len(v.expectedSupGroups) { t.Errorf("%s found unexpected supplemental groups. Expected: %v, actual %v", k, v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups) } for _, g := range v.expectedSupGroups { if !hasSupGroup(g, v.pod.Spec.SecurityContext.SupplementalGroups) { t.Errorf("%s expected sup group %d", k, g) } } } } } // now add an escalated scc to the group and re-run the cases that expected failure, they should // now pass by validating against the escalated scc. adminSCC := &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "scc-admin", }, AllowPrivilegedContainer: true, AllowHostNetwork: true, AllowHostPorts: true, AllowHostPID: true, AllowHostIPC: true, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyRunAsAny, }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyRunAsAny, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyRunAsAny, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyRunAsAny, }, Groups: []string{"system:serviceaccounts"}, } cache.Add(adminSCC) for i := 0; i < 2; i++ { for k, v := range testCases { v.pod.Spec.Containers, v.pod.Spec.InitContainers = v.pod.Spec.InitContainers, v.pod.Spec.Containers if !v.shouldAdmit { attrs := kadmission.NewAttributesRecord(v.pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) err := p.Admit(attrs) if err != nil { t.Errorf("Expected %s to pass with escalated scc but got error %v", k, err) } validatedSCC, ok := v.pod.Annotations[allocator.ValidatedSCCAnnotation] if !ok { t.Errorf("%s expected to find the validated annotation on the pod for the scc but found none", k) } if validatedSCC != adminSCC.Name { t.Errorf("%s should have validated against %s but found %s", k, adminSCC.Name, validatedSCC) } } } } }
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) } } }
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) } } }