From a8206ef58be376a196605e0dc09d3fb5bb081d23 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 22 Jun 2021 14:11:59 -0400 Subject: [PATCH] PodSecurity: check: runAsNonRoot --- .../policy/check_runAsNonRoot.go | 105 ++++++++++++++++++ .../test/fixtures_runAsNonRoot.go | 91 +++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go create mode 100644 staging/src/k8s.io/pod-security-admission/test/fixtures_runAsNonRoot.go diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go b/staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go new file mode 100644 index 00000000000..cfffe2cc237 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go @@ -0,0 +1,105 @@ +/* +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 ( + "strings" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/pod-security-admission/api" +) + +/* +Containers must be required to run as non-root users. + +**Restricted Fields:** + +spec.securityContext.runAsNonRoot +spec.containers[*].securityContext.runAsNonRoot +spec.initContainers[*].securityContext.runAsNonRoot + +**Allowed Values:** true +*/ + +func init() { + addCheck(CheckRunAsNonRoot) +} + +// CheckRunAsNonRoot returns a restricted level check +// that requires runAsNonRoot=true in 1.0+ +func CheckRunAsNonRoot() Check { + return Check{ + ID: "runAsNonRoot", + Level: api.LevelRestricted, + Versions: []VersionedCheck{ + { + MinimumVersion: api.MajorMinorVersion(1, 0), + CheckPod: runAsNonRoot_1_0, + }, + }, + } +} + +func runAsNonRoot_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { + var forbiddenPaths []string + + // TODO: how to check ephemeral containers + + containerCount := 0 + containerRunAsNonRootCount := 0 + podRunAsNonRoot := false + + visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) { + containerCount++ + if container.SecurityContext != nil && container.SecurityContext.RunAsNonRoot != nil { + if !*container.SecurityContext.RunAsNonRoot { + forbiddenPaths = append(forbiddenPaths, path.Child("securityContext", "runAsNonRoot").String()) + } else { + containerRunAsNonRootCount++ + } + } + }) + + if podSpec.SecurityContext != nil && podSpec.SecurityContext.RunAsNonRoot != nil { + if !*podSpec.SecurityContext.RunAsNonRoot { + forbiddenPaths = append(forbiddenPaths, field.NewPath("spec").Child("securityContext", "runAsNonRoot").String()) + } else { + podRunAsNonRoot = true + } + } + + // pod or containers explicitly set runAsNonRoot=false + if len(forbiddenPaths) > 0 { + return CheckResult{ + Allowed: false, + ForbiddenReason: "runAsNonRoot != false", + ForbiddenDetail: strings.Join(forbiddenPaths, ", "), + } + } + + // pod didn't set runAsNonRoot and not all containers opted into runAsNonRoot + if podRunAsNonRoot == false && containerCount > containerRunAsNonRootCount { + return CheckResult{ + Allowed: false, + ForbiddenReason: "runAsNonRoot != false", + } + } + + return CheckResult{Allowed: true} +} diff --git a/staging/src/k8s.io/pod-security-admission/test/fixtures_runAsNonRoot.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_runAsNonRoot.go new file mode 100644 index 00000000000..ec199b7844e --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_runAsNonRoot.go @@ -0,0 +1,91 @@ +/* +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" + "k8s.io/utils/pointer" +) + +/* +TODO: include field paths in reflect-based unit test + +podFields: []string{ + `securityContext.runAsNonRoot`, +}, +containerFields: []string{ + `securityContext.runAsNonRoot`, +}, + +*/ + +func init() { + + fixtureData_1_0 := fixtureGenerator{ + generatePass: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSecurityContext(p) + return []*corev1.Pod{ + // set at pod level + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + p.Spec.Containers[0].SecurityContext.RunAsNonRoot = nil + p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = nil + }), + // set on all containers + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext.RunAsNonRoot = nil + p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + }), + // set at pod level and containers + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(true) + }), + } + }, + generateFail: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSecurityContext(p) + return []*corev1.Pod{ + // unset everywhere + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext.RunAsNonRoot = nil + p.Spec.Containers[0].SecurityContext.RunAsNonRoot = nil + p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = nil + }), + // explicit false on pod + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }), + // explicit false on containers + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.RunAsNonRoot = pointer.BoolPtr(false) }), + // nil security contexts + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext = nil + p.Spec.Containers[0].SecurityContext = nil + p.Spec.InitContainers[0].SecurityContext = nil + }), + } + }, + } + + registerFixtureGenerator( + fixtureKey{level: api.LevelRestricted, version: api.MajorMinorVersion(1, 0), check: "runAsNonRoot"}, + fixtureData_1_0, + ) +}