diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts.go b/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts.go index cb3ee1a806d..5ae5150e501 100644 --- a/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts.go +++ b/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts.go @@ -18,6 +18,8 @@ package policy import ( "fmt" + "strconv" + "strings" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,6 +28,17 @@ import ( "k8s.io/pod-security-admission/api" ) +/* +HostPort ports must be forbidden. + +**Restricted Fields:** + +spec.containers[*].ports[*].hostPort +spec.initContainers[*].ports[*].hostPort + +**Allowed Values:** undefined/0 +*/ + func init() { addCheck(CheckHostPorts) } @@ -46,25 +59,32 @@ func CheckHostPorts() Check { } func hostPorts_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult { - forbiddenContainers := sets.NewString() - forbiddenHostPorts := sets.NewInt32() + var badContainers []string + forbiddenHostPorts := sets.NewString() visitContainersWithPath(podSpec, field.NewPath("spec"), func(container *corev1.Container, path *field.Path) { + valid := true for _, c := range container.Ports { if c.HostPort != 0 { - forbiddenContainers.Insert(container.Name) - forbiddenHostPorts.Insert(c.HostPort) + valid = false + forbiddenHostPorts.Insert(strconv.Itoa(int(c.HostPort))) } } + if !valid { + badContainers = append(badContainers, container.Name) + } }) - if len(forbiddenHostPorts) > 0 { + if len(badContainers) > 0 { return CheckResult{ Allowed: false, - ForbiddenReason: "forbidden host ports", + ForbiddenReason: "hostPort", ForbiddenDetail: fmt.Sprintf( - "containers %q use these host ports %d", - forbiddenContainers.List(), - forbiddenHostPorts.List(), + "%s %s %s %s %s", + pluralize("container", "containers", len(badContainers)), + joinQuote(badContainers), + pluralize("uses", "use", len(badContainers)), + pluralize("hostPort", "hostPorts", len(forbiddenHostPorts)), + strings.Join(forbiddenHostPorts.List(), ", "), ), } } diff --git a/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts_test.go b/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts_test.go new file mode 100644 index 00000000000..06da718d760 --- /dev/null +++ b/staging/src/k8s.io/pod-security-admission/policy/check_hostPorts_test.go @@ -0,0 +1,71 @@ +/* +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 TestHostPort(t *testing.T) { + tests := []struct { + name string + pod *corev1.Pod + expectReason string + expectDetail string + }{ + { + name: "one container, one host port", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "a", Ports: []corev1.ContainerPort{{HostPort: 0}}}, + {Name: "b", Ports: []corev1.ContainerPort{{HostPort: 0}, {HostPort: 20}}}, + }, + }}, + expectReason: `hostPort`, + expectDetail: `container "b" uses hostPort 20`, + }, + { + name: "multiple containers, multiple host port", + pod: &corev1.Pod{Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "a", Ports: []corev1.ContainerPort{{HostPort: 0}}}, + {Name: "b", Ports: []corev1.ContainerPort{{HostPort: 0}, {HostPort: 10}, {HostPort: 20}}}, + {Name: "c", Ports: []corev1.ContainerPort{{HostPort: 0}, {HostPort: 10}, {HostPort: 30}}}, + }, + }}, + expectReason: `hostPort`, + expectDetail: `containers "b", "c" use hostPorts 10, 20, 30`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := hostPorts_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) + } + }) + } +} diff --git a/staging/src/k8s.io/pod-security-admission/test/fixtures_hostPorts.go b/staging/src/k8s.io/pod-security-admission/test/fixtures_hostPorts.go index f6d13d27480..a48ad4d3a04 100644 --- a/staging/src/k8s.io/pod-security-admission/test/fixtures_hostPorts.go +++ b/staging/src/k8s.io/pod-security-admission/test/fixtures_hostPorts.go @@ -31,7 +31,7 @@ containerFields: []string{ func init() { fixtureData_1_0 := fixtureGenerator{ - expectErrorSubstring: "forbidden host ports", + expectErrorSubstring: "hostPort", generatePass: func(p *corev1.Pod) []*corev1.Pod { return []*corev1.Pod{ // no host ports @@ -60,6 +60,7 @@ func init() { }, } }), + // check initContainer tweak(p, func(p *corev1.Pod) { p.Spec.InitContainers[0].Ports = []corev1.ContainerPort{ { @@ -68,7 +69,7 @@ func init() { }, } }), - // both init-container and app container use host ports and regular ports + // mix of hostPorts and regular ports tweak(p, func(p *corev1.Pod) { p.Spec.Containers[0].Ports = []corev1.ContainerPort{ {