PodSecurity: baseline capabilities: cleanup

Rename to capabilities_baseline
Add unit test exercising forbidden reason and details
Consolidate integration test fixtures
This commit is contained in:
Jordan Liggitt 2021-07-07 13:01:23 -04:00
parent b390e9e32d
commit 809abf4f5b
3 changed files with 93 additions and 32 deletions

View File

@ -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()),
),
}
}

View File

@ -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)
}
})
}
}

View File

@ -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,
)
}