mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	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
 | 
			
		||||
	// to specify the age after which an image will be garbage collected.
 | 
			
		||||
	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() {
 | 
			
		||||
@@ -1125,6 +1137,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	// unintentionally on either side:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import (
 | 
			
		||||
	_ "k8s.io/kubernetes/pkg/apis/apps/install"
 | 
			
		||||
	_ "k8s.io/kubernetes/pkg/apis/batch/install"
 | 
			
		||||
	_ "k8s.io/kubernetes/pkg/apis/core/install"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/features"
 | 
			
		||||
 | 
			
		||||
	admissionv1 "k8s.io/api/admission/v1"
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
@@ -151,6 +152,7 @@ func (p *Plugin) updateDelegate() {
 | 
			
		||||
 | 
			
		||||
func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
 | 
			
		||||
	c.inspectedFeatureGates = true
 | 
			
		||||
	policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	// 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
 | 
			
		||||
	var badSetters []string
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,10 +25,12 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestRunAsNonRoot(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		pod          *corev1.Pod
 | 
			
		||||
		expectReason string
 | 
			
		||||
		expectDetail string
 | 
			
		||||
		name                                     string
 | 
			
		||||
		pod                                      *corev1.Pod
 | 
			
		||||
		expectReason                             string
 | 
			
		||||
		expectDetail                             string
 | 
			
		||||
		allowed                                  bool
 | 
			
		||||
		enableUserNamespacesPodSecurityStandards bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "no explicit runAsNonRoot",
 | 
			
		||||
@@ -80,12 +82,36 @@ func TestRunAsNonRoot(t *testing.T) {
 | 
			
		||||
			expectReason: `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 {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			if tc.enableUserNamespacesPodSecurityStandards {
 | 
			
		||||
				RelaxPolicyForUserNamespacePods(true)
 | 
			
		||||
			}
 | 
			
		||||
			result := runAsNonRoot_1_0(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
			
		||||
			if result.Allowed {
 | 
			
		||||
			if result.Allowed && !tc.allowed {
 | 
			
		||||
				t.Fatal("expected disallowed")
 | 
			
		||||
			}
 | 
			
		||||
			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 {
 | 
			
		||||
	// 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
 | 
			
		||||
	var badSetters []string
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,11 +25,12 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestRunAsUser(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		pod          *corev1.Pod
 | 
			
		||||
		expectAllow  bool
 | 
			
		||||
		expectReason string
 | 
			
		||||
		expectDetail string
 | 
			
		||||
		name                                     string
 | 
			
		||||
		pod                                      *corev1.Pod
 | 
			
		||||
		expectAllow                              bool
 | 
			
		||||
		expectReason                             string
 | 
			
		||||
		expectDetail                             string
 | 
			
		||||
		enableUserNamespacesPodSecurityStandards bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "pod runAsUser=0",
 | 
			
		||||
@@ -90,10 +91,35 @@ func TestRunAsUser(t *testing.T) {
 | 
			
		||||
			}},
 | 
			
		||||
			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 {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			if tc.enableUserNamespacesPodSecurityStandards {
 | 
			
		||||
				RelaxPolicyForUserNamespacePods(true)
 | 
			
		||||
			}
 | 
			
		||||
			result := runAsUser_1_23(&tc.pod.ObjectMeta, &tc.pod.Spec)
 | 
			
		||||
			if tc.expectAllow {
 | 
			
		||||
				if !result.Allowed {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,12 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
package policy
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
 | 
			
		||||
	corev1 "k8s.io/api/core/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func joinQuote(items []string) string {
 | 
			
		||||
	if len(items) == 0 {
 | 
			
		||||
@@ -31,3 +36,21 @@ func pluralize(singular, plural string, count int) string {
 | 
			
		||||
	}
 | 
			
		||||
	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