Merge pull request #118760 from saschagrunert/user-namespaces-pss
KEP-127: Update PSS based on feature gate
This commit is contained in:
		@@ -886,6 +886,18 @@ const (
 | 
				
			|||||||
	// ImageMaximumGCAge enables the Kubelet configuration field of the same name, allowing an admin
 | 
						// ImageMaximumGCAge enables the Kubelet configuration field of the same name, allowing an admin
 | 
				
			||||||
	// to specify the age after which an image will be garbage collected.
 | 
						// to specify the age after which an image will be garbage collected.
 | 
				
			||||||
	ImageMaximumGCAge featuregate.Feature = "ImageMaximumGCAge"
 | 
						ImageMaximumGCAge featuregate.Feature = "ImageMaximumGCAge"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// owner: @saschagrunert
 | 
				
			||||||
 | 
						// alpha: v1.28
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Enables user namespace support for Pod Security Standards. Enabling this
 | 
				
			||||||
 | 
						// feature will modify all Pod Security Standard rules to allow setting:
 | 
				
			||||||
 | 
						// spec[.*].securityContext.[runAsNonRoot,runAsUser]
 | 
				
			||||||
 | 
						// This feature gate should only be enabled if all nodes in the cluster
 | 
				
			||||||
 | 
						// support the user namespace feature and have it enabled. The feature gate
 | 
				
			||||||
 | 
						// will not graduate or be enabled by default in future Kubernetes
 | 
				
			||||||
 | 
						// releases.
 | 
				
			||||||
 | 
						UserNamespacesPodSecurityStandards featuregate.Feature = "UserNamespacesPodSecurityStandards"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@@ -1125,6 +1137,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ImageMaximumGCAge: {Default: false, PreRelease: featuregate.Alpha},
 | 
						ImageMaximumGCAge: {Default: false, PreRelease: featuregate.Alpha},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						UserNamespacesPodSecurityStandards: {Default: false, PreRelease: featuregate.Alpha},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// inherited features from generic apiserver, relisted here to get a conflict if it is changed
 | 
						// inherited features from generic apiserver, relisted here to get a conflict if it is changed
 | 
				
			||||||
	// unintentionally on either side:
 | 
						// unintentionally on either side:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ import (
 | 
				
			|||||||
	_ "k8s.io/kubernetes/pkg/apis/apps/install"
 | 
						_ "k8s.io/kubernetes/pkg/apis/apps/install"
 | 
				
			||||||
	_ "k8s.io/kubernetes/pkg/apis/batch/install"
 | 
						_ "k8s.io/kubernetes/pkg/apis/batch/install"
 | 
				
			||||||
	_ "k8s.io/kubernetes/pkg/apis/core/install"
 | 
						_ "k8s.io/kubernetes/pkg/apis/core/install"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	admissionv1 "k8s.io/api/admission/v1"
 | 
						admissionv1 "k8s.io/api/admission/v1"
 | 
				
			||||||
	appsv1 "k8s.io/api/apps/v1"
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
@@ -151,6 +152,7 @@ func (p *Plugin) updateDelegate() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
 | 
					func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
 | 
				
			||||||
	c.inspectedFeatureGates = true
 | 
						c.inspectedFeatureGates = true
 | 
				
			||||||
 | 
						policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateInitialization ensures all required options are set
 | 
					// ValidateInitialization ensures all required options are set
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,6 +59,11 @@ func CheckRunAsNonRoot() Check {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func runAsNonRoot_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
 | 
					func runAsNonRoot_1_0(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
 | 
				
			||||||
 | 
						// See KEP-127: https://github.com/kubernetes/enhancements/blob/308ba8d/keps/sig-node/127-user-namespaces/README.md?plain=1#L411-L447
 | 
				
			||||||
 | 
						if relaxPolicyForUserNamespacePod(podSpec) {
 | 
				
			||||||
 | 
							return CheckResult{Allowed: true}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// things that explicitly set runAsNonRoot=false
 | 
						// things that explicitly set runAsNonRoot=false
 | 
				
			||||||
	var badSetters []string
 | 
						var badSetters []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,8 @@ func TestRunAsNonRoot(t *testing.T) {
 | 
				
			|||||||
		pod                                      *corev1.Pod
 | 
							pod                                      *corev1.Pod
 | 
				
			||||||
		expectReason                             string
 | 
							expectReason                             string
 | 
				
			||||||
		expectDetail                             string
 | 
							expectDetail                             string
 | 
				
			||||||
 | 
							allowed                                  bool
 | 
				
			||||||
 | 
							enableUserNamespacesPodSecurityStandards bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "no explicit runAsNonRoot",
 | 
								name: "no explicit runAsNonRoot",
 | 
				
			||||||
@@ -80,12 +82,36 @@ func TestRunAsNonRoot(t *testing.T) {
 | 
				
			|||||||
			expectReason: `runAsNonRoot != true`,
 | 
								expectReason: `runAsNonRoot != true`,
 | 
				
			||||||
			expectDetail: `pod or containers "a", "b" must set securityContext.runAsNonRoot=true`,
 | 
								expectDetail: `pod or containers "a", "b" must set securityContext.runAsNonRoot=true`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserNamespacesPodSecurityStandards enabled without HostUsers",
 | 
				
			||||||
 | 
								pod: &corev1.Pod{Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
									HostUsers: utilpointer.Bool(false),
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								allowed:                                  true,
 | 
				
			||||||
 | 
								enableUserNamespacesPodSecurityStandards: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserNamespacesPodSecurityStandards enabled with HostUsers",
 | 
				
			||||||
 | 
								pod: &corev1.Pod{Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
									Containers: []corev1.Container{
 | 
				
			||||||
 | 
										{Name: "a"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									HostUsers: utilpointer.Bool(true),
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								expectReason:                             `runAsNonRoot != true`,
 | 
				
			||||||
 | 
								expectDetail:                             `pod or container "a" must set securityContext.runAsNonRoot=true`,
 | 
				
			||||||
 | 
								allowed:                                  false,
 | 
				
			||||||
 | 
								enableUserNamespacesPodSecurityStandards: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tc := range tests {
 | 
						for _, tc := range tests {
 | 
				
			||||||
		t.Run(tc.name, func(t *testing.T) {
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if tc.enableUserNamespacesPodSecurityStandards {
 | 
				
			||||||
 | 
									RelaxPolicyForUserNamespacePods(true)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			result := runAsNonRoot_1_0(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
								result := runAsNonRoot_1_0(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
				
			||||||
			if result.Allowed {
 | 
								if result.Allowed && !tc.allowed {
 | 
				
			||||||
				t.Fatal("expected disallowed")
 | 
									t.Fatal("expected disallowed")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if e, a := tc.expectReason, result.ForbiddenReason; e != a {
 | 
								if e, a := tc.expectReason, result.ForbiddenReason; e != a {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,11 @@ func CheckRunAsUser() Check {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func runAsUser_1_23(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
 | 
					func runAsUser_1_23(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {
 | 
				
			||||||
 | 
						// See KEP-127: https://github.com/kubernetes/enhancements/blob/308ba8d/keps/sig-node/127-user-namespaces/README.md?plain=1#L411-L447
 | 
				
			||||||
 | 
						if relaxPolicyForUserNamespacePod(podSpec) {
 | 
				
			||||||
 | 
							return CheckResult{Allowed: true}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// things that explicitly set runAsUser=0
 | 
						// things that explicitly set runAsUser=0
 | 
				
			||||||
	var badSetters []string
 | 
						var badSetters []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ func TestRunAsUser(t *testing.T) {
 | 
				
			|||||||
		expectAllow                              bool
 | 
							expectAllow                              bool
 | 
				
			||||||
		expectReason                             string
 | 
							expectReason                             string
 | 
				
			||||||
		expectDetail                             string
 | 
							expectDetail                             string
 | 
				
			||||||
 | 
							enableUserNamespacesPodSecurityStandards bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "pod runAsUser=0",
 | 
								name: "pod runAsUser=0",
 | 
				
			||||||
@@ -90,10 +91,35 @@ func TestRunAsUser(t *testing.T) {
 | 
				
			|||||||
			}},
 | 
								}},
 | 
				
			||||||
			expectAllow: true,
 | 
								expectAllow: true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserNamespacesPodSecurityStandards enabled without HostUsers",
 | 
				
			||||||
 | 
								pod: &corev1.Pod{Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
									HostUsers: utilpointer.Bool(false),
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								expectAllow:                              true,
 | 
				
			||||||
 | 
								enableUserNamespacesPodSecurityStandards: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "UserNamespacesPodSecurityStandards enabled with HostUsers",
 | 
				
			||||||
 | 
								pod: &corev1.Pod{Spec: corev1.PodSpec{
 | 
				
			||||||
 | 
									SecurityContext: &corev1.PodSecurityContext{RunAsUser: utilpointer.Int64(0)},
 | 
				
			||||||
 | 
									Containers: []corev1.Container{
 | 
				
			||||||
 | 
										{Name: "a", SecurityContext: nil},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									HostUsers: utilpointer.Bool(true),
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
								expectAllow:                              false,
 | 
				
			||||||
 | 
								expectReason:                             `runAsUser=0`,
 | 
				
			||||||
 | 
								expectDetail:                             `pod must not set runAsUser=0`,
 | 
				
			||||||
 | 
								enableUserNamespacesPodSecurityStandards: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tc := range tests {
 | 
						for _, tc := range tests {
 | 
				
			||||||
		t.Run(tc.name, func(t *testing.T) {
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if tc.enableUserNamespacesPodSecurityStandards {
 | 
				
			||||||
 | 
									RelaxPolicyForUserNamespacePods(true)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			result := runAsUser_1_23(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
								result := runAsUser_1_23(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
				
			||||||
			if tc.expectAllow {
 | 
								if tc.expectAllow {
 | 
				
			||||||
				if !result.Allowed {
 | 
									if !result.Allowed {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,12 @@ limitations under the License.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package policy
 | 
					package policy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "strings"
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func joinQuote(items []string) string {
 | 
					func joinQuote(items []string) string {
 | 
				
			||||||
	if len(items) == 0 {
 | 
						if len(items) == 0 {
 | 
				
			||||||
@@ -31,3 +36,21 @@ func pluralize(singular, plural string, count int) string {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return plural
 | 
						return plural
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var relaxPolicyForUserNamespacePods = &atomic.Bool{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RelaxPolicyForUserNamespacePods allows opting into relaxing runAsUser /
 | 
				
			||||||
 | 
					// runAsNonRoot restricted policies for user namespace pods, before the
 | 
				
			||||||
 | 
					// usernamespace feature has reached GA and propagated to the oldest supported
 | 
				
			||||||
 | 
					// nodes.
 | 
				
			||||||
 | 
					// This should only be opted into in clusters where the administrator ensures
 | 
				
			||||||
 | 
					// all nodes in the cluster enable the user namespace feature.
 | 
				
			||||||
 | 
					func RelaxPolicyForUserNamespacePods(relax bool) {
 | 
				
			||||||
 | 
						relaxPolicyForUserNamespacePods.Store(relax)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// relaxPolicyForUserNamespacePod returns true if a policy should be relaxed
 | 
				
			||||||
 | 
					// because of enabled user namespaces in the provided pod spec.
 | 
				
			||||||
 | 
					func relaxPolicyForUserNamespacePod(podSpec *corev1.PodSpec) bool {
 | 
				
			||||||
 | 
						return relaxPolicyForUserNamespacePods.Load() && podSpec != nil && podSpec.HostUsers != nil && !*podSpec.HostUsers
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user