From 12ea930aae3a21ef63294a35a2710c275d60757e Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 22 Jun 2021 14:11:38 -0400 Subject: [PATCH] PodSecurity: check: selinux Co-authored-by: Tim Allclair --- .../policy/check_selinux.go | 110 ++++++++++++++++++ .../test/fixtures_selinux.go | 98 ++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 staging/src/k8s.io/pod-security-admission/policy/check_selinux.go create mode 100644 staging/src/k8s.io/pod-security-admission/test/fixtures_selinux.go diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_selinux.go b/staging/src/k8s.io/pod-security-admission/policy/check_selinux.go new file mode 100644 index 00000000000..fa34d56254d --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_selinux.go @@ -0,0 +1,110 @@ +/* +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/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/pod-security-admission/api" +) + +/* +Setting the SELinux type is restricted, and setting a custom SELinux user or role option is forbidden. + +**Restricted Fields:** +spec.securityContext.seLinuxOptions.type +spec.containers[*].securityContext.seLinuxOptions.type +spec.initContainers[*].securityContext.seLinuxOptions.type + +**Allowed Values:** +undefined/empty +container_t +container_init_t +container_kvm_t + +**Restricted Fields:** +spec.securityContext.seLinuxOptions.user +spec.containers[*].securityContext.seLinuxOptions.user +spec.initContainers[*].securityContext.seLinuxOptions.user +spec.securityContext.seLinuxOptions.role +spec.containers[*].securityContext.seLinuxOptions.role +spec.initContainers[*].securityContext.seLinuxOptions.role + +**Allowed Values:** undefined/empty +*/ + +func init() { + addCheck(CheckSELinux) +} + +// CheckSELinux returns a baseline level check +// that limits seLinuxOptions type, user, and role values in 1.0+ +func CheckSELinux() Check { + return Check{ + ID: "selinux", + Level: api.LevelBaseline, + Versions: []VersionedCheck{ + { + MinimumVersion: api.MajorMinorVersion(1, 0), + CheckPod: checkSelinux_1_0, + }, + }, + } +} + +var ( + selinux_allowed_types_1_0 = sets.NewString("", "container_t", "container_init_t", "container_kvm_t") +) + +func checkSelinux_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { + var forbiddenDetails []string + + checkSelinuxOptions := func(path *field.Path, opts *corev1.SELinuxOptions) { + if !selinux_allowed_types_1_0.Has(opts.Type) { + forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "type").String()) + } + if len(opts.User) > 0 { + forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "user").String()) + } + if len(opts.Role) > 0 { + forbiddenDetails = append(forbiddenDetails, path.Child("securityContext", "seLinuxOptions", "role").String()) + } + } + + if podSpec.SecurityContext != nil && podSpec.SecurityContext.SELinuxOptions != nil { + checkSelinuxOptions(field.NewPath("spec"), podSpec.SecurityContext.SELinuxOptions) + } + + visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) { + if container.SecurityContext != nil && container.SecurityContext.SELinuxOptions != nil { + checkSelinuxOptions(path, container.SecurityContext.SELinuxOptions) + } + }) + + if len(forbiddenDetails) > 0 { + return CheckResult{ + Allowed: false, + ForbiddenReason: "forbidden seLinuxOptions", + ForbiddenDetail: strings.Join(forbiddenDetails, ", "), + } + } + return CheckResult{Allowed: true} +} diff --git a/staging/src/k8s.io/pod-security-admission/test/fixtures_selinux.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_selinux.go new file mode 100644 index 00000000000..ed093b5adbe --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_selinux.go @@ -0,0 +1,98 @@ +/* +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 + +podFields: []string{ + `spec.securityContext.seLinuxOptions.type`, + `spec.securityContext.seLinuxOptions.user`, + `spec.securityContext.seLinuxOptions.role`, +}, +containerFields: []string{ + `securityContext.seLinuxOptions.type`, + `securityContext.seLinuxOptions.user`, + `securityContext.seLinuxOptions.role`, +}, +*/ + +func init() { + fixtureData_1_0 := fixtureGenerator{ + expectErrorSubstring: "seLinuxOptions", + generatePass: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSELinuxOptions(p) + return []*corev1.Pod{ + // security context with no seLinuxOptions + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions = nil }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions = nil }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions = nil }), + // seLinuxOptions with type="" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "" }), + // seLinuxOptions with type="container_t" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_t" }), + // seLinuxOptions with type="container_init_t" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_init_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_init_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_init_t" }), + // seLinuxOptions with type="container_kvm_t" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "container_kvm_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "container_kvm_t" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "container_kvm_t" }), + // seLinuxOptions with level="" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Level = "" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Level = "" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Level = "" }), + // seLinuxOptions with arbitrary level="" + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Level = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Level = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Level = "somevalue" }), + } + }, + generateFail: func(p *corev1.Pod) []*corev1.Pod { + p = ensureSELinuxOptions(p) + return []*corev1.Pod{ + // seLinuxOptions with out of bounds type + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Type = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Type = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Type = "somevalue" }), + // seLinuxOptions with out of bounds user + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.User = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.User = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.User = "somevalue" }), + // seLinuxOptions with out of bounds role + tweak(p, func(p *corev1.Pod) { p.Spec.SecurityContext.SELinuxOptions.Role = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].SecurityContext.SELinuxOptions.Role = "somevalue" }), + tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].SecurityContext.SELinuxOptions.Role = "somevalue" }), + } + }, + } + + registerFixtureGenerator( + fixtureKey{level: api.LevelBaseline, version: api.MajorMinorVersion(1, 0), check: "selinux"}, + fixtureData_1_0, + ) +}