diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser.go b/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser.go new file mode 100644 index 00000000000..de20f4d0ad4 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser.go @@ -0,0 +1,99 @@ +/* +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" + "strings" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/pod-security-admission/api" +) + +/* +Containers must not set runAsUser: 0 + +**Restricted Fields:** + +spec.securityContext.runAsUser +spec.containers[*].securityContext.runAsUser +spec.initContainers[*].securityContext.runAsUser + +**Allowed Values:** +non-zero values +undefined/null + +*/ + +func init() { + addCheck(CheckRunAsUser) +} + +// CheckRunAsUser returns a restricted level check +// that forbides runAsUser=0 in 1.23+ +func CheckRunAsUser() Check { + return Check{ + ID: "runAsUser", + Level: api.LevelRestricted, + Versions: []VersionedCheck{ + { + MinimumVersion: api.MajorMinorVersion(1, 23), + CheckPod: runAsUser_1_23, + }, + }, + } +} + +func runAsUser_1_23(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { + // things that explicitly set runAsUser=0 + var badSetters []string + + if podSpec.SecurityContext != nil && podSpec.SecurityContext.RunAsUser != nil && *podSpec.SecurityContext.RunAsUser == 0 { + badSetters = append(badSetters, "pod") + } + + // containers that explicitly set runAsUser=0 + var explicitlyBadContainers []string + + visitContainers(podSpec, func(container *corev1.Container) { + if container.SecurityContext != nil && container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 { + explicitlyBadContainers = append(explicitlyBadContainers, container.Name) + } + }) + + if len(explicitlyBadContainers) > 0 { + badSetters = append( + badSetters, + fmt.Sprintf( + "%s %s", + pluralize("container", "containers", len(explicitlyBadContainers)), + joinQuote(explicitlyBadContainers), + ), + ) + } + // pod or containers explicitly set runAsUser=0 + if len(badSetters) > 0 { + return CheckResult{ + Allowed: false, + ForbiddenReason: "runAsUser=0", + ForbiddenDetail: fmt.Sprintf("%s must not set runAsUser=0", strings.Join(badSetters, " and ")), + } + } + + return CheckResult{Allowed: true} +} diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser_test.go b/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser_test.go new file mode 100644 index 00000000000..ac00535f974 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_runAsUser_test.go @@ -0,0 +1,115 @@ +/* +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" + utilpointer "k8s.io/utils/pointer" +) + +func TestRunAsUser(t *testing.T) { + tests := []struct { + name string + pod *corev1.Pod + expectAllow bool + expectReason string + expectDetail string + }{ + { + name: "pod runAsUser=0", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{RunAsUser: utilpointer.Int64(0)}, + Containers: []corev1.Container{ + {Name: "a", SecurityContext: nil}, + }, + }}, + expectReason: `runAsUser=0`, + expectDetail: `pod must not set runAsUser=0`, + }, + { + name: "pod runAsUser=non-zero", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{RunAsUser: utilpointer.Int64(1000)}, + Containers: []corev1.Container{ + {Name: "a", SecurityContext: nil}, + }, + }}, + expectAllow: true, + }, + { + name: "pod runAsUser=nil", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{RunAsUser: nil}, + Containers: []corev1.Container{ + {Name: "a", SecurityContext: nil}, + }, + }}, + expectAllow: true, + }, + { + name: "containers runAsUser=0", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + SecurityContext: &corev1.PodSecurityContext{RunAsUser: utilpointer.Int64(1000)}, + Containers: []corev1.Container{ + {Name: "a", SecurityContext: nil}, + {Name: "b", SecurityContext: &corev1.SecurityContext{}}, + {Name: "c", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(0)}}, + {Name: "d", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(0)}}, + {Name: "e", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(1)}}, + {Name: "f", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(1)}}, + }, + }}, + expectReason: `runAsUser=0`, + expectDetail: `containers "c", "d" must not set runAsUser=0`, + }, + { + name: "containers runAsUser=non-zero", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "c", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(1)}}, + {Name: "d", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(2)}}, + {Name: "e", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(3)}}, + {Name: "f", SecurityContext: &corev1.SecurityContext{RunAsUser: utilpointer.Int64(4)}}, + }, + }}, + expectAllow: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := runAsUser_1_23(&tc.pod.ObjectMeta, &tc.pod.Spec) + if tc.expectAllow { + if !result.Allowed { + t.Fatalf("expected to be allowed, disallowed: %s, %s", result.ForbiddenReason, result.ForbiddenDetail) + } + return + } + 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_runAsUser.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_runAsUser.go new file mode 100644 index 00000000000..d01ad951a40 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_runAsUser.go @@ -0,0 +1,66 @@ +/* +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.runAsUser`, +}, +containerFields: []string{ + `securityContext.runAsUser`, +}, + +*/ + +func init() { + + fixtureData_1_23 := fixtureGenerator{ + generatePass: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSecurityContext(p) + return []*corev1.Pod{ + tweak(p, func(p *corev1.Pod) { + p.Spec.SecurityContext.RunAsUser = pointer.Int64Ptr(1000) + p.Spec.Containers[0].SecurityContext.RunAsUser = pointer.Int64Ptr(1000) + p.Spec.InitContainers[0].SecurityContext.RunAsUser = pointer.Int64Ptr(1000) + }), + } + }, + generateFail: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSecurityContext(p) + return []*corev1.Pod{ + // explicit 0 on pod + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.RunAsUser = pointer.Int64Ptr(0) }), + // explicit 0 on containers + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.RunAsUser = pointer.Int64Ptr(0) }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.RunAsUser = pointer.Int64Ptr(0) }), + } + }, + } + + registerFixtureGenerator( + fixtureKey{level: api.LevelRestricted, version: api.MajorMinorVersion(1, 23), check: "runAsUser"}, + fixtureData_1_23, + ) +}