mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 12:07:47 +00:00
Merge pull request #118760 from saschagrunert/user-namespaces-pss
KEP-127: Update PSS based on feature gate
This commit is contained in:
commit
1c8f88d4b6
@ -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
|
||||||
|
|
||||||
|
@ -25,10 +25,12 @@ import (
|
|||||||
|
|
||||||
func TestRunAsNonRoot(t *testing.T) {
|
func TestRunAsNonRoot(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
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
|
||||||
|
|
||||||
|
@ -25,11 +25,12 @@ import (
|
|||||||
|
|
||||||
func TestRunAsUser(t *testing.T) {
|
func TestRunAsUser(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
pod *corev1.Pod
|
pod *corev1.Pod
|
||||||
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user