mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 02:09:56 +00:00
PodSecurityPolicy should respect and validate user-supplied RunAsNonRoot fields.
This commit is contained in:
parent
8df56da448
commit
158f17b9bb
@ -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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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}},
|
||||||
}},
|
}
|
||||||
},
|
|
||||||
|
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,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create a PSP with strategies that will populate a blank security context
|
// Create a PSP with strategies that will populate a blank security context
|
||||||
createPSP := func() *extensions.PodSecurityPolicy {
|
createPSP := func() *extensions.PodSecurityPolicy {
|
||||||
uid := types.UnixUserID(1)
|
uid := types.UnixUserID(1)
|
||||||
return &extensions.PodSecurityPolicy{
|
return &extensions.PodSecurityPolicy{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "psp-sa",
|
Name: "psp-sa",
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
seccomp.AllowedProfilesAnnotationKey: "*",
|
seccomp.AllowedProfilesAnnotationKey: "*",
|
||||||
seccomp.DefaultProfileAnnotationKey: "foo",
|
seccomp.DefaultProfileAnnotationKey: "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
Spec: extensions.PodSecurityPolicySpec{
|
||||||
Spec: extensions.PodSecurityPolicySpec{
|
DefaultAddCapabilities: []api.Capability{"foo"},
|
||||||
DefaultAddCapabilities: []api.Capability{"foo"},
|
RequiredDropCapabilities: []api.Capability{"bar"},
|
||||||
RequiredDropCapabilities: []api.Capability{"bar"},
|
RunAsUser: extensions.RunAsUserStrategyOptions{
|
||||||
RunAsUser: extensions.RunAsUserStrategyOptions{
|
Rule: extensions.RunAsUserStrategyMustRunAs,
|
||||||
Rule: extensions.RunAsUserStrategyMustRunAs,
|
Ranges: []extensions.UserIDRange{{Min: uid, Max: uid}},
|
||||||
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,
|
||||||
},
|
},
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|||||||
|
|
||||||
// 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
|
|||||||
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) {
|
|||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user