From 8c5b01376a4967dcda3650517bfa058fd26db8f5 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Thu, 5 Oct 2017 15:56:12 -0400 Subject: [PATCH] PodSecurityPolicy: Order by name, prefer non-mutating policies, require *api.Pod, allow GC updates --- .../security/podsecuritypolicy/BUILD | 6 +- .../security/podsecuritypolicy/admission.go | 129 ++-- .../podsecuritypolicy/admission_test.go | 662 +++++++++++++----- 3 files changed, 550 insertions(+), 247 deletions(-) diff --git a/plugin/pkg/admission/security/podsecuritypolicy/BUILD b/plugin/pkg/admission/security/podsecuritypolicy/BUILD index f6bbf4a2de..782b75eb79 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/BUILD +++ b/plugin/pkg/admission/security/podsecuritypolicy/BUILD @@ -16,12 +16,12 @@ go_library( "//pkg/client/informers/informers_generated/internalversion:go_default_library", "//pkg/client/listers/extensions/internalversion:go_default_library", "//pkg/kubeapiserver/admission:go_default_library", + "//pkg/registry/rbac:go_default_library", "//pkg/security/podsecuritypolicy:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", - "//pkg/securitycontext:go_default_library", "//pkg/serviceaccount:go_default_library", - "//pkg/util/maps:go_default_library", "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", @@ -48,6 +48,8 @@ go_test( "//pkg/security/podsecuritypolicy/seccomp:go_default_library", "//pkg/security/podsecuritypolicy/util:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission.go b/plugin/pkg/admission/security/podsecuritypolicy/admission.go index e0eb705a4e..19de55b93e 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission.go @@ -19,10 +19,12 @@ package podsecuritypolicy import ( "fmt" "io" + "sort" "strings" "github.com/golang/glog" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/admission" @@ -34,11 +36,10 @@ import ( informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion" extensionslisters "k8s.io/kubernetes/pkg/client/listers/extensions/internalversion" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" + rbacregistry "k8s.io/kubernetes/pkg/registry/rbac" psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy" psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util" - sc "k8s.io/kubernetes/pkg/securitycontext" "k8s.io/kubernetes/pkg/serviceaccount" - "k8s.io/kubernetes/pkg/util/maps" ) const ( @@ -120,8 +121,17 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { } pod, ok := a.GetObject().(*api.Pod) - // if we can't convert then we don't handle this object so just return + // if we can't convert then fail closed since we've already checked that this is supposed to be a pod object. + // this shouldn't normally happen during admission but could happen if an integrator passes a versioned + // pod object rather than an internal object. if !ok { + return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) + } + + // if this is an update, see if we are only updating the ownerRef/finalizers. Garbage collection does this + // and we should allow it in general, since you had the power to update and the power to delete. + // The worst that happens is that you delete something, but you aren't controlling the privileged object itself + if a.GetOperation() == admission.Update && rbacregistry.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), apiequality.Semantic) { return nil } @@ -143,32 +153,70 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { return nil } + // sort by name to make order deterministic + // TODO(liggitt): add priority field to allow admins to bucket differently + sort.SliceStable(matchedPolicies, func(i, j int) bool { + return strings.Compare(matchedPolicies[i].Name, matchedPolicies[j].Name) < 0 + }) + providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) - logProviders(pod, providers, errs) + logProviders(a, pod, providers, errs) if len(providers) == 0 { return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request")) } + // TODO(liggitt): allow spec mutation during initializing updates? + specMutationAllowed := a.GetOperation() == admission.Create + // all containers in a single pod must validate under a single provider or we will reject the request validationErrs := field.ErrorList{} + var ( + allowedPod *api.Pod + allowingProvider psp.Provider + ) + +loop: for _, provider := range providers { - if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 { + podCopy := pod.DeepCopy() + + if errs := assignSecurityContext(provider, podCopy, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); 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.GetPSPName()) + // the entire pod validated + + switch { + case apiequality.Semantic.DeepEqual(pod, podCopy): + // if it validated without mutating anything, use this result + allowedPod = podCopy + allowingProvider = provider + break loop + case specMutationAllowed && allowedPod == nil: + // if mutation is allowed and this is the first PSP to allow the pod, remember it, + // but continue to see if another PSP allows without mutating + allowedPod = podCopy + allowingProvider = provider + glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s with mutation", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName()) + case !specMutationAllowed: + glog.V(6).Infof("pod %s (generate: %s) in namespace %s validated against provider %s, but required mutation, skipping", pod.Name, pod.GenerateName, a.GetNamespace(), provider.GetPSPName()) + } + } + + if allowedPod != nil { + *pod = *allowedPod + // annotate and accept the pod + glog.V(4).Infof("pod %s (generate: %s) in namespace %s validated against provider %s", pod.Name, pod.GenerateName, a.GetNamespace(), allowingProvider.GetPSPName()) if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } - pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName() + pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = allowingProvider.GetPSPName() return nil } // we didn't validate against any provider, reject the pod and give the errors for each attempt - glog.V(4).Infof("unable to validate pod %s (generate: %s) against any pod security policy: %v", pod.Name, pod.GenerateName, validationErrs) + glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any pod security policy: %v", pod.Name, pod.GenerateName, a.GetNamespace(), validationErrs) return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) } @@ -176,82 +224,43 @@ func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { // and validates that the sc falls within the psp constraints. All containers must validate against // the same psp or is not considered valid. func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList { - generatedSCs := make([]*api.SecurityContext, len(pod.Spec.Containers)) - var generatedInitSCs []*api.SecurityContext - errs := field.ErrorList{} psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error())) } - - // save the original PSC and validate the generated PSC. Leave the generated PSC - // set for container generation/validation. We will reset to original post container - // validation. - originalPSC := pod.Spec.SecurityContext pod.Spec.SecurityContext = psc - originalAnnotations := maps.CopySS(pod.Annotations) pod.Annotations = pscAnnotations + errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...) - // Note: this is not changing the original container, we will set container SCs later so long - // as all containers validated under the same PSP. - for i, containerCopy := range pod.Spec.InitContainers { - // We will determine the effective security context for the container and validate against that - // since that is how the sc provider will eventually apply settings in the runtime. - // This results in an SC that is based on the Pod's PSC with the set fields from the container - // overriding pod level settings. - containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy) - - sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy) + for i := range pod.Spec.InitContainers { + sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.InitContainers[i]) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error())) continue } - generatedInitSCs = append(generatedInitSCs, sc) - - containerCopy.SecurityContext = sc + pod.Spec.InitContainers[i].SecurityContext = sc pod.Annotations = scAnnotations - errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...) + errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.InitContainers[i], field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...) } - // Note: this is not changing the original container, we will set container SCs later so long - // as all containers validated under the same PSP. - for i, containerCopy := range pod.Spec.Containers { - // We will determine the effective security context for the container and validate against that - // since that is how the sc provider will eventually apply settings in the runtime. - // This results in an SC that is based on the Pod's PSC with the set fields from the container - // overriding pod level settings. - containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy) - - sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy) + for i := range pod.Spec.Containers { + sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[i]) if err != nil { errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error())) continue } - generatedSCs[i] = sc - containerCopy.SecurityContext = sc + pod.Spec.Containers[i].SecurityContext = sc pod.Annotations = scAnnotations - errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "containers").Index(i).Child("securityContext"))...) + errs = append(errs, provider.ValidateContainerSecurityContext(pod, &pod.Spec.Containers[i], field.NewPath("spec", "containers").Index(i).Child("securityContext"))...) } if len(errs) > 0 { - // ensure psc is not mutated if there are errors - pod.Spec.SecurityContext = originalPSC - pod.Annotations = originalAnnotations return errs } - - // if we've reached this code then we've generated and validated an SC for every container in the - // pod so let's apply what we generated. Note: the psc is already applied. - for i, sc := range generatedInitSCs { - pod.Spec.InitContainers[i].SecurityContext = sc - } - for i, sc := range generatedSCs { - pod.Spec.Containers[i].SecurityContext = sc - } return nil } @@ -328,13 +337,13 @@ func buildAttributes(info user.Info, namespace string, policy *extensions.PodSec // logProviders logs what providers were found for the pod as well as any errors that were encountered // while creating providers. -func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { +func logProviders(a admission.Attributes, pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) { for _, err := range providerCreationErrs { glog.V(4).Infof("provider creation error: %v", err) } if len(providers) == 0 { - glog.V(4).Infof("unable to validate pod %s (generate: %s) against any provider.", pod.Name, pod.GenerateName) + glog.V(4).Infof("unable to validate pod %s (generate: %s) in namespace %s against any provider.", pod.Name, pod.GenerateName, a.GetNamespace()) return } @@ -342,5 +351,5 @@ func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs [ for i, p := range providers { names[i] = p.GetPSPName() } - glog.V(4).Infof("validating pod %s (generate: %s) against providers: %s", pod.Name, pod.GenerateName, strings.Join(names, ",")) + glog.V(4).Infof("validating pod %s (generate: %s) in namespace %s against providers: %s", pod.Name, pod.GenerateName, a.GetNamespace(), strings.Join(names, ",")) } diff --git a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go index 585f2f8f10..c43c7fb29f 100644 --- a/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go +++ b/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/assert" + "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/sets" @@ -48,7 +50,7 @@ const defaultContainerName = "test-c" // an authorizer that always returns true. func NewTestAdmission(lister extensionslisters.PodSecurityPolicyLister) kadmission.Interface { return &podSecurityPolicyPlugin{ - Handler: kadmission.NewHandler(kadmission.Create), + Handler: kadmission.NewHandler(kadmission.Create, kadmission.Update), strategyFactory: kpsp.NewSimpleStrategyFactory(), pspMatcher: getMatchingPolicies, authz: &TestAuthorizer{}, @@ -270,6 +272,173 @@ func TestAdmitPrivileged(t *testing.T) { } } +func defaultPod(t *testing.T, pod *kapi.Pod) *kapi.Pod { + v1Pod := &v1.Pod{} + if err := kapi.Scheme.Convert(pod, v1Pod, nil); err != nil { + t.Fatal(err) + } + kapi.Scheme.Default(v1Pod) + apiPod := &kapi.Pod{} + if err := kapi.Scheme.Convert(v1Pod, apiPod, nil); err != nil { + t.Fatal(err) + } + return apiPod +} + +func TestAdmitPreferNonmutating(t *testing.T) { + mutating1 := restrictivePSP() + mutating1.Name = "mutating1" + mutating1.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(1), Max: int64(1)}} + + mutating2 := restrictivePSP() + mutating2.Name = "mutating2" + mutating2.Spec.RunAsUser.Ranges = []extensions.UserIDRange{{Min: int64(2), Max: int64(2)}} + + privilegedPSP := permissivePSP() + privilegedPSP.Name = "privileged" + + unprivilegedRunAsAnyPod := defaultPod(t, &kapi.Pod{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: kapi.PodSpec{ + ServiceAccountName: "default", + Containers: []kapi.Container{{Name: "mycontainer", Image: "myimage"}}, + }, + }) + changedPod := unprivilegedRunAsAnyPod.DeepCopy() + changedPod.Spec.Containers[0].Image = "myimage2" + + gcChangedPod := unprivilegedRunAsAnyPod.DeepCopy() + gcChangedPod.OwnerReferences = []metav1.OwnerReference{{Kind: "Foo", Name: "bar"}} + gcChangedPod.Finalizers = []string{"foo"} + + tests := map[string]struct { + operation kadmission.Operation + pod *kapi.Pod + oldPod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectMutation bool + expectedPodUser *int64 + expectedContainerUser *int64 + expectedPSP string + }{ + "pod should not be mutated by allow-all strategies": { + operation: kadmission.Create, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{privilegedPSP}, + shouldPass: true, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: privilegedPSP.Name, + }, + "pod should prefer non-mutating PSP on create": { + operation: kadmission.Create, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, + shouldPass: true, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: privilegedPSP.Name, + }, + "pod should use deterministic mutating PSP on create": { + operation: kadmission.Create, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, + shouldPass: true, + expectMutation: true, + expectedPodUser: nil, + expectedContainerUser: &mutating1.Spec.RunAsUser.Ranges[0].Min, + expectedPSP: mutating1.Name, + }, + "pod should prefer non-mutating PSP on update": { + operation: kadmission.Update, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + oldPod: changedPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1, privilegedPSP}, + shouldPass: true, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: privilegedPSP.Name, + }, + "pod should not allow mutation on update": { + operation: kadmission.Update, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + oldPod: changedPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, + shouldPass: false, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: "", + }, + "pod should be allowed if completely unchanged on update": { + operation: kadmission.Update, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + oldPod: unprivilegedRunAsAnyPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, + shouldPass: true, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: "", + }, + "pod should be allowed if unchanged on update except finalizers,ownerrefs": { + operation: kadmission.Update, + pod: unprivilegedRunAsAnyPod.DeepCopy(), + oldPod: gcChangedPod.DeepCopy(), + psps: []*extensions.PodSecurityPolicy{mutating2, mutating1}, + shouldPass: true, + expectMutation: false, + expectedPodUser: nil, + expectedContainerUser: nil, + expectedPSP: "", + }, + } + + for k, v := range tests { + testPSPAdmitAdvanced(k, v.operation, v.psps, v.pod, v.oldPod, v.shouldPass, v.expectMutation, v.expectedPSP, t) + + if v.shouldPass { + actualPodUser := (*int64)(nil) + if v.pod.Spec.SecurityContext != nil { + actualPodUser = v.pod.Spec.SecurityContext.RunAsUser + } + if (actualPodUser == nil) != (v.expectedPodUser == nil) { + t.Errorf("%s expected pod user %v, got %v", k, v.expectedPodUser, actualPodUser) + } else if actualPodUser != nil && *actualPodUser != *v.expectedPodUser { + t.Errorf("%s expected pod user %v, got %v", k, *v.expectedPodUser, *actualPodUser) + } + + actualContainerUser := (*int64)(nil) + if v.pod.Spec.Containers[0].SecurityContext != nil { + actualContainerUser = v.pod.Spec.Containers[0].SecurityContext.RunAsUser + } + if (actualContainerUser == nil) != (v.expectedContainerUser == nil) { + t.Errorf("%s expected container user %v, got %v", k, v.expectedContainerUser, actualContainerUser) + } else if actualContainerUser != nil && *actualContainerUser != *v.expectedContainerUser { + t.Errorf("%s expected container user %v, got %v", k, *v.expectedContainerUser, *actualContainerUser) + } + } + } +} + +func TestFailClosedOnInvalidPod(t *testing.T) { + plugin := NewTestAdmission(nil) + pod := &v1.Pod{} + attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) + err := plugin.Admit(attrs) + + if err == nil { + t.Fatalf("expected versioned pod object to fail admission") + } + if !strings.Contains(err.Error(), "unexpected type") { + t.Errorf("expected type error but got: %v", err) + } +} + func TestAdmitCaps(t *testing.T) { createPodWithCaps := func(caps *kapi.Capabilities) *kapi.Pod { pod := goodPod() @@ -693,66 +862,125 @@ func TestAdmitHostIPC(t *testing.T) { } } -func TestAdmitSELinux(t *testing.T) { - createPodWithSELinux := func(opts *kapi.SELinuxOptions) *kapi.Pod { - pod := goodPod() - // doesn't matter if we set it here or on the container, the - // admission controller uses DetermineEffectiveSC to get the defaulting - // behavior so it can validate what will be applied at runtime - pod.Spec.SecurityContext.SELinuxOptions = opts - return pod - } +func createPodWithSecurityContexts(podSC *kapi.PodSecurityContext, containerSC *kapi.SecurityContext) *kapi.Pod { + pod := goodPod() + pod.Spec.SecurityContext = podSC + pod.Spec.Containers[0].SecurityContext = containerSC + return pod +} - runAsAny := restrictivePSP() +func TestAdmitSELinux(t *testing.T) { + runAsAny := permissivePSP() runAsAny.Name = "runAsAny" runAsAny.Spec.SELinux.Rule = extensions.SELinuxStrategyRunAsAny + runAsAny.Spec.SELinux.SELinuxOptions = nil - mustRunAs := restrictivePSP() + mustRunAs := permissivePSP() mustRunAs.Name = "mustRunAs" + mustRunAs.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs + mustRunAs.Spec.SELinux.SELinuxOptions = &kapi.SELinuxOptions{} mustRunAs.Spec.SELinux.SELinuxOptions.Level = "level" mustRunAs.Spec.SELinux.SELinuxOptions.Role = "role" mustRunAs.Spec.SELinux.SELinuxOptions.Type = "type" mustRunAs.Spec.SELinux.SELinuxOptions.User = "user" tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedSELinux *kapi.SELinuxOptions - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedPodSC *kapi.PodSecurityContext + expectedContainerSC *kapi.SecurityContext + expectedPSP string }{ - "runAsAny with no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedSELinux: nil, - expectedPSP: runAsAny.Name, + "runAsAny with no request": { + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: nil, + expectedPSP: runAsAny.Name, }, + "runAsAny with empty pod request": { + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{}, + expectedContainerSC: nil, + expectedPSP: runAsAny.Name, + }, + "runAsAny with empty container request": { + pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{}), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: &kapi.SecurityContext{}, + expectedPSP: runAsAny.Name, + }, + "runAsAny with pod request": { - pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedSELinux: &kapi.SELinuxOptions{User: "foo"}, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, + expectedContainerSC: nil, + expectedPSP: runAsAny.Name, }, + "runAsAny with container request": { + pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, + expectedPSP: runAsAny.Name, + }, + "runAsAny with pod and container request": { + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "bar"}}, + expectedContainerSC: &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, + expectedPSP: runAsAny.Name, + }, + "mustRunAs with bad pod request": { - pod: createPodWithSELinux(&kapi.SELinuxOptions{User: "foo"}), + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}, nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, shouldPass: false, }, - "mustRunAs with no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, - expectedPSP: mustRunAs.Name, + "mustRunAs with bad container request": { + pod: createPodWithSecurityContexts(nil, &kapi.SecurityContext{SELinuxOptions: &kapi.SELinuxOptions{User: "foo"}}), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: false, + }, + "mustRunAs with no request": { + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, + expectedContainerSC: nil, + expectedPSP: mustRunAs.Name, }, "mustRunAs with good pod request": { - pod: createPodWithSELinux(&kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedSELinux: mustRunAs.Spec.SELinux.SELinuxOptions, - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts( + &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}}, + nil, + ), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, + expectedContainerSC: nil, + expectedPSP: mustRunAs.Name, + }, + "mustRunAs with good container request": { + pod: createPodWithSecurityContexts( + &kapi.PodSecurityContext{SELinuxOptions: &kapi.SELinuxOptions{Level: "level", Role: "role", Type: "type", User: "user"}}, + nil, + ), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SELinuxOptions: mustRunAs.Spec.SELinux.SELinuxOptions}, + expectedContainerSC: nil, + expectedPSP: mustRunAs.Name, }, } @@ -760,20 +988,11 @@ func TestAdmitSELinux(t *testing.T) { testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) if v.shouldPass { - if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux == nil { - // ok, don't need to worry about identifying specific diffs - continue + if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { + t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } - if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions == nil && v.expectedSELinux != nil { - t.Errorf("%s expected selinux to be: %v but found nil", k, v.expectedSELinux) - continue - } - if v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions != nil && v.expectedSELinux == nil { - t.Errorf("%s expected selinux to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions) - continue - } - if !reflect.DeepEqual(*v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions) { - t.Errorf("%s expected selinux to be: %v but found %v", k, *v.expectedSELinux, *v.pod.Spec.Containers[0].SecurityContext.SELinuxOptions) + if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) { + t.Errorf("%s unexpected diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext)) } } } @@ -859,85 +1078,139 @@ func TestAdmitAppArmor(t *testing.T) { } func TestAdmitRunAsUser(t *testing.T) { - createPodWithRunAsUser := func(user int64) *kapi.Pod { - pod := goodPod() - // doesn't matter if we set it here or on the container, the - // admission controller uses DetermineEffectiveSC to get the defaulting - // behavior so it can validate what will be applied at runtime - userID := int64(user) - pod.Spec.SecurityContext.RunAsUser = &userID - return pod + podSC := func(user *int64) *kapi.PodSecurityContext { + return &kapi.PodSecurityContext{RunAsUser: user} + } + containerSC := func(user *int64) *kapi.SecurityContext { + return &kapi.SecurityContext{RunAsUser: user} } - runAsAny := restrictivePSP() + runAsAny := permissivePSP() runAsAny.Name = "runAsAny" runAsAny.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyRunAsAny - mustRunAs := restrictivePSP() + mustRunAs := permissivePSP() mustRunAs.Name = "mustRunAs" + mustRunAs.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs + mustRunAs.Spec.RunAsUser.Ranges = []extensions.UserIDRange{ + {Min: int64(999), Max: int64(1000)}, + } - runAsNonRoot := restrictivePSP() + runAsNonRoot := permissivePSP() runAsNonRoot.Name = "runAsNonRoot" runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot + trueValue := true + tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedRunAsUser *int64 - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedPodSC *kapi.PodSecurityContext + expectedContainerSC *kapi.SecurityContext + expectedPSP string }{ "runAsAny no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedRunAsUser: nil, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: nil, + expectedPSP: runAsAny.Name, }, "runAsAny pod request": { - pod: createPodWithRunAsUser(1), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedRunAsUser: userIDPtr(1), - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: podSC(userIDPtr(1)), + expectedContainerSC: nil, + expectedPSP: runAsAny.Name, }, + "runAsAny container request": { + pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(1))), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: containerSC(userIDPtr(1)), + expectedPSP: runAsAny.Name, + }, + "mustRunAs pod request out of range": { - pod: createPodWithRunAsUser(1), + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, shouldPass: false, }, + "mustRunAs container request out of range": { + pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1))), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: false, + }, + "mustRunAs pod request in range": { - pod: createPodWithRunAsUser(999), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: podSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), + expectedContainerSC: nil, + expectedPSP: mustRunAs.Name, }, - "mustRunAs no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedRunAsUser: &mustRunAs.Spec.RunAsUser.Ranges[0].Min, - expectedPSP: mustRunAs.Name, + "mustRunAs container request in range": { + pod: createPodWithSecurityContexts(nil, containerSC(userIDPtr(999))), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), + expectedPSP: mustRunAs.Name, }, - "runAsNonRoot no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: true, - expectedRunAsUser: nil, - expectedPSP: runAsNonRoot.Name, + "mustRunAs pod and container request in range": { + pod: createPodWithSecurityContexts(podSC(userIDPtr(999)), containerSC(userIDPtr(1000))), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: podSC(userIDPtr(999)), + expectedContainerSC: containerSC(userIDPtr(1000)), + expectedPSP: mustRunAs.Name, + }, + "mustRunAs no request": { + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: containerSC(&mustRunAs.Spec.RunAsUser.Ranges[0].Min), + expectedPSP: mustRunAs.Name, + }, + + "runAsNonRoot no request": { + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: true, + expectedPodSC: nil, + expectedContainerSC: &kapi.SecurityContext{RunAsNonRoot: &trueValue}, + expectedPSP: runAsNonRoot.Name, }, "runAsNonRoot pod request root": { - pod: createPodWithRunAsUser(0), + pod: createPodWithSecurityContexts(podSC(userIDPtr(0)), nil), psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, shouldPass: false, }, "runAsNonRoot pod request non-root": { - pod: createPodWithRunAsUser(1), - psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, - shouldPass: true, - expectedRunAsUser: userIDPtr(1), - expectedPSP: runAsNonRoot.Name, + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), nil), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: true, + expectedPodSC: podSC(userIDPtr(1)), + expectedPSP: runAsNonRoot.Name, + }, + "runAsNonRoot container request root": { + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(0))), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: false, + }, + "runAsNonRoot container request non-root": { + pod: createPodWithSecurityContexts(podSC(userIDPtr(1)), containerSC(userIDPtr(2))), + psps: []*extensions.PodSecurityPolicy{runAsNonRoot}, + shouldPass: true, + expectedPodSC: podSC(userIDPtr(1)), + expectedContainerSC: containerSC(userIDPtr(2)), + expectedPSP: runAsNonRoot.Name, }, } @@ -945,82 +1218,83 @@ func TestAdmitRunAsUser(t *testing.T) { testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) if v.shouldPass { - if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser == nil { - // ok, don't need to worry about identifying specific diffs - continue + if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { + t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } - if v.pod.Spec.Containers[0].SecurityContext.RunAsUser == nil && v.expectedRunAsUser != nil { - t.Errorf("%s expected RunAsUser to be: %v but found nil", k, v.expectedRunAsUser) - continue - } - if v.pod.Spec.Containers[0].SecurityContext.RunAsUser != nil && v.expectedRunAsUser == nil { - t.Errorf("%s expected RunAsUser to be nil but found: %v", k, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser) - continue - } - if *v.expectedRunAsUser != *v.pod.Spec.Containers[0].SecurityContext.RunAsUser { - t.Errorf("%s expected RunAsUser to be: %v but found %v", k, *v.expectedRunAsUser, *v.pod.Spec.Containers[0].SecurityContext.RunAsUser) + if !reflect.DeepEqual(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext) { + t.Errorf("%s unexpected container sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedContainerSC, v.pod.Spec.Containers[0].SecurityContext)) } } } } func TestAdmitSupplementalGroups(t *testing.T) { - createPodWithSupGroup := func(group int64) *kapi.Pod { - pod := goodPod() - // doesn't matter if we set it here or on the container, the - // admission controller uses DetermineEffectiveSC to get the defaulting - // behavior so it can validate what will be applied at runtime - groupID := int64(group) - pod.Spec.SecurityContext.SupplementalGroups = []int64{groupID} - return pod + podSC := func(group int64) *kapi.PodSecurityContext { + return &kapi.PodSecurityContext{SupplementalGroups: []int64{group}} } - runAsAny := restrictivePSP() + runAsAny := permissivePSP() runAsAny.Name = "runAsAny" runAsAny.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyRunAsAny - mustRunAs := restrictivePSP() + mustRunAs := permissivePSP() mustRunAs.Name = "mustRunAs" + mustRunAs.Spec.SupplementalGroups.Rule = extensions.SupplementalGroupsStrategyMustRunAs + mustRunAs.Spec.SupplementalGroups.Ranges = []extensions.GroupIDRange{{Min: int64(999), Max: int64(1000)}} tests := map[string]struct { - pod *kapi.Pod - psps []*extensions.PodSecurityPolicy - shouldPass bool - expectedSupGroups []int64 - expectedPSP string + pod *kapi.Pod + psps []*extensions.PodSecurityPolicy + shouldPass bool + expectedPodSC *kapi.PodSecurityContext + expectedPSP string }{ "runAsAny no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedSupGroups: nil, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: nil, + expectedPSP: runAsAny.Name, + }, + "runAsAny empty pod request": { + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{}, + expectedPSP: runAsAny.Name, + }, + "runAsAny empty pod request empty supplemental groups": { + pod: createPodWithSecurityContexts(&kapi.PodSecurityContext{SupplementalGroups: []int64{}}, nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{}}, + expectedPSP: runAsAny.Name, }, "runAsAny pod request": { - pod: createPodWithSupGroup(1), - psps: []*extensions.PodSecurityPolicy{runAsAny}, - shouldPass: true, - expectedSupGroups: []int64{1}, - expectedPSP: runAsAny.Name, + pod: createPodWithSecurityContexts(podSC(1), nil), + psps: []*extensions.PodSecurityPolicy{runAsAny}, + shouldPass: true, + expectedPodSC: &kapi.PodSecurityContext{SupplementalGroups: []int64{1}}, + expectedPSP: runAsAny.Name, }, "mustRunAs no pod request": { - pod: goodPod(), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedSupGroups: []int64{mustRunAs.Spec.SupplementalGroups.Ranges[0].Min}, - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts(nil, nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: podSC(mustRunAs.Spec.SupplementalGroups.Ranges[0].Min), + expectedPSP: mustRunAs.Name, }, "mustRunAs bad pod request": { - pod: createPodWithSupGroup(1), + pod: createPodWithSecurityContexts(podSC(1), nil), psps: []*extensions.PodSecurityPolicy{mustRunAs}, shouldPass: false, }, "mustRunAs good pod request": { - pod: createPodWithSupGroup(999), - psps: []*extensions.PodSecurityPolicy{mustRunAs}, - shouldPass: true, - expectedSupGroups: []int64{999}, - expectedPSP: mustRunAs.Name, + pod: createPodWithSecurityContexts(podSC(999), nil), + psps: []*extensions.PodSecurityPolicy{mustRunAs}, + shouldPass: true, + expectedPodSC: podSC(999), + expectedPSP: mustRunAs.Name, }, } @@ -1028,16 +1302,8 @@ func TestAdmitSupplementalGroups(t *testing.T) { testPSPAdmit(k, v.psps, v.pod, v.shouldPass, v.expectedPSP, t) if v.shouldPass { - if v.pod.Spec.SecurityContext.SupplementalGroups == nil && v.expectedSupGroups != nil { - t.Errorf("%s expected SupplementalGroups to be: %v but found nil", k, v.expectedSupGroups) - continue - } - if v.pod.Spec.SecurityContext.SupplementalGroups != nil && v.expectedSupGroups == nil { - t.Errorf("%s expected SupplementalGroups to be nil but found: %v", k, v.pod.Spec.SecurityContext.SupplementalGroups) - continue - } - if !reflect.DeepEqual(v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups) { - t.Errorf("%s expected SupplementalGroups to be: %v but found %v", k, v.expectedSupGroups, v.pod.Spec.SecurityContext.SupplementalGroups) + if !reflect.DeepEqual(v.expectedPodSC, v.pod.Spec.SecurityContext) { + t.Errorf("%s unexpected pod sc diff:\n%s", k, diff.ObjectGoPrintSideBySide(v.expectedPodSC, v.pod.Spec.SecurityContext)) } } } @@ -1372,6 +1638,10 @@ func TestAdmitSysctls(t *testing.T) { } func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod *kapi.Pod, shouldPass bool, expectedPSP string, t *testing.T) { + testPSPAdmitAdvanced(testCaseName, kadmission.Create, psps, pod, nil, shouldPass, true, expectedPSP, t) +} + +func testPSPAdmitAdvanced(testCaseName string, op kadmission.Operation, psps []*extensions.PodSecurityPolicy, pod, oldPod *kapi.Pod, shouldPass bool, canMutate bool, expectedPSP string, t *testing.T) { informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) store := informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Informer().GetStore() @@ -1379,9 +1649,11 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod store.Add(psp) } + originalPod := pod.DeepCopy() + plugin := NewTestAdmission(informerFactory.Extensions().InternalVersion().PodSecurityPolicies().Lister()) - attrs := kadmission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) + attrs := kadmission.NewAttributesRecord(pod, oldPod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", op, &user.DefaultInfo{}) err := plugin.Admit(attrs) if shouldPass && err != nil { @@ -1392,6 +1664,18 @@ func testPSPAdmit(testCaseName string, psps []*extensions.PodSecurityPolicy, pod if pod.Annotations[psputil.ValidatedPSPAnnotation] != expectedPSP { t.Errorf("%s: expected to validate under %q PSP but found %q", testCaseName, expectedPSP, pod.Annotations[psputil.ValidatedPSPAnnotation]) } + + if !canMutate { + podWithoutPSPAnnotation := pod.DeepCopy() + delete(podWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation) + + originalPodWithoutPSPAnnotation := originalPod.DeepCopy() + delete(originalPodWithoutPSPAnnotation.Annotations, psputil.ValidatedPSPAnnotation) + + if !apiequality.Semantic.DeepEqual(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec) { + t.Errorf("%s: expected no mutation, got %s", testCaseName, diff.ObjectGoPrintSideBySide(originalPodWithoutPSPAnnotation.Spec, podWithoutPSPAnnotation.Spec)) + } + } } if !shouldPass && err == nil { @@ -1415,10 +1699,6 @@ func TestAssignSecurityContext(t *testing.T) { } } - // these are set up such that the containers always have a nil uid. If the case should not - // validate then the uids should not have been updated by the strategy. If the case should - // validate then uids should be set. This is ensuring that we're hanging on to the old SC - // as we generate/validate and only updating the original container if the entire pod validates testCases := map[string]struct { pod *kapi.Pod shouldValidate bool @@ -1464,24 +1744,6 @@ func TestAssignSecurityContext(t *testing.T) { t.Errorf("%s expected validation errors but received none", k) continue } - - // if we shouldn't have validated ensure that uid is not set on the containers - if !v.shouldValidate { - for _, c := range v.pod.Spec.Containers { - if c.SecurityContext.RunAsUser != nil { - t.Errorf("%s had non-nil UID %d. UID should not be set on test cases that don't validate", k, *c.SecurityContext.RunAsUser) - } - } - } - - // if we validated then the pod sc should be updated now with the defaults from the psp - if v.shouldValidate { - for _, c := range v.pod.Spec.Containers { - if *c.SecurityContext.RunAsUser != 999 { - t.Errorf("%s expected uid to be defaulted to 999 but found %v", k, *c.SecurityContext.RunAsUser) - } - } - } } } @@ -1765,6 +2027,36 @@ func restrictivePSP() *extensions.PodSecurityPolicy { } } +func permissivePSP() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "privileged", + Annotations: map[string]string{}, + }, + Spec: extensions.PodSecurityPolicySpec{ + AllowPrivilegeEscalation: true, + HostIPC: true, + HostNetwork: true, + HostPID: true, + HostPorts: []extensions.HostPortRange{{Min: 0, Max: 65536}}, + Volumes: []extensions.FSType{extensions.All}, + AllowedCapabilities: []kapi.Capability{extensions.AllowAllCapabilities}, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyRunAsAny, + }, + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, + }, + FSGroup: extensions.FSGroupStrategyOptions{ + Rule: extensions.FSGroupStrategyRunAsAny, + }, + SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ + Rule: extensions.SupplementalGroupsStrategyRunAsAny, + }, + }, + } +} + func createNamespaceForTest() *kapi.Namespace { return &kapi.Namespace{ ObjectMeta: metav1.ObjectMeta{