Commit 158f17b9 authored by Quintin Lee's avatar Quintin Lee

PodSecurityPolicy should respect and validate user-supplied RunAsNonRoot fields.

parent 8df56da4
...@@ -165,7 +165,7 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container ...@@ -165,7 +165,7 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container
// if we're using the non-root strategy set the marker that this container should not be // 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 // 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. // or, if runAsUser is not set, the image UID will be checked.
if s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot { if sc.RunAsNonRoot == nil && s.psp.Spec.RunAsUser.Rule == extensions.RunAsUserStrategyMustRunAsNonRoot {
nonRoot := true nonRoot := true
sc.RunAsNonRoot = &nonRoot sc.RunAsNonRoot = &nonRoot
} }
......
...@@ -112,76 +112,86 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) { ...@@ -112,76 +112,86 @@ func TestCreatePodSecurityContextNonmutating(t *testing.T) {
} }
func TestCreateContainerSecurityContextNonmutating(t *testing.T) { func TestCreateContainerSecurityContextNonmutating(t *testing.T) {
// Create a pod with a security context that needs filling in untrue := false
createPod := func() *api.Pod { tests := []struct {
return &api.Pod{ security *api.SecurityContext
Spec: api.PodSpec{ }{
Containers: []api.Container{{ {nil},
SecurityContext: &api.SecurityContext{}, {&api.SecurityContext{RunAsNonRoot: &untrue}},
}},
},
}
} }
// Create a PSP with strategies that will populate a blank security context for _, tc := range tests {
createPSP := func() *extensions.PodSecurityPolicy { // Create a pod with a security context that needs filling in
uid := types.UnixUserID(1) createPod := func() *api.Pod {
return &extensions.PodSecurityPolicy{ return &api.Pod{
ObjectMeta: metav1.ObjectMeta{ Spec: api.PodSpec{
Name: "psp-sa", Containers: []api.Container{{
Annotations: map[string]string{ SecurityContext: tc.security,
seccomp.AllowedProfilesAnnotationKey: "*", }},
seccomp.DefaultProfileAnnotationKey: "foo",
}, },
}, }
Spec: extensions.PodSecurityPolicySpec{ }
DefaultAddCapabilities: []api.Capability{"foo"},
RequiredDropCapabilities: []api.Capability{"bar"}, // Create a PSP with strategies that will populate a blank security context
RunAsUser: extensions.RunAsUserStrategyOptions{ createPSP := func() *extensions.PodSecurityPolicy {
Rule: extensions.RunAsUserStrategyMustRunAs, uid := types.UnixUserID(1)
Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}}, return &extensions.PodSecurityPolicy{
}, ObjectMeta: metav1.ObjectMeta{
SELinux: extensions.SELinuxStrategyOptions{ Name: "psp-sa",
Rule: extensions.SELinuxStrategyMustRunAs, Annotations: map[string]string{
SELinuxOptions: &api.SELinuxOptions{User: "you"}, seccomp.AllowedProfilesAnnotationKey: "*",
}, seccomp.DefaultProfileAnnotationKey: "foo",
// these are pod mutating strategies that are tested above },
FSGroup: extensions.FSGroupStrategyOptions{
Rule: extensions.FSGroupStrategyRunAsAny,
}, },
SupplementalGroups: extensions.SupplementalGroupsStrategyOptions{ Spec: extensions.PodSecurityPolicySpec{
Rule: extensions.SupplementalGroupsStrategyRunAsAny, 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,
}, },
// mutates the container SC by defaulting to true if container sets nil }
ReadOnlyRootFilesystem: true,
},
} }
}
pod := createPod() pod := createPod()
psp := createPSP() psp := createPSP()
provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory()) provider, err := NewSimpleProvider(psp, "namespace", NewSimpleStrategyFactory())
if err != nil { if err != nil {
t.Fatalf("unable to create provider %v", err) t.Fatalf("unable to create provider %v", err)
} }
sc, _, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0]) sc, _, err := provider.CreateContainerSecurityContext(pod, &pod.Spec.Containers[0])
if err != nil { if err != nil {
t.Fatalf("unable to create container security context %v", err) t.Fatalf("unable to create container security context %v", err)
} }
// The generated security context should have filled in missing options, so they should differ // The generated security context should have filled in missing options, so they should differ
if reflect.DeepEqual(sc, &pod.Spec.Containers[0].SecurityContext) { 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") t.Error("expected created security context to be different than container's, but they were identical")
} }
// Creating the provider or the security context should not have mutated the psp or pod // Creating the provider or the security context should not have mutated the psp or pod
if !reflect.DeepEqual(createPod(), pod) { if !reflect.DeepEqual(createPod(), pod) {
diffs := diff.ObjectDiff(createPod(), pod) diffs := diff.ObjectDiff(createPod(), pod)
t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs) t.Errorf("pod was mutated by CreateContainerSecurityContext. diff:\n%s", diffs)
} }
if !reflect.DeepEqual(createPSP(), psp) { if !reflect.DeepEqual(createPSP(), psp) {
t.Error("psp was mutated by CreateContainerSecurityContext") t.Error("psp was mutated by CreateContainerSecurityContext")
}
} }
} }
......
...@@ -41,8 +41,9 @@ func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*types.UnixU ...@@ -41,8 +41,9 @@ func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*types.UnixU
// Validate ensures that the specified values fall within the range of the strategy. Validation // 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 // of this will pass if either the UID is not set, assuming that the image will provided the UID
// or if the UID is set it is not root. In order to work properly this assumes that the kubelet // or if the UID is set it is not root. Validation will fail if RunAsNonRoot is set to false.
// performs a final check on runAsUser or the image UID when runAsUser is nil. // In order to work properly this assumes that the kubelet performs a final check on runAsUser
// or the image UID when runAsUser is nil.
func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorList { func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
securityContextPath := field.NewPath("securityContext") securityContextPath := field.NewPath("securityContext")
...@@ -51,8 +52,13 @@ func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorLi ...@@ -51,8 +52,13 @@ func (s *nonRoot) Validate(pod *api.Pod, container *api.Container) field.ErrorLi
allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail)) allErrs = append(allErrs, field.Invalid(securityContextPath, container.SecurityContext, detail))
return allErrs return allErrs
} }
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
}
if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 { if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 {
detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy %s", container.Name) detail := fmt.Sprintf("running with the root UID is forbidden by the pod security policy for container %s", container.Name)
allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail)) allErrs = append(allErrs, field.Invalid(securityContextPath.Child("runAsUser"), *container.SecurityContext.RunAsUser, detail))
return allErrs return allErrs
} }
......
...@@ -52,30 +52,70 @@ func TestNonRootGenerate(t *testing.T) { ...@@ -52,30 +52,70 @@ func TestNonRootGenerate(t *testing.T) {
func TestNonRootValidate(t *testing.T) { func TestNonRootValidate(t *testing.T) {
goodUID := types.UnixUserID(1) goodUID := types.UnixUserID(1)
badUID := types.UnixUserID(0) badUID := types.UnixUserID(0)
untrue := false
unfalse := true
s, err := NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{}) s, err := NewRunAsNonRoot(&extensions.RunAsUserStrategyOptions{})
if err != nil { if err != nil {
t.Fatalf("unexpected error initializing NewMustRunAs %v", err) t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
} }
container := &api.Container{ tests := []struct {
SecurityContext: &api.SecurityContext{ container *api.Container
RunAsUser: &badUID, 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",
}, },
} }
errs := s.Validate(nil, container) for i, tc := range tests {
if len(errs) == 0 { errs := s.Validate(nil, tc.container)
t.Errorf("expected errors from root uid but got none") if (len(errs) == 0) == tc.expectedErr {
} t.Errorf(tc.msg, i, errs)
}
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)
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment