Change:
What keywords did you search in Kubernetes issues before filing this one? (If you have found any duplicates, you should instead reply there.): PodSecurityPolicy RunAsNonRoot Is this a BUG REPORT or FEATURE REQUEST? (choose one): BUG REPORT <!-- If this is a BUG REPORT, please: Fill in as much of the template below as you can. If you leave out information, we can't help you as well. If this is a FEATURE REQUEST, please: Describe in detail the feature/behavior/change you'd like to see. In both cases, be ready for followup questions, and please respond in a timely manner. If we can't reproduce a bug or think a feature already exists, we might close your issue. If we're wrong, PLEASE feel free to reopen it and explain why. --Kubernetes version (use ): 1.7.0-beta, 1.8.0 (HEAD) Environment: Enable PodSecurityPolicy Cloud provider or hardware configuration: gce, probably any OS (e.g. from /etc/os-release): gci Kernel (e.g. ): Install tools: Others: What happened: The PSP admission controller ignores the specified RunAsNonRoot, and overwrites it. What you expected to happen: the admission controller is not supposed to overwrite user specified fields in the podsecuritycontext and securitycontext. How to reproduce it (as minimally and precisely as possible): enable psp, and create a pod with [only] RunAsNonRoot=false specified in a securitycontext. Anything else we need to know:
/sig auth
there are actually 2 bugs here. fails to properly restrict this field when matching against a PSP, which is a latent bug as long as we overwrite the value anyway. overwrites the user-supplied setting for the securitycontext/podsecuritycontext. I suspect the best fix will be to use the API's deep copy function. We are also doing a couple other sketchy things: assigning the "copy" built-in as a variable, and creating an "sc" shadow variable.
I have a half fix in but we should probably fix it in a separate PR, because we may want to patch 1.6.
the effect is that PSP can overly restrict a pod that wanted to run as root, but expressed that as instead of since the workaround is easy (set in your pod if you want to run as root), I don't anticipate backporting the fix.
SGTM. I'll try and get the unit tests in after the GKE summit (on friday).

Discussion:
// if we're using the non-root strategy set the marker that this container should not be // run as root which will signal to the kubelet to do a final check either on the runAsUser // or, if runAsUser is not set, the image UID will be checked. <del> if s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot { </del> <ins> if sc.RunAsNonRoot == nil && s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot { </ins> nonRoot := true sc.RunAsNonRoot = &nonRoot }

} func TestCreateContainerSecurityContextNonmutating(t *testing.T) { <del> // Create a pod with a security context that needs filling in createPod := func() *api.Pod { return &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{{ SecurityContext: &api.SecurityContext{}, }}, }, } </del> <ins> untrue := false tests := []struct { security *api.SecurityContext }{ {nil}, {&api.SecurityContext{RunAsNonRoot: &untrue}}, </ins> } <del> // Create a PSP with strategies that will populate a blank security context createPSP := func() *extensions.PodSecurityPolicy { uid := types.UnixUserID(1) return &extensions.PodSecurityPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "psp-sa", Annotations: map[string]string{ seccomp.AllowedProfilesAnnotationKey: "*", seccomp.DefaultProfileAnnotationKey:  "foo", </del> <ins> for _, tc := range tests { // Create a pod with a security context that needs filling in createPod := func() *api.Pod { return &api.Pod{ Spec: api.PodSpec{ Containers: []api.Container{{ SecurityContext: tc.security, }}, </ins> }, <del> }, Spec: extensions.PodSecurityPolicySpec{ DefaultAddCapabilities:   []api.Capability{"foo"}, RequiredDropCapabilities: []api.Capability{"bar"}, RunAsUser: extensions.RunAsUserStrategyOptions{ Rule:   extensions.RunAsUserStrategyMustRunAs, Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}}, }, SELinux: extensions.SELinuxStrategyOptions{ Rule:           extensions.SELinuxStrategyMustRunAs, SELinuxOptions: &api.SELinuxOptions{User: "you"}, }, // these are pod mutating strategies that are tested above FSGroup: extensions.FSGroupStrategyOptions{ Rule: extensions.FSGroupStrategyRunAsAny, </del> <ins> } } // Create a PSP with strategies that will populate a blank security context createPSP := func() *extensions.PodSecurityPolicy { uid := types.UnixUserID(1) return &extensions.PodSecurityPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "psp-sa", Annotations: map[string]string{ seccomp.AllowedProfilesAnnotationKey: "*", seccomp.DefaultProfileAnnotationKey:  "foo", }, </ins> }, <del> SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ Rule: extensions.SupplementalGroupsStrategyRunAsAny, </del> <ins> Spec: extensions.PodSecurityPolicySpec{ DefaultAddCapabilities:   []api.Capability{"foo"}, RequiredDropCapabilities: []api.Capability{"bar"}, RunAsUser: extensions.RunAsUserStrategyOptions{ Rule:   extensions.RunAsUserStrategyMustRunAs, Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}}, }, SELinux: extensions.SELinuxStrategyOptions{ Rule:           extensions.SELinuxStrategyMustRunAs, SELinuxOptions: &api.SELinuxOptions{User: "you"}, }, // these are pod mutating strategies that are tested above FSGroup: extensions.FSGroupStrategyOptions{ Rule: extensions.FSGroupStrategyRunAsAny, }, SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ Rule: extensions.SupplementalGroupsStrategyRunAsAny, }, // mutates the container SC by defaulting to true if container sets nil ReadOnlyRootFilesystem: true, </ins> }, <del> // mutates the container SC by defaulting to true if container sets nil ReadOnlyRootFilesystem: true, }, </del> <ins> } </ins> } <del> } pod := createPod() psp := createPSP() </del> <ins> pod := createPod() psp := createPSP() </ins> <del> provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) if err != nil { t.Fatalf("unable to create provider %v", err) } sc, _, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0]) if err != nil { t.Fatalf("unable to create container security context %v", err) } </del> <ins> provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) if err != nil { t.Fatalf("unable to create provider %v", err) } sc, _, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0]) if err != nil { t.Fatalf("unable to create container security context %v", err) } </ins> <del> // The generated security context should have filled in missing options, so they should differ if reflect.DeepEqual(sc, &pod.Spec.Containers[0].SecurityContext) { t.Error("expected created security context to be different than container's, but they were identical") } </del> <ins> // The generated security context should have filled in missing options, so they should differ if reflect.DeepEqual(sc, &pod.Spec.Containers[0].SecurityContext) { t.Error("expected created security context to be different than container's, but they were identical") } </ins> <del> // Creating the provider or the security context should not have mutated the psp or pod if !reflect.DeepEqual(createPod(), pod) { diffs := diff.ObjectDiff(createPod(), pod) t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:n%s", diffs) } if !reflect.DeepEqual(createPSP(), psp) { t.Error("psp was mutated by CreateContainerSecurityContext") </del> <ins> // Creating the provider or the security context should not have mutated the psp or pod if !reflect.DeepEqual(createPod(), pod) { diffs := diff.ObjectDiff(createPod(), pod) t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:n%s", diffs) } if !reflect.DeepEqual(createPSP(), psp) { t.Error("psp was mutated by CreateContainerSecurityContext") } </ins> } }

// Validate ensures that the specified values fall within the range of the strategy.  Validation // of this will pass if either the UID is not set, assuming that the image will provided the UID <del> // or if the UID is set it is not root.  In order to work properly this assumes that the kubelet // performs a final check on runAsUser or the image UID when runAsUser is nil. </del> <ins> // or if the UID is set it is not root.  Validation will fail if RunAsNonRoot is set to false. // In order to work properly this assumes that the kubelet performs a final check on runAsUser // or the image UID when runAsUser is nil. </ins> func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorList { allErrs := field.ErrorList{} securityContextPath := field.NewPath("securityContext")

allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail)) return allErrs } <ins> if container.SecurityContext.RunAsNonRoot != nil && *container.SecurityContext.RunAsNonRoot == false { detail := fmt.Sprintf("RunAsNonRoot must be true for container %s", container.Name) allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsNonRoot"), *container.SecurityContext.RunAsNonRoot, detail)) return allErrs } </ins> if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 { <del> detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy %s", container.Name) </del> <ins> detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy for container %s", container.Name) </ins> allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail)) return allErrs }

func TestNonRootValidate(t *testing.T) { goodUID := types.UnixUserID(1) badUID := types.UnixUserID(0) <ins> untrue := false unfalse := true </ins> s, err := NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{}) if err != nil { t.Fatalf("unexpected error initializing NewMustRunAs %v", err) } <del> container := &api.Container{ SecurityContext: &api.SecurityContext{ RunAsUser: &badUID, </del> <ins> tests := []struct { container   *api.Container expectedErr bool msg         string }{ { container: &api.Container{ SecurityContext: &api.SecurityContext{ RunAsUser: &badUID, }, }, expectedErr: true, msg:         "in test case %d, expected errors from root uid but got none: %v", }, { container: &api.Container{ SecurityContext: &api.SecurityContext{ RunAsUser: &goodUID, }, }, expectedErr: false, msg:         "in test case %d, expected no errors from non-root uid but got %v", }, { container: &api.Container{ SecurityContext: &api.SecurityContext{ RunAsNonRoot: &untrue, }, }, expectedErr: true, msg:         "in test case %d, expected errors from RunAsNonRoot but got none: %v", }, { container: &api.Container{ SecurityContext: &api.SecurityContext{ RunAsNonRoot: &unfalse, RunAsUser:    &goodUID, }, }, expectedErr: false, msg:         "in test case %d, expected no errors from non-root uid but got %v", }, { container: &api.Container{ SecurityContext: &api.SecurityContext{ RunAsNonRoot: &unfalse, RunAsUser:    &badUID, }, }, expectedErr: true, msg:         "in test case %d, expected errors from root uid but got %v", </ins> }, } <del> errs := s.Validate(nil, container) if len(errs) == 0 { t.Errorf("expected errors from root uid but got none") } container.SecurityContext.RunAsUser = &goodUID errs = s.Validate(nil, container) if len(errs) != 0 { t.Errorf("expected no errors from non-root uid but got %v", errs) } container.SecurityContext.RunAsUser = nil errs = s.Validate(nil, container) if len(errs) != 0 { t.Errorf("expected no errors from nil uid but got %v", errs) </del> <ins> for i, tc := range tests { errs := s.Validate(nil, tc.container) if (len(errs) == 0) == tc.expectedErr { t.Errorf(tc.msg, i, errs) } </ins> } }