From 93c6f8969a88e057cf44c6dfa59e0b80c26d664f Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 27 Jun 2021 04:01:09 -0400 Subject: [PATCH] PodSecurity: check: addCapabilities --- .../policy/check_addCapabilities.go | 92 +++++++++++++++ .../test/fixtures_addCapabilities.go | 106 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go create mode 100644 staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go b/staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go new file mode 100644 index 00000000000..baa0de44a2d --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_addCapabilities.go @@ -0,0 +1,92 @@ +/* +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 ( + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/pod-security-admission/api" +) + +func init() { + addCheck(CheckAddCapabilities) +} + +// CheckAddCapabilities returns a baseline level check +// that limits the capabilities that can be added in 1.0+ +func CheckAddCapabilities() Check { + return Check{ + ID: "addCapabilities", + Level: api.LevelBaseline, + Versions: []VersionedCheck{ + { + MinimumVersion: api.MajorMinorVersion(1, 0), + CheckPod: addCapabilities_1_0, + }, + }, + } +} + +var ( + capabilities_allowed_1_0 = sets.NewString( + "AUDIT_WRITE", + "CHOWN", + "DAC_OVERRIDE", + "FOWNER", + "FSETID", + "KILL", + "MKNOD", + "NET_BIND_SERVICE", + "SETFCAP", + "SETGID", + "SETPCAP", + "SETUID", + "SYS_CHROOT", + ) +) + +func addCapabilities_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { + forbiddenContainers := sets.NewString() + forbiddenCapabilities := sets.NewString() + visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) { + if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil { + for _, c := range container.SecurityContext.Capabilities.Add { + if !capabilities_allowed_1_0.Has(string(c)) { + forbiddenContainers.Insert(container.Name) + forbiddenCapabilities.Insert(string(c)) + } + } + } + }) + + if len(forbiddenCapabilities) > 0 { + return CheckResult{ + Allowed: false, + ForbiddenReason: "forbidden capabilities", + ForbiddenDetail: fmt.Sprintf( + "containers %q added %q", + forbiddenContainers.List(), + forbiddenCapabilities.List(), + ), + } + } + return CheckResult{Allowed: true} +} diff --git a/staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go new file mode 100644 index 00000000000..99d5b157201 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_addCapabilities.go @@ -0,0 +1,106 @@ +/* +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 test + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/pod-security-admission/api" +) + +/* +TODO: include field paths in reflect-based unit test + +containerFields: []string{ + `securityContext.capabilities.add`, +}, +*/ + +// ensureCapabilities ensures the pod and all initContainers and containers have a non-nil capabilities. +func ensureCapabilities(p *corev1.Pod) *corev1.Pod { + p = ensureSecurityContext(p) + for i := range p.Spec.Containers { + if p.Spec.Containers[i].SecurityContext.Capabilities == nil { + p.Spec.Containers[i].SecurityContext.Capabilities = &corev1.Capabilities{} + } + } + for i := range p.Spec.InitContainers { + if p.Spec.InitContainers[i].SecurityContext.Capabilities == nil { + p.Spec.InitContainers[i].SecurityContext.Capabilities = &corev1.Capabilities{} + } + } + return p +} + +func init() { + fixtureData_1_0 := fixtureGenerator{ + expectErrorSubstring: "forbidden capabilities", + generatePass: func(p *corev1.Pod) []*corev1.Pod { + p = ensureCapabilities(p) + return []*corev1.Pod{ + // all allowed capabilities + tweak(p, func(p *corev1.Pod) { + 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", + } + }), + } + }, + generateFail: func(p *corev1.Pod) []*corev1.Pod { + p = ensureCapabilities(p) + return []*corev1.Pod{ + // NET_RAW + tweak(p, func(p *corev1.Pod) { + p.Spec.Containers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"} + }), + tweak(p, func(p *corev1.Pod) { + p.Spec.InitContainers[0].SecurityContext.Capabilities.Add = []corev1.Capability{"NET_RAW"} + }), + // case-difference + 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"}, + fixtureData_1_0, + ) +}