From b6a750c1f6ed6b6b3f4672460ab8d096dc51eaa1 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sat, 14 Oct 2017 15:07:02 -0400 Subject: [PATCH] SecurityContext: Add accessors/mutators for effective container security context --- pkg/securitycontext/BUILD | 12 +- pkg/securitycontext/accessors.go | 407 +++++++++++++++ pkg/securitycontext/accessors_test.go | 726 ++++++++++++++++++++++++++ 3 files changed, 1143 insertions(+), 2 deletions(-) create mode 100644 pkg/securitycontext/accessors.go create mode 100644 pkg/securitycontext/accessors_test.go diff --git a/pkg/securitycontext/BUILD b/pkg/securitycontext/BUILD index 9d8d2e42c0..1061140280 100644 --- a/pkg/securitycontext/BUILD +++ b/pkg/securitycontext/BUILD @@ -9,6 +9,7 @@ load( go_library( name = "go_default_library", srcs = [ + "accessors.go", "doc.go", "fake.go", "util.go", @@ -22,10 +23,17 @@ go_library( go_test( name = "go_default_test", - srcs = ["util_test.go"], + srcs = [ + "accessors_test.go", + "util_test.go", + ], importpath = "k8s.io/kubernetes/pkg/securitycontext", library = ":go_default_library", - deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], + deps = [ + "//pkg/api:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + ], ) filegroup( diff --git a/pkg/securitycontext/accessors.go b/pkg/securitycontext/accessors.go new file mode 100644 index 0000000000..f7abb22a25 --- /dev/null +++ b/pkg/securitycontext/accessors.go @@ -0,0 +1,407 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package securitycontext + +import ( + "reflect" + + "k8s.io/kubernetes/pkg/api" +) + +// PodSecurityContextAccessor allows reading the values of a PodSecurityContext object +type PodSecurityContextAccessor interface { + HostNetwork() bool + HostPID() bool + HostIPC() bool + SELinuxOptions() *api.SELinuxOptions + RunAsUser() *int64 + RunAsNonRoot() *bool + SupplementalGroups() []int64 + FSGroup() *int64 +} + +// PodSecurityContextMutator allows reading and writing the values of a PodSecurityContext object +type PodSecurityContextMutator interface { + PodSecurityContextAccessor + + SetHostNetwork(bool) + SetHostPID(bool) + SetHostIPC(bool) + SetSELinuxOptions(*api.SELinuxOptions) + SetRunAsUser(*int64) + SetRunAsNonRoot(*bool) + SetSupplementalGroups([]int64) + SetFSGroup(*int64) + + // PodSecurityContext returns the current PodSecurityContext object + PodSecurityContext() *api.PodSecurityContext +} + +// NewPodSecurityContextAccessor returns an accessor for the given pod security context. +// May be initialized with a nil PodSecurityContext. +func NewPodSecurityContextAccessor(podSC *api.PodSecurityContext) PodSecurityContextAccessor { + return &podSecurityContextWrapper{podSC: podSC} +} + +// NewPodSecurityContextMutator returns a mutator for the given pod security context. +// May be initialized with a nil PodSecurityContext. +func NewPodSecurityContextMutator(podSC *api.PodSecurityContext) PodSecurityContextMutator { + return &podSecurityContextWrapper{podSC: podSC} +} + +type podSecurityContextWrapper struct { + podSC *api.PodSecurityContext +} + +func (w *podSecurityContextWrapper) PodSecurityContext() *api.PodSecurityContext { + return w.podSC +} + +func (w *podSecurityContextWrapper) ensurePodSC() { + if w.podSC == nil { + w.podSC = &api.PodSecurityContext{} + } +} + +func (w *podSecurityContextWrapper) HostNetwork() bool { + if w.podSC == nil { + return false + } + return w.podSC.HostNetwork +} +func (w *podSecurityContextWrapper) SetHostNetwork(v bool) { + if w.podSC == nil && v == false { + return + } + w.ensurePodSC() + w.podSC.HostNetwork = v +} +func (w *podSecurityContextWrapper) HostPID() bool { + if w.podSC == nil { + return false + } + return w.podSC.HostPID +} +func (w *podSecurityContextWrapper) SetHostPID(v bool) { + if w.podSC == nil && v == false { + return + } + w.ensurePodSC() + w.podSC.HostPID = v +} +func (w *podSecurityContextWrapper) HostIPC() bool { + if w.podSC == nil { + return false + } + return w.podSC.HostIPC +} +func (w *podSecurityContextWrapper) SetHostIPC(v bool) { + if w.podSC == nil && v == false { + return + } + w.ensurePodSC() + w.podSC.HostIPC = v +} +func (w *podSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions { + if w.podSC == nil { + return nil + } + return w.podSC.SELinuxOptions +} +func (w *podSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) { + if w.podSC == nil && v == nil { + return + } + w.ensurePodSC() + w.podSC.SELinuxOptions = v +} +func (w *podSecurityContextWrapper) RunAsUser() *int64 { + if w.podSC == nil { + return nil + } + return w.podSC.RunAsUser +} +func (w *podSecurityContextWrapper) SetRunAsUser(v *int64) { + if w.podSC == nil && v == nil { + return + } + w.ensurePodSC() + w.podSC.RunAsUser = v +} +func (w *podSecurityContextWrapper) RunAsNonRoot() *bool { + if w.podSC == nil { + return nil + } + return w.podSC.RunAsNonRoot +} +func (w *podSecurityContextWrapper) SetRunAsNonRoot(v *bool) { + if w.podSC == nil && v == nil { + return + } + w.ensurePodSC() + w.podSC.RunAsNonRoot = v +} +func (w *podSecurityContextWrapper) SupplementalGroups() []int64 { + if w.podSC == nil { + return nil + } + return w.podSC.SupplementalGroups +} +func (w *podSecurityContextWrapper) SetSupplementalGroups(v []int64) { + if w.podSC == nil && len(v) == 0 { + return + } + w.ensurePodSC() + if len(v) == 0 && len(w.podSC.SupplementalGroups) == 0 { + return + } + w.podSC.SupplementalGroups = v +} +func (w *podSecurityContextWrapper) FSGroup() *int64 { + if w.podSC == nil { + return nil + } + return w.podSC.FSGroup +} +func (w *podSecurityContextWrapper) SetFSGroup(v *int64) { + if w.podSC == nil && v == nil { + return + } + w.ensurePodSC() + w.podSC.FSGroup = v +} + +type ContainerSecurityContextAccessor interface { + Capabilities() *api.Capabilities + Privileged() *bool + SELinuxOptions() *api.SELinuxOptions + RunAsUser() *int64 + RunAsNonRoot() *bool + ReadOnlyRootFilesystem() *bool + AllowPrivilegeEscalation() *bool +} + +type ContainerSecurityContextMutator interface { + ContainerSecurityContextAccessor + + ContainerSecurityContext() *api.SecurityContext + + SetCapabilities(*api.Capabilities) + SetPrivileged(*bool) + SetSELinuxOptions(*api.SELinuxOptions) + SetRunAsUser(*int64) + SetRunAsNonRoot(*bool) + SetReadOnlyRootFilesystem(*bool) + SetAllowPrivilegeEscalation(*bool) +} + +func NewContainerSecurityContextAccessor(containerSC *api.SecurityContext) ContainerSecurityContextAccessor { + return &containerSecurityContextWrapper{containerSC: containerSC} +} + +func NewContainerSecurityContextMutator(containerSC *api.SecurityContext) ContainerSecurityContextMutator { + return &containerSecurityContextWrapper{containerSC: containerSC} +} + +type containerSecurityContextWrapper struct { + containerSC *api.SecurityContext +} + +func (w *containerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext { + return w.containerSC +} + +func (w *containerSecurityContextWrapper) ensureContainerSC() { + if w.containerSC == nil { + w.containerSC = &api.SecurityContext{} + } +} + +func (w *containerSecurityContextWrapper) Capabilities() *api.Capabilities { + if w.containerSC == nil { + return nil + } + return w.containerSC.Capabilities +} +func (w *containerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.Capabilities = v +} +func (w *containerSecurityContextWrapper) Privileged() *bool { + if w.containerSC == nil { + return nil + } + return w.containerSC.Privileged +} +func (w *containerSecurityContextWrapper) SetPrivileged(v *bool) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.Privileged = v +} +func (w *containerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions { + if w.containerSC == nil { + return nil + } + return w.containerSC.SELinuxOptions +} +func (w *containerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.SELinuxOptions = v +} +func (w *containerSecurityContextWrapper) RunAsUser() *int64 { + if w.containerSC == nil { + return nil + } + return w.containerSC.RunAsUser +} +func (w *containerSecurityContextWrapper) SetRunAsUser(v *int64) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.RunAsUser = v +} +func (w *containerSecurityContextWrapper) RunAsNonRoot() *bool { + if w.containerSC == nil { + return nil + } + return w.containerSC.RunAsNonRoot +} +func (w *containerSecurityContextWrapper) SetRunAsNonRoot(v *bool) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.RunAsNonRoot = v +} +func (w *containerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool { + if w.containerSC == nil { + return nil + } + return w.containerSC.ReadOnlyRootFilesystem +} +func (w *containerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.ReadOnlyRootFilesystem = v +} +func (w *containerSecurityContextWrapper) AllowPrivilegeEscalation() *bool { + if w.containerSC == nil { + return nil + } + return w.containerSC.AllowPrivilegeEscalation +} +func (w *containerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) { + if w.containerSC == nil && v == nil { + return + } + w.ensureContainerSC() + w.containerSC.AllowPrivilegeEscalation = v +} + +func NewEffectiveContainerSecurityContextAccessor(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextAccessor { + return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC} +} + +func NewEffectiveContainerSecurityContextMutator(podSC PodSecurityContextAccessor, containerSC ContainerSecurityContextMutator) ContainerSecurityContextMutator { + return &effectiveContainerSecurityContextWrapper{podSC: podSC, containerSC: containerSC} +} + +type effectiveContainerSecurityContextWrapper struct { + podSC PodSecurityContextAccessor + containerSC ContainerSecurityContextMutator +} + +func (w *effectiveContainerSecurityContextWrapper) ContainerSecurityContext() *api.SecurityContext { + return w.containerSC.ContainerSecurityContext() +} + +func (w *effectiveContainerSecurityContextWrapper) Capabilities() *api.Capabilities { + return w.containerSC.Capabilities() +} +func (w *effectiveContainerSecurityContextWrapper) SetCapabilities(v *api.Capabilities) { + if !reflect.DeepEqual(w.Capabilities(), v) { + w.containerSC.SetCapabilities(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) Privileged() *bool { + return w.containerSC.Privileged() +} +func (w *effectiveContainerSecurityContextWrapper) SetPrivileged(v *bool) { + if !reflect.DeepEqual(w.Privileged(), v) { + w.containerSC.SetPrivileged(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) SELinuxOptions() *api.SELinuxOptions { + if v := w.containerSC.SELinuxOptions(); v != nil { + return v + } + return w.podSC.SELinuxOptions() +} +func (w *effectiveContainerSecurityContextWrapper) SetSELinuxOptions(v *api.SELinuxOptions) { + if !reflect.DeepEqual(w.SELinuxOptions(), v) { + w.containerSC.SetSELinuxOptions(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) RunAsUser() *int64 { + if v := w.containerSC.RunAsUser(); v != nil { + return v + } + return w.podSC.RunAsUser() +} +func (w *effectiveContainerSecurityContextWrapper) SetRunAsUser(v *int64) { + if !reflect.DeepEqual(w.RunAsUser(), v) { + w.containerSC.SetRunAsUser(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) RunAsNonRoot() *bool { + if v := w.containerSC.RunAsNonRoot(); v != nil { + return v + } + return w.podSC.RunAsNonRoot() +} +func (w *effectiveContainerSecurityContextWrapper) SetRunAsNonRoot(v *bool) { + if !reflect.DeepEqual(w.RunAsNonRoot(), v) { + w.containerSC.SetRunAsNonRoot(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) ReadOnlyRootFilesystem() *bool { + return w.containerSC.ReadOnlyRootFilesystem() +} +func (w *effectiveContainerSecurityContextWrapper) SetReadOnlyRootFilesystem(v *bool) { + if !reflect.DeepEqual(w.ReadOnlyRootFilesystem(), v) { + w.containerSC.SetReadOnlyRootFilesystem(v) + } +} +func (w *effectiveContainerSecurityContextWrapper) AllowPrivilegeEscalation() *bool { + return w.containerSC.AllowPrivilegeEscalation() +} +func (w *effectiveContainerSecurityContextWrapper) SetAllowPrivilegeEscalation(v *bool) { + if !reflect.DeepEqual(w.AllowPrivilegeEscalation(), v) { + w.containerSC.SetAllowPrivilegeEscalation(v) + } +} diff --git a/pkg/securitycontext/accessors_test.go b/pkg/securitycontext/accessors_test.go new file mode 100644 index 0000000000..7f6e73cd0e --- /dev/null +++ b/pkg/securitycontext/accessors_test.go @@ -0,0 +1,726 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package securitycontext + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/kubernetes/pkg/api" +) + +func TestPodSecurityContextAccessor(t *testing.T) { + fsGroup := int64(2) + runAsUser := int64(1) + runAsNonRoot := true + + testcases := []*api.PodSecurityContext{ + nil, + {}, + {FSGroup: &fsGroup}, + {HostIPC: true}, + {HostNetwork: true}, + {HostPID: true}, + {RunAsNonRoot: &runAsNonRoot}, + {RunAsUser: &runAsUser}, + {SELinuxOptions: &api.SELinuxOptions{User: "bob"}}, + {SupplementalGroups: []int64{1, 2, 3}}, + } + + for i, tc := range testcases { + expected := tc + if expected == nil { + expected = &api.PodSecurityContext{} + } + + a := NewPodSecurityContextAccessor(tc) + + if v := a.FSGroup(); !reflect.DeepEqual(expected.FSGroup, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.FSGroup, v) + } + if v := a.HostIPC(); !reflect.DeepEqual(expected.HostIPC, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.HostIPC, v) + } + if v := a.HostNetwork(); !reflect.DeepEqual(expected.HostNetwork, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.HostNetwork, v) + } + if v := a.HostPID(); !reflect.DeepEqual(expected.HostPID, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.HostPID, v) + } + if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v) + } + if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v) + } + if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v) + } + if v := a.SupplementalGroups(); !reflect.DeepEqual(expected.SupplementalGroups, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.SupplementalGroups, v) + } + } +} + +func TestPodSecurityContextMutator(t *testing.T) { + testcases := map[string]struct { + newSC func() *api.PodSecurityContext + }{ + "nil": { + newSC: func() *api.PodSecurityContext { return nil }, + }, + "zero": { + newSC: func() *api.PodSecurityContext { return &api.PodSecurityContext{} }, + }, + "populated": { + newSC: func() *api.PodSecurityContext { + return &api.PodSecurityContext{ + HostNetwork: true, + HostIPC: true, + HostPID: true, + SELinuxOptions: &api.SELinuxOptions{}, + RunAsUser: nil, + RunAsNonRoot: nil, + SupplementalGroups: nil, + FSGroup: nil, + } + }, + }, + } + + nonNilSC := func(sc *api.PodSecurityContext) *api.PodSecurityContext { + if sc == nil { + return &api.PodSecurityContext{} + } + return sc + } + + for k, tc := range testcases { + { + sc := tc.newSC() + originalSC := tc.newSC() + m := NewPodSecurityContextMutator(sc) + + // no-op sets should not modify the object + m.SetFSGroup(m.FSGroup()) + m.SetHostNetwork(m.HostNetwork()) + m.SetHostIPC(m.HostIPC()) + m.SetHostPID(m.HostPID()) + m.SetRunAsNonRoot(m.RunAsNonRoot()) + m.SetRunAsUser(m.RunAsUser()) + m.SetSELinuxOptions(m.SELinuxOptions()) + m.SetSupplementalGroups(m.SupplementalGroups()) + if !reflect.DeepEqual(sc, originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC) + } + if !reflect.DeepEqual(m.PodSecurityContext(), originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.PodSecurityContext(), originalSC) + } + } + + // FSGroup + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + i := int64(1123) + modifiedSC.FSGroup = &i + m.SetFSGroup(&i) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // HostNetwork + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + modifiedSC.HostNetwork = !modifiedSC.HostNetwork + m.SetHostNetwork(!m.HostNetwork()) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // HostIPC + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + modifiedSC.HostIPC = !modifiedSC.HostIPC + m.SetHostIPC(!m.HostIPC()) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // HostPID + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + modifiedSC.HostPID = !modifiedSC.HostPID + m.SetHostPID(!m.HostPID()) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // RunAsNonRoot + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + b := true + modifiedSC.RunAsNonRoot = &b + m.SetRunAsNonRoot(&b) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // RunAsUser + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + i := int64(1123) + modifiedSC.RunAsUser = &i + m.SetRunAsUser(&i) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // SELinuxOptions + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"} + m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"}) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + + // SupplementalGroups + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewPodSecurityContextMutator(tc.newSC()) + modifiedSC.SupplementalGroups = []int64{1, 1, 2, 3} + m.SetSupplementalGroups([]int64{1, 1, 2, 3}) + if !reflect.DeepEqual(m.PodSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.PodSecurityContext())) + continue + } + } + } +} + +func TestContainerSecurityContextAccessor(t *testing.T) { + privileged := true + runAsUser := int64(1) + runAsNonRoot := true + readOnlyRootFilesystem := true + allowPrivilegeEscalation := true + + testcases := []*api.SecurityContext{ + nil, + {}, + {Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}}, + {Privileged: &privileged}, + {SELinuxOptions: &api.SELinuxOptions{User: "bob"}}, + {RunAsUser: &runAsUser}, + {RunAsNonRoot: &runAsNonRoot}, + {ReadOnlyRootFilesystem: &readOnlyRootFilesystem}, + {AllowPrivilegeEscalation: &allowPrivilegeEscalation}, + } + + for i, tc := range testcases { + expected := tc + if expected == nil { + expected = &api.SecurityContext{} + } + + a := NewContainerSecurityContextAccessor(tc) + + if v := a.Capabilities(); !reflect.DeepEqual(expected.Capabilities, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.Capabilities, v) + } + if v := a.Privileged(); !reflect.DeepEqual(expected.Privileged, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.Privileged, v) + } + if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v) + } + if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v) + } + if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v) + } + if v := a.ReadOnlyRootFilesystem(); !reflect.DeepEqual(expected.ReadOnlyRootFilesystem, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.ReadOnlyRootFilesystem, v) + } + if v := a.AllowPrivilegeEscalation(); !reflect.DeepEqual(expected.AllowPrivilegeEscalation, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.AllowPrivilegeEscalation, v) + } + } +} + +func TestContainerSecurityContextMutator(t *testing.T) { + testcases := map[string]struct { + newSC func() *api.SecurityContext + }{ + "nil": { + newSC: func() *api.SecurityContext { return nil }, + }, + "zero": { + newSC: func() *api.SecurityContext { return &api.SecurityContext{} }, + }, + "populated": { + newSC: func() *api.SecurityContext { + return &api.SecurityContext{ + Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}, + SELinuxOptions: &api.SELinuxOptions{}, + } + }, + }, + } + + nonNilSC := func(sc *api.SecurityContext) *api.SecurityContext { + if sc == nil { + return &api.SecurityContext{} + } + return sc + } + + for k, tc := range testcases { + { + sc := tc.newSC() + originalSC := tc.newSC() + m := NewContainerSecurityContextMutator(sc) + + // no-op sets should not modify the object + m.SetAllowPrivilegeEscalation(m.AllowPrivilegeEscalation()) + m.SetCapabilities(m.Capabilities()) + m.SetPrivileged(m.Privileged()) + m.SetReadOnlyRootFilesystem(m.ReadOnlyRootFilesystem()) + m.SetRunAsNonRoot(m.RunAsNonRoot()) + m.SetRunAsUser(m.RunAsUser()) + m.SetSELinuxOptions(m.SELinuxOptions()) + if !reflect.DeepEqual(sc, originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC) + } + if !reflect.DeepEqual(m.ContainerSecurityContext(), originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.ContainerSecurityContext(), originalSC) + } + } + + // AllowPrivilegeEscalation + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + b := true + modifiedSC.AllowPrivilegeEscalation = &b + m.SetAllowPrivilegeEscalation(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // Capabilities + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + modifiedSC.Capabilities = &api.Capabilities{Drop: []api.Capability{"test2"}} + m.SetCapabilities(&api.Capabilities{Drop: []api.Capability{"test2"}}) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // Privileged + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + b := true + modifiedSC.Privileged = &b + m.SetPrivileged(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // ReadOnlyRootFilesystem + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + b := true + modifiedSC.ReadOnlyRootFilesystem = &b + m.SetReadOnlyRootFilesystem(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // RunAsNonRoot + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + b := true + modifiedSC.RunAsNonRoot = &b + m.SetRunAsNonRoot(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // RunAsUser + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + i := int64(1123) + modifiedSC.RunAsUser = &i + m.SetRunAsUser(&i) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // SELinuxOptions + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewContainerSecurityContextMutator(tc.newSC()) + modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"} + m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"}) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + } +} + +func TestEffectiveContainerSecurityContextAccessor(t *testing.T) { + privileged := true + runAsUser := int64(1) + runAsUserPod := int64(12) + runAsNonRoot := true + runAsNonRootPod := false + readOnlyRootFilesystem := true + allowPrivilegeEscalation := true + + testcases := []struct { + PodSC *api.PodSecurityContext + SC *api.SecurityContext + Effective *api.SecurityContext + }{ + { + PodSC: nil, + SC: nil, + Effective: nil, + }, + { + PodSC: &api.PodSecurityContext{}, + SC: &api.SecurityContext{}, + Effective: &api.SecurityContext{}, + }, + { + PodSC: &api.PodSecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + RunAsUser: &runAsUser, + RunAsNonRoot: &runAsNonRoot, + }, + SC: nil, + Effective: &api.SecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + RunAsUser: &runAsUser, + RunAsNonRoot: &runAsNonRoot, + }, + }, + { + PodSC: &api.PodSecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + RunAsUser: &runAsUserPod, + RunAsNonRoot: &runAsNonRootPod, + }, + SC: &api.SecurityContext{}, + Effective: &api.SecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + RunAsUser: &runAsUserPod, + RunAsNonRoot: &runAsNonRootPod, + }, + }, + { + PodSC: &api.PodSecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + RunAsUser: &runAsUserPod, + RunAsNonRoot: &runAsNonRootPod, + }, + SC: &api.SecurityContext{ + AllowPrivilegeEscalation: &allowPrivilegeEscalation, + Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}, + Privileged: &privileged, + ReadOnlyRootFilesystem: &readOnlyRootFilesystem, + RunAsUser: &runAsUser, + RunAsNonRoot: &runAsNonRoot, + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + }, + Effective: &api.SecurityContext{ + AllowPrivilegeEscalation: &allowPrivilegeEscalation, + Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}, + Privileged: &privileged, + ReadOnlyRootFilesystem: &readOnlyRootFilesystem, + RunAsUser: &runAsUser, + RunAsNonRoot: &runAsNonRoot, + SELinuxOptions: &api.SELinuxOptions{User: "bob"}, + }, + }, + } + + for i, tc := range testcases { + expected := tc.Effective + if expected == nil { + expected = &api.SecurityContext{} + } + + a := NewEffectiveContainerSecurityContextAccessor( + NewPodSecurityContextAccessor(tc.PodSC), + NewContainerSecurityContextMutator(tc.SC), + ) + + if v := a.Capabilities(); !reflect.DeepEqual(expected.Capabilities, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.Capabilities, v) + } + if v := a.Privileged(); !reflect.DeepEqual(expected.Privileged, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.Privileged, v) + } + if v := a.RunAsNonRoot(); !reflect.DeepEqual(expected.RunAsNonRoot, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsNonRoot, v) + } + if v := a.RunAsUser(); !reflect.DeepEqual(expected.RunAsUser, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.RunAsUser, v) + } + if v := a.SELinuxOptions(); !reflect.DeepEqual(expected.SELinuxOptions, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.SELinuxOptions, v) + } + if v := a.ReadOnlyRootFilesystem(); !reflect.DeepEqual(expected.ReadOnlyRootFilesystem, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.ReadOnlyRootFilesystem, v) + } + if v := a.AllowPrivilegeEscalation(); !reflect.DeepEqual(expected.AllowPrivilegeEscalation, v) { + t.Errorf("%d: expected %#v, got %#v", i, expected.AllowPrivilegeEscalation, v) + } + } +} + +func TestEffectiveContainerSecurityContextMutator(t *testing.T) { + runAsNonRootPod := false + runAsUserPod := int64(12) + + testcases := map[string]struct { + newPodSC func() *api.PodSecurityContext + newSC func() *api.SecurityContext + }{ + "nil": { + newPodSC: func() *api.PodSecurityContext { return nil }, + newSC: func() *api.SecurityContext { return nil }, + }, + "zero": { + newPodSC: func() *api.PodSecurityContext { return &api.PodSecurityContext{} }, + newSC: func() *api.SecurityContext { return &api.SecurityContext{} }, + }, + "populated pod sc": { + newPodSC: func() *api.PodSecurityContext { + return &api.PodSecurityContext{ + SELinuxOptions: &api.SELinuxOptions{User: "poduser"}, + RunAsNonRoot: &runAsNonRootPod, + RunAsUser: &runAsUserPod, + } + }, + newSC: func() *api.SecurityContext { + return &api.SecurityContext{} + }, + }, + "populated sc": { + newPodSC: func() *api.PodSecurityContext { return nil }, + newSC: func() *api.SecurityContext { + return &api.SecurityContext{ + Capabilities: &api.Capabilities{Drop: []api.Capability{"test"}}, + SELinuxOptions: &api.SELinuxOptions{}, + } + }, + }, + } + + nonNilSC := func(sc *api.SecurityContext) *api.SecurityContext { + if sc == nil { + return &api.SecurityContext{} + } + return sc + } + + for k, tc := range testcases { + { + podSC := tc.newPodSC() + sc := tc.newSC() + originalPodSC := tc.newPodSC() + originalSC := tc.newSC() + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(podSC), + NewContainerSecurityContextMutator(sc), + ) + + // no-op sets should not modify the object + m.SetAllowPrivilegeEscalation(m.AllowPrivilegeEscalation()) + m.SetCapabilities(m.Capabilities()) + m.SetPrivileged(m.Privileged()) + m.SetReadOnlyRootFilesystem(m.ReadOnlyRootFilesystem()) + m.SetRunAsNonRoot(m.RunAsNonRoot()) + m.SetRunAsUser(m.RunAsUser()) + m.SetSELinuxOptions(m.SELinuxOptions()) + if !reflect.DeepEqual(podSC, originalPodSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, podSC, originalPodSC) + } + if !reflect.DeepEqual(sc, originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, sc, originalSC) + } + if !reflect.DeepEqual(m.ContainerSecurityContext(), originalSC) { + t.Errorf("%s: unexpected mutation: %#v, %#v", k, m.ContainerSecurityContext(), originalSC) + } + } + + // AllowPrivilegeEscalation + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + b := true + modifiedSC.AllowPrivilegeEscalation = &b + m.SetAllowPrivilegeEscalation(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // Capabilities + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + modifiedSC.Capabilities = &api.Capabilities{Drop: []api.Capability{"test2"}} + m.SetCapabilities(&api.Capabilities{Drop: []api.Capability{"test2"}}) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // Privileged + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + b := true + modifiedSC.Privileged = &b + m.SetPrivileged(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // ReadOnlyRootFilesystem + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + b := true + modifiedSC.ReadOnlyRootFilesystem = &b + m.SetReadOnlyRootFilesystem(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // RunAsNonRoot + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + b := true + modifiedSC.RunAsNonRoot = &b + m.SetRunAsNonRoot(&b) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // RunAsUser + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + i := int64(1123) + modifiedSC.RunAsUser = &i + m.SetRunAsUser(&i) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + + // SELinuxOptions + { + modifiedSC := nonNilSC(tc.newSC()) + m := NewEffectiveContainerSecurityContextMutator( + NewPodSecurityContextAccessor(tc.newPodSC()), + NewContainerSecurityContextMutator(tc.newSC()), + ) + modifiedSC.SELinuxOptions = &api.SELinuxOptions{User: "bob"} + m.SetSELinuxOptions(&api.SELinuxOptions{User: "bob"}) + if !reflect.DeepEqual(m.ContainerSecurityContext(), modifiedSC) { + t.Errorf("%s: unexpected object:\n%s", k, diff.ObjectGoPrintSideBySide(modifiedSC, m.ContainerSecurityContext())) + continue + } + } + } +}