Skip to content

Commit 1c8f88d

Browse files
authored
Merge pull request kubernetes#118760 from saschagrunert/user-namespaces-pss
KEP-127: Update PSS based on feature gate
2 parents 57c7b66 + 77e0ade commit 1c8f88d

File tree

7 files changed

+112
-11
lines changed

7 files changed

+112
-11
lines changed

pkg/features/kube_features.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,18 @@ const (
886886
// ImageMaximumGCAge enables the Kubelet configuration field of the same name, allowing an admin
887887
// to specify the age after which an image will be garbage collected.
888888
ImageMaximumGCAge featuregate.Feature = "ImageMaximumGCAge"
889+
890+
// owner: @saschagrunert
891+
// alpha: v1.28
892+
//
893+
// Enables user namespace support for Pod Security Standards. Enabling this
894+
// feature will modify all Pod Security Standard rules to allow setting:
895+
// spec[.*].securityContext.[runAsNonRoot,runAsUser]
896+
// This feature gate should only be enabled if all nodes in the cluster
897+
// support the user namespace feature and have it enabled. The feature gate
898+
// will not graduate or be enabled by default in future Kubernetes
899+
// releases.
900+
UserNamespacesPodSecurityStandards featuregate.Feature = "UserNamespacesPodSecurityStandards"
889901
)
890902

891903
func init() {
@@ -1125,6 +1137,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
11251137

11261138
ImageMaximumGCAge: {Default: false, PreRelease: featuregate.Alpha},
11271139

1140+
UserNamespacesPodSecurityStandards: {Default: false, PreRelease: featuregate.Alpha},
1141+
11281142
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
11291143
// unintentionally on either side:
11301144

plugin/pkg/admission/security/podsecurity/admission.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
_ "k8s.io/kubernetes/pkg/apis/apps/install"
2828
_ "k8s.io/kubernetes/pkg/apis/batch/install"
2929
_ "k8s.io/kubernetes/pkg/apis/core/install"
30+
"k8s.io/kubernetes/pkg/features"
3031

3132
admissionv1 "k8s.io/api/admission/v1"
3233
appsv1 "k8s.io/api/apps/v1"
@@ -151,6 +152,7 @@ func (p *Plugin) updateDelegate() {
151152

152153
func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
153154
c.inspectedFeatureGates = true
155+
policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards))
154156
}
155157

156158
// ValidateInitialization ensures all required options are set

staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ func CheckRunAsNonRoot() Check {
5959
}
6060

6161
func runAsNonRoot_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
62+
// See KEP-127: https://github.com/kubernetes/enhancements/blob/308ba8d/keps/sig-node/127-user-namespaces/README.md?plain=1#L411-L447
63+
if relaxPolicyForUserNamespacePod(podSpec) {
64+
return CheckResult{Allowed: true}
65+
}
66+
6267
// things that explicitly set runAsNonRoot=false
6368
var badSetters []string
6469

staging/src/k8s.io/pod-security-admission/policy/check_runAsNonRoot_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import (
2525

2626
func TestRunAsNonRoot(t *testing.T) {
2727
tests := []struct {
28-
name string
29-
pod *corev1.Pod
30-
expectReason string
31-
expectDetail string
28+
name string
29+
pod *corev1.Pod
30+
expectReason string
31+
expectDetail string
32+
allowed bool
33+
enableUserNamespacesPodSecurityStandards bool
3234
}{
3335
{
3436
name: "no explicit runAsNonRoot",
@@ -80,12 +82,36 @@ func TestRunAsNonRoot(t *testing.T) {
8082
expectReason: `runAsNonRoot != true`,
8183
expectDetail: `pod or containers "a", "b" must set securityContext.runAsNonRoot=true`,
8284
},
85+
{
86+
name: "UserNamespacesPodSecurityStandards enabled without HostUsers",
87+
pod: &corev1.Pod{Spec: corev1.PodSpec{
88+
HostUsers: utilpointer.Bool(false),
89+
}},
90+
allowed: true,
91+
enableUserNamespacesPodSecurityStandards: true,
92+
},
93+
{
94+
name: "UserNamespacesPodSecurityStandards enabled with HostUsers",
95+
pod: &corev1.Pod{Spec: corev1.PodSpec{
96+
Containers: []corev1.Container{
97+
{Name: "a"},
98+
},
99+
HostUsers: utilpointer.Bool(true),
100+
}},
101+
expectReason: `runAsNonRoot != true`,
102+
expectDetail: `pod or container "a" must set securityContext.runAsNonRoot=true`,
103+
allowed: false,
104+
enableUserNamespacesPodSecurityStandards: true,
105+
},
83106
}
84107

85108
for _, tc := range tests {
86109
t.Run(tc.name, func(t *testing.T) {
110+
if tc.enableUserNamespacesPodSecurityStandards {
111+
RelaxPolicyForUserNamespacePods(true)
112+
}
87113
result := runAsNonRoot_1_0(&tc.pod.ObjectMeta, &tc.pod.Spec)
88-
if result.Allowed {
114+
if result.Allowed && !tc.allowed {
89115
t.Fatal("expected disallowed")
90116
}
91117
if e, a := tc.expectReason, result.ForbiddenReason; e != a {

staging/src/k8s.io/pod-security-admission/policy/check_runAsUser.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ func CheckRunAsUser() Check {
6060
}
6161

6262
func runAsUser_1_23(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
63+
// See KEP-127: https://github.com/kubernetes/enhancements/blob/308ba8d/keps/sig-node/127-user-namespaces/README.md?plain=1#L411-L447
64+
if relaxPolicyForUserNamespacePod(podSpec) {
65+
return CheckResult{Allowed: true}
66+
}
67+
6368
// things that explicitly set runAsUser=0
6469
var badSetters []string
6570

staging/src/k8s.io/pod-security-admission/policy/check_runAsUser_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ import (
2525

2626
func TestRunAsUser(t *testing.T) {
2727
tests := []struct {
28-
name string
29-
pod *corev1.Pod
30-
expectAllow bool
31-
expectReason string
32-
expectDetail string
28+
name string
29+
pod *corev1.Pod
30+
expectAllow bool
31+
expectReason string
32+
expectDetail string
33+
enableUserNamespacesPodSecurityStandards bool
3334
}{
3435
{
3536
name: "pod runAsUser=0",
@@ -90,10 +91,35 @@ func TestRunAsUser(t *testing.T) {
9091
}},
9192
expectAllow: true,
9293
},
94+
{
95+
name: "UserNamespacesPodSecurityStandards enabled without HostUsers",
96+
pod: &corev1.Pod{Spec: corev1.PodSpec{
97+
HostUsers: utilpointer.Bool(false),
98+
}},
99+
expectAllow: true,
100+
enableUserNamespacesPodSecurityStandards: true,
101+
},
102+
{
103+
name: "UserNamespacesPodSecurityStandards enabled with HostUsers",
104+
pod: &corev1.Pod{Spec: corev1.PodSpec{
105+
SecurityContext: &corev1.PodSecurityContext{RunAsUser: utilpointer.Int64(0)},
106+
Containers: []corev1.Container{
107+
{Name: "a", SecurityContext: nil},
108+
},
109+
HostUsers: utilpointer.Bool(true),
110+
}},
111+
expectAllow: false,
112+
expectReason: `runAsUser=0`,
113+
expectDetail: `pod must not set runAsUser=0`,
114+
enableUserNamespacesPodSecurityStandards: true,
115+
},
93116
}
94117

95118
for _, tc := range tests {
96119
t.Run(tc.name, func(t *testing.T) {
120+
if tc.enableUserNamespacesPodSecurityStandards {
121+
RelaxPolicyForUserNamespacePods(true)
122+
}
97123
result := runAsUser_1_23(&tc.pod.ObjectMeta, &tc.pod.Spec)
98124
if tc.expectAllow {
99125
if !result.Allowed {

staging/src/k8s.io/pod-security-admission/policy/helpers.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ limitations under the License.
1616

1717
package policy
1818

19-
import "strings"
19+
import (
20+
"strings"
21+
"sync/atomic"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
)
2025

2126
func joinQuote(items []string) string {
2227
if len(items) == 0 {
@@ -31,3 +36,21 @@ func pluralize(singular, plural string, count int) string {
3136
}
3237
return plural
3338
}
39+
40+
var relaxPolicyForUserNamespacePods = &atomic.Bool{}
41+
42+
// RelaxPolicyForUserNamespacePods allows opting into relaxing runAsUser /
43+
// runAsNonRoot restricted policies for user namespace pods, before the
44+
// usernamespace feature has reached GA and propagated to the oldest supported
45+
// nodes.
46+
// This should only be opted into in clusters where the administrator ensures
47+
// all nodes in the cluster enable the user namespace feature.
48+
func RelaxPolicyForUserNamespacePods(relax bool) {
49+
relaxPolicyForUserNamespacePods.Store(relax)
50+
}
51+
52+
// relaxPolicyForUserNamespacePod returns true if a policy should be relaxed
53+
// because of enabled user namespaces in the provided pod spec.
54+
func relaxPolicyForUserNamespacePod(podSpec *corev1.PodSpec) bool {
55+
return relaxPolicyForUserNamespacePods.Load() && podSpec != nil && podSpec.HostUsers != nil && !*podSpec.HostUsers
56+
}

0 commit comments

Comments
 (0)