Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KEP-127: Update PSS based on feature gate #118760

Merged
merged 1 commit into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions pkg/features/kube_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,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() {
Expand Down Expand Up @@ -1123,6 +1135,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:

Expand Down
2 changes: 2 additions & 0 deletions plugin/pkg/admission/security/podsecurity/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 24 additions & 1 deletion staging/src/k8s.io/pod-security-admission/policy/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}