func TestCreateProvidersFromConstraints(t *testing.T) { namespaceValid := &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "default", Annotations: map[string]string{ allocator.UIDRangeAnnotation: "1/3", allocator.MCSAnnotation: "s0:c1,c0", allocator.SupplementalGroupsAnnotation: "1/3", }, }, } namespaceNoUID := &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "default", Annotations: map[string]string{ allocator.MCSAnnotation: "s0:c1,c0", allocator.SupplementalGroupsAnnotation: "1/3", }, }, } namespaceNoMCS := &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "default", Annotations: map[string]string{ allocator.UIDRangeAnnotation: "1/3", allocator.SupplementalGroupsAnnotation: "1/3", }, }, } namespaceNoSupplementalGroupsFallbackToUID := &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "default", Annotations: map[string]string{ allocator.UIDRangeAnnotation: "1/3", allocator.MCSAnnotation: "s0:c1,c0", }, }, } namespaceBadSupGroups := &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "default", Annotations: map[string]string{ allocator.UIDRangeAnnotation: "1/3", allocator.MCSAnnotation: "s0:c1,c0", allocator.SupplementalGroupsAnnotation: "", }, }, } testCases := map[string]struct { // use a generating function so we can test for non-mutation scc func() *kapi.SecurityContextConstraints namespace *kapi.Namespace expectedErr string }{ "valid non-preallocated scc": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "valid non-preallocated scc", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyRunAsAny, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyRunAsAny, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyRunAsAny, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyRunAsAny, }, } }, namespace: namespaceValid, }, "valid pre-allocated scc": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "valid pre-allocated scc", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, SELinuxOptions: &kapi.SELinuxOptions{User: "******"}, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, } }, namespace: namespaceValid, }, "pre-allocated no uid annotation": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "pre-allocated no uid annotation", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyRunAsAny, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyRunAsAny, }, } }, namespace: namespaceNoUID, expectedErr: "unable to find pre-allocated uid annotation", }, "pre-allocated no mcs annotation": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "pre-allocated no mcs annotation", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyMustRunAs, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAsRange, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyRunAsAny, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyRunAsAny, }, } }, namespace: namespaceNoMCS, expectedErr: "unable to find pre-allocated mcs annotation", }, "pre-allocated group falls back to UID annotation": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "pre-allocated no sup group annotation", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyRunAsAny, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyRunAsAny, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, } }, namespace: namespaceNoSupplementalGroupsFallbackToUID, }, "pre-allocated group bad value fails": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "pre-allocated no sup group annotation", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyRunAsAny, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyRunAsAny, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyMustRunAs, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyMustRunAs, }, } }, namespace: namespaceBadSupGroups, expectedErr: "unable to find pre-allocated group annotation", }, "bad scc strategy options": { scc: func() *kapi.SecurityContextConstraints { return &kapi.SecurityContextConstraints{ ObjectMeta: kapi.ObjectMeta{ Name: "bad scc user options", }, SELinuxContext: kapi.SELinuxContextStrategyOptions{ Type: kapi.SELinuxStrategyRunAsAny, }, RunAsUser: kapi.RunAsUserStrategyOptions{ Type: kapi.RunAsUserStrategyMustRunAs, }, FSGroup: kapi.FSGroupStrategyOptions{ Type: kapi.FSGroupStrategyRunAsAny, }, SupplementalGroups: kapi.SupplementalGroupsStrategyOptions{ Type: kapi.SupplementalGroupsStrategyRunAsAny, }, } }, namespace: namespaceValid, expectedErr: "MustRunAs requires a UID", }, } for k, v := range testCases { // create the admission handler tc := clientsetfake.NewSimpleClientset(v.namespace) scc := v.scc() // create the providers, this method only needs the namespace attributes := kadmission.NewAttributesRecord(nil, nil, kapi.Kind("Pod").WithVersion("version"), v.namespace.Name, "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, nil) _, errs := oscc.CreateProvidersFromConstraints(attributes.GetNamespace(), []*kapi.SecurityContextConstraints{scc}, tc) if !reflect.DeepEqual(scc, v.scc()) { diff := diff.ObjectDiff(scc, v.scc()) t.Errorf("%s createProvidersFromConstraints mutated constraints. diff:\n%s", k, diff) } if len(v.expectedErr) > 0 && len(errs) != 1 { t.Errorf("%s expected a single error '%s' but received %v", k, v.expectedErr, errs) continue } if len(v.expectedErr) == 0 && len(errs) != 0 { t.Errorf("%s did not expect an error but received %v", k, errs) continue } // check that we got the error we expected if len(v.expectedErr) > 0 { if !strings.Contains(errs[0].Error(), v.expectedErr) { t.Errorf("%s expected error '%s' but received %v", k, v.expectedErr, errs[0]) } } } }
// 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 = oscc.DeduplicateSecurityContextConstraints(matchedConstraints) sort.Sort(oscc.ByPriority(matchedConstraints)) providers, errs := oscc.CreateProvidersFromConstraints(a.GetNamespace(), matchedConstraints, c.client) 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 := oscc.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)) }