diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go b/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline.go similarity index 57% rename from staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go rename to staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline.go index baa0de44a2d..ac6f1ba121b 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline.go @@ -26,20 +26,32 @@ import ( "k8s.io/pod-security-admission/api" ) +/* +Adding NET_RAW or capabilities beyond the default set must be disallowed. + +**Restricted Fields:** +spec.containers[*].securityContext.capabilities.add +spec.initContainers[*].securityContext.capabilities.add + +**Allowed Values:** +undefined / empty +values from the default set "AUDIT_WRITE", "CHOWN", "DAC_OVERRIDE","FOWNER", "FSETID", "KILL", "MKNOD", "NET_BIND_SERVICE", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_CHROOT" +*/ + func init() { - addCheck(CheckAddCapabilities) + addCheck(CheckCapabilitiesBaseline) } -// CheckAddCapabilities returns a baseline level check +// CheckCapabilitiesBaseline returns a baseline level check // that limits the capabilities that can be added in 1.0+ -func CheckAddCapabilities() Check { +func CheckCapabilitiesBaseline() Check { return Check{ - ID: "addCapabilities", + ID: "capabilities_baseline", Level: api.LevelBaseline, Versions: []VersionedCheck{ { MinimumVersion: api.MajorMinorVersion(1, 0), - CheckPod: addCapabilities_1_0, + CheckPod: capabilitiesBaseline_1_0, }, }, } @@ -63,28 +75,33 @@ var ( ) ) -func addCapabilities_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { - forbiddenContainers := sets.NewString() - forbiddenCapabilities := sets.NewString() +func capabilitiesBaseline_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { + var badContainers []string + nonDefaultCapabilities := sets.NewString() visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) { if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { + valid := true for _, c := range container.SecurityContext.Capabilities.Add { if !capabilities_allowed_1_0.Has(string(c)) { - forbiddenContainers.Insert(container.Name) - forbiddenCapabilities.Insert(string(c)) + valid = false + nonDefaultCapabilities.Insert(string(c)) } } + if !valid { + badContainers = append(badContainers, container.Name) + } } }) - if len(forbiddenCapabilities) > 0 { + if len(badContainers) > 0 { return CheckResult{ Allowed: false, - ForbiddenReason: "forbidden capabilities", + ForbiddenReason: "non-default capabilities", ForbiddenDetail: fmt.Sprintf( - "containers %q added %q", - forbiddenContainers.List(), - forbiddenCapabilities.List(), + "%s %s must not include %s in securityContext.capabilities.add", + pluralize("container", "containers", len(badContainers)), + joinQuote(badContainers), + joinQuote(nonDefaultCapabilities.List()), ), } } diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline_test.go b/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline_test.go new file mode 100644 index 00000000000..3ba21bbf362 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_capabilities_baseline_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 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 policy + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" +) + +func TestCapabilitiesBaseline(t *testing.T) { + tests := []struct { + name string + pod *corev1.Pod + expectReason string + expectDetail string + }{ + { + name: "multiple containers", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "a", SecurityContext: &corev1.SecurityContext{Capabilities: &corev1.Capabilities{Add: []corev1.Capability{"FOO", "BAR"}}}}, + {Name: "b", SecurityContext: &corev1.SecurityContext{Capabilities: &corev1.Capabilities{Add: []corev1.Capability{"BAR", "BAZ"}}}}, + }}}, + expectReason: `non-default capabilities`, + expectDetail: `containers "a", "b" must not include "BAR", "BAZ", "FOO" in securityContext.capabilities.add`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := capabilitiesBaseline_1_0(&tc.pod.ObjectMeta, &tc.pod.Spec) + if result.Allowed { + t.Fatal("expected disallowed") + } + if e, a := tc.expectReason, result.ForbiddenReason; e != a { + t.Errorf("expected\n%s\ngot\n%s", e, a) + } + if e, a := tc.expectDetail, result.ForbiddenDetail; e != a { + t.Errorf("expected\n%s\ngot\n%s", e, a) + } + }) + } +} diff --git a/staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_capabilities_baseline.go similarity index 82% rename from staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go rename to staging/src/k8s.io/pod-security-admission/test/fixtures_capabilities_baseline.go index 6d752de6c10..0ba6aaf0e6d 100644 --- a/staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_capabilities_baseline.go @@ -47,7 +47,7 @@ func ensureCapabilities(p *corev1.Pod) *corev1.Pod { func init() { fixtureData_1_0 := fixtureGenerator{ - expectErrorSubstring: "forbidden capabilities", + expectErrorSubstring: "non-default capabilities", generatePass: func(p *corev1.Pod) []*corev1.Pod { // don't generate fixtures if minimal valid pod drops ALL if p.Spec.Containers[0].SecurityContext != nil && p.Spec.Containers[0].SecurityContext.Capabilities != nil { @@ -65,8 +65,6 @@ func init() { p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{ "AUDIT_WRITE", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "MKNOD", "NET_BIND_SERVICE", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_CHROOT", } - }), - tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{ "AUDIT_WRITE", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "MKNOD", "NET_BIND_SERVICE", "SETFCAP", "SETGID", "SETPCAP", "SETUID", "SYS_CHROOT", } @@ -80,6 +78,7 @@ func init() { tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"} }), + // ensure init container is enforced tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"} }), @@ -87,29 +86,16 @@ func init() { tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"chown"} }), - tweak(p, func(p *corev1.Pod) { - p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"chown"} - }), - // unknown capability - tweak(p, func(p *corev1.Pod) { - p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"bogus"} - }), - tweak(p, func(p *corev1.Pod) { - p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"bogus"} - }), // CAP_ prefix tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"CAP_CHOWN"} }), - tweak(p, func(p *corev1.Pod) { - p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"CAP_CHOWN"} - }), } }, } registerFixtureGenerator( - fixtureKey{level: api.LevelBaseline, version: api.MajorMinorVersion(1, 0), check: "addCapabilities"}, + fixtureKey{level: api.LevelBaseline, version: api.MajorMinorVersion(1, 0), check: "capabilities_baseline"}, fixtureData_1_0, ) }