From c8d85b1de7741d1a243d689ceebc55e042a0f0eb Mon Sep 17 00:00:00 2001 From: Rohit Patil Date: Mon, 10 Nov 2025 12:59:35 +0530 Subject: [PATCH 1/4] UPSTREAM: 1234: Fix user namespace validation for runAsGroup, fsGroup, and supplementalGroups --- pkg/apis/core/validation/validation.go | 28 ++- pkg/apis/core/validation/validation_test.go | 223 +++++++++++++++++++- 2 files changed, 239 insertions(+), 12 deletions(-) diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 9f1ec62259d39..7814e987ebe16 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -4532,7 +4532,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...) allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...) allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...) - allErrs = append(allErrs, validatePodSpecSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...) + allErrs = append(allErrs, validatePodSpecSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts, hostUsers)...) allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...) allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...) allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...) @@ -5402,7 +5402,7 @@ func validateSELinuxChangePolicy(seLinuxChangePolicy *core.PodSELinuxChangePolic // validatePodSpecSecurityContext verifies the SecurityContext of a PodSpec, // whether that is defined in a Pod or in an embedded PodSpec (e.g. a // Deployment's pod template). -func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions) field.ErrorList { +func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, spec *core.PodSpec, specPath, fldPath *field.Path, opts PodValidationOptions, hostUsers bool) field.ErrorList { allErrs := field.ErrorList{} if securityContext != nil { @@ -5410,21 +5410,37 @@ func validatePodSpecSecurityContext(securityContext *core.PodSecurityContext, sp for _, msg := range validation.IsValidGroupID(*securityContext.FSGroup) { allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), msg)) } + // When user namespaces are enabled (hostUsers=false), GIDs must be in range 0-65535 + if !hostUsers && *securityContext.FSGroup > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *securityContext.FSGroup, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } if securityContext.RunAsUser != nil { for _, msg := range validation.IsValidUserID(*securityContext.RunAsUser) { allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), msg)) } + // When user namespaces are enabled (hostUsers=false), UIDs must be in range 0-65535 + if !hostUsers && *securityContext.RunAsUser > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *securityContext.RunAsUser, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } if securityContext.RunAsGroup != nil { for _, msg := range validation.IsValidGroupID(*securityContext.RunAsGroup) { allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *(securityContext.RunAsGroup), msg)) } + // When user namespaces are enabled (hostUsers=false), GIDs must be in range 0-65535 + if !hostUsers && *securityContext.RunAsGroup > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *securityContext.RunAsGroup, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } for g, gid := range securityContext.SupplementalGroups { for _, msg := range validation.IsValidGroupID(gid) { allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, msg)) } + // When user namespaces are enabled (hostUsers=false), GIDs must be in range 0-65535 + if !hostUsers && gid > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("supplementalGroups").Index(g), gid, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } if securityContext.ShareProcessNamespace != nil && securityContext.HostPID && *securityContext.ShareProcessNamespace { allErrs = append(allErrs, field.Invalid(fldPath.Child("shareProcessNamespace"), *securityContext.ShareProcessNamespace, "ShareProcessNamespace and HostPID cannot both be enabled")) @@ -8072,12 +8088,20 @@ func ValidateSecurityContext(sc *core.SecurityContext, fldPath *field.Path, host for _, msg := range validation.IsValidUserID(*sc.RunAsUser) { allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, msg)) } + // When user namespaces are enabled (hostUsers=false), UIDs must be in range 0-65535 + if !hostUsers && *sc.RunAsUser > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *sc.RunAsUser, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } if sc.RunAsGroup != nil { for _, msg := range validation.IsValidGroupID(*sc.RunAsGroup) { allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *sc.RunAsGroup, msg)) } + // When user namespaces are enabled (hostUsers=false), GIDs must be in range 0-65535 + if !hostUsers && *sc.RunAsGroup > 65534 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsGroup"), *sc.RunAsGroup, "must be between 0 and 65535 when user namespaces are enabled (hostUsers=false)")) + } } if sc.ProcMount != nil { diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 15aed1b0a5610..d398ea5496e3d 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -23086,17 +23086,35 @@ func TestValidateSecurityContext(t *testing.T) { procMountUnmasked := fullValidSC() procMountUnmasked.ProcMount = &umPmt + // Test user namespace limits - valid cases + validUserNsUser := fullValidSC() + validUID := int64(65534) // Max valid UID for user namespaces + validUserNsUser.RunAsUser = &validUID + + validUserNsGroup := fullValidSC() + validGID := int64(65534) // Max valid GID for user namespaces + validUserNsGroup.RunAsGroup = &validGID + + beyondLimitWithHostUsers := fullValidSC() + highUID := int64(100000) + highGID := int64(100000) + beyondLimitWithHostUsers.RunAsUser = &highUID + beyondLimitWithHostUsers.RunAsGroup = &highGID + successCases := map[string]struct { sc *core.SecurityContext hostUsers bool }{ - "all settings": {allSettings, false}, - "no capabilities": {noCaps, false}, - "no selinux": {noSELinux, false}, - "no priv request": {noPrivRequest, false}, - "no run as user": {noRunAsUser, false}, - "proc mount set": {procMountSet, true}, - "proc mount unmasked": {procMountUnmasked, false}, + "all settings": {allSettings, false}, + "no capabilities": {noCaps, false}, + "no selinux": {noSELinux, false}, + "no priv request": {noPrivRequest, false}, + "no run as user": {noRunAsUser, false}, + "proc mount set": {procMountSet, true}, + "proc mount unmasked": {procMountUnmasked, false}, + "valid user namespace uid at boundary": {validUserNsUser, false}, + "valid user namespace gid at boundary": {validUserNsGroup, false}, + "high uid/gid with hostUsers true": {beyondLimitWithHostUsers, true}, } for k, v := range successCases { if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) != 0 { @@ -23119,37 +23137,84 @@ func TestValidateSecurityContext(t *testing.T) { capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"} capSysAdminWithoutEscalation.AllowPrivilegeEscalation = ptr.To(false) + // Test user namespace limits - error cases + invalidUserNsUserBoundary := fullValidSC() + invalidUID := int64(65535) // One above max valid UID for user namespaces + invalidUserNsUserBoundary.RunAsUser = &invalidUID + + invalidUserNsUserHigh := fullValidSC() + veryHighUID := int64(100000) // Way above limit + invalidUserNsUserHigh.RunAsUser = &veryHighUID + + invalidUserNsGroupBoundary := fullValidSC() + invalidGID := int64(65535) // One above max valid GID for user namespaces + invalidUserNsGroupBoundary.RunAsGroup = &invalidGID + + invalidUserNsGroupHigh := fullValidSC() + veryHighGID := int64(100000) // Way above limit + invalidUserNsGroupHigh.RunAsGroup = &veryHighGID + errorCases := map[string]struct { sc *core.SecurityContext errorType field.ErrorType errorDetail string capAllowPriv bool + hostUsers bool }{ "request privileged when capabilities forbids": { sc: privRequestWithGlobalDeny, errorType: "FieldValueForbidden", errorDetail: "disallowed by cluster policy", + hostUsers: true, }, "negative RunAsUser": { sc: negativeRunAsUser, errorType: "FieldValueInvalid", errorDetail: "must be between", + hostUsers: true, }, "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": { sc: capSysAdminWithoutEscalation, errorType: "FieldValueInvalid", errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN", + hostUsers: true, }, "with privileged and allowPrivilegeEscalation false": { sc: privWithoutEscalation, errorType: "FieldValueInvalid", errorDetail: "cannot set `allowPrivilegeEscalation` to false and `privileged` to true", capAllowPriv: true, + hostUsers: true, }, "with unmasked proc mount type and no user namespace": { sc: procMountUnmasked, errorType: "FieldValueInvalid", errorDetail: "`hostUsers` must be false to use `Unmasked`", + hostUsers: true, + }, + "runAsUser exceeds user namespace boundary": { + sc: invalidUserNsUserBoundary, + errorType: "FieldValueInvalid", + errorDetail: "must be between 0 and 65535 when user namespaces are enabled", + hostUsers: false, + }, + "runAsUser exceeds user namespace limit": { + sc: invalidUserNsUserHigh, + errorType: "FieldValueInvalid", + errorDetail: "must be between 0 and 65535 when user namespaces are enabled", + hostUsers: false, + }, + "runAsGroup exceeds user namespace boundary": { + sc: invalidUserNsGroupBoundary, + errorType: "FieldValueInvalid", + errorDetail: "must be between 0 and 65535 when user namespaces are enabled", + hostUsers: false, + }, + "runAsGroup exceeds user namespace limit": { + sc: invalidUserNsGroupHigh, + errorType: "FieldValueInvalid", + errorDetail: "must be between 0 and 65535 when user namespaces are enabled", + hostUsers: false, }, } for k, v := range errorCases { @@ -23157,14 +23222,152 @@ func TestValidateSecurityContext(t *testing.T) { capabilities.Initialize(capabilities.Capabilities{ AllowPrivileged: v.capAllowPriv, }) - // note the unconditional `true` here for hostUsers. The failure case to test for ProcMount only includes it being true, - // and the field is ignored if ProcMount isn't set. Thus, we can unconditionally set to `true` and simplify the test matrix setup. - if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), true); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { + if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) { t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs) } } } +func TestValidatePodSecurityContextUserNamespaceLimits(t *testing.T) { + validUID := int64(65534) + invalidUID := int64(65535) + veryHighUID := int64(100000) + validGID := int64(65534) + invalidGID := int64(65535) + veryHighGID := int64(100000) + + successCases := []struct { + name string + sc *core.PodSecurityContext + hostUsers bool + }{ + { + name: "valid fsGroup at boundary with hostUsers false", + sc: &core.PodSecurityContext{ + FSGroup: &validGID, + }, + hostUsers: false, + }, + { + name: "valid runAsUser at boundary with hostUsers false", + sc: &core.PodSecurityContext{ + RunAsUser: &validUID, + }, + hostUsers: false, + }, + { + name: "valid runAsGroup at boundary with hostUsers false", + sc: &core.PodSecurityContext{ + RunAsGroup: &validGID, + }, + hostUsers: false, + }, + { + name: "valid supplementalGroups at boundary with hostUsers false", + sc: &core.PodSecurityContext{ + SupplementalGroups: []int64{0, 1000, validGID}, + }, + hostUsers: false, + }, + { + name: "high values allowed with hostUsers true", + sc: &core.PodSecurityContext{ + FSGroup: &veryHighGID, + RunAsUser: &veryHighUID, + RunAsGroup: &veryHighGID, + SupplementalGroups: []int64{veryHighGID}, + }, + hostUsers: true, + }, + } + + for _, tc := range successCases { + t.Run(tc.name, func(t *testing.T) { + spec := &core.PodSpec{ + SecurityContext: tc.sc, + RestartPolicy: core.RestartPolicyAlways, + Containers: []core.Container{{Name: "test", Image: "test"}}, + } + errs := validatePodSpecSecurityContext(tc.sc, spec, field.NewPath("spec"), field.NewPath("spec", "securityContext"), PodValidationOptions{}, tc.hostUsers) + if len(errs) != 0 { + t.Errorf("Expected success, got %v", errs) + } + }) + } + + errorCases := []struct { + name string + sc *core.PodSecurityContext + hostUsers bool + expectedErr string + }{ + { + name: "fsGroup exceeds limit with hostUsers false", + sc: &core.PodSecurityContext{ + FSGroup: &invalidGID, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + { + name: "runAsUser exceeds limit with hostUsers false", + sc: &core.PodSecurityContext{ + RunAsUser: &invalidUID, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + { + name: "runAsGroup exceeds limit with hostUsers false", + sc: &core.PodSecurityContext{ + RunAsGroup: &invalidGID, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + { + name: "supplementalGroups exceeds limit with hostUsers false", + sc: &core.PodSecurityContext{ + SupplementalGroups: []int64{1000, invalidGID, 2000}, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + { + name: "very high fsGroup with hostUsers false", + sc: &core.PodSecurityContext{ + FSGroup: &veryHighGID, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + { + name: "very high runAsUser with hostUsers false", + sc: &core.PodSecurityContext{ + RunAsUser: &veryHighUID, + }, + hostUsers: false, + expectedErr: "must be between 0 and 65535 when user namespaces are enabled", + }, + } + + for _, tc := range errorCases { + t.Run(tc.name, func(t *testing.T) { + spec := &core.PodSpec{ + SecurityContext: tc.sc, + RestartPolicy: core.RestartPolicyAlways, + Containers: []core.Container{{Name: "test", Image: "test"}}, + } + errs := validatePodSpecSecurityContext(tc.sc, spec, field.NewPath("spec"), field.NewPath("spec", "securityContext"), PodValidationOptions{}, tc.hostUsers) + if len(errs) == 0 { + t.Errorf("Expected error, got none") + } else if !strings.Contains(errs[0].Error(), tc.expectedErr) { + t.Errorf("Expected error containing %q, got %v", tc.expectedErr, errs[0]) + } + }) + } +} + func fakeValidSecurityContext(priv bool) *core.SecurityContext { return &core.SecurityContext{ Privileged: &priv, From 1e8ff724ad434182d4c0fa6ec5fddbb55dea1646 Mon Sep 17 00:00:00 2001 From: Rohit Patil Date: Wed, 12 Nov 2025 23:08:08 +0530 Subject: [PATCH 2/4] UPSTREAM: 1234: Fix user namespace validation for runAsGroup, fsGroup, and supplementalGroups2 --- .../defaulting_scc_test.go | 3 ++ .../securitycontextconstraints/defaults.go | 5 +- .../validation/validation.go | 17 +++++++ .../openshift/api/security/v1/types.go | 25 ++++++++++ .../sccmatching/provider.go | 50 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go index 90c0f0de3b00c..4fd327939ed85 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go @@ -81,6 +81,9 @@ func TestDefaultingHappens(t *testing.T) { "priority": null, "readOnlyRootFilesystem": false, "requiredDropCapabilities": null, + "runAsGroup": { + "type": "RunAsAny" + }, "runAsUser": { "type": "RunAsAny" }, diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go index e6e4b5ff44fc7..c0029fa09aa3c 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go @@ -7,7 +7,7 @@ import ( sccutil "github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/util" ) -// Default SCCs for new fields. FSGroup and SupplementalGroups are +// Default SCCs for new fields. FSGroup, SupplementalGroups, and RunAsGroup are // set to the RunAsAny strategy if they are unset on the scc. func SetDefaults_SCC(scc *securityv1.SecurityContextConstraints) { if len(scc.FSGroup.Type) == 0 { @@ -16,6 +16,9 @@ func SetDefaults_SCC(scc *securityv1.SecurityContextConstraints) { if len(scc.SupplementalGroups.Type) == 0 { scc.SupplementalGroups.Type = securityv1.SupplementalGroupsStrategyRunAsAny } + if len(scc.RunAsGroup.Type) == 0 { + scc.RunAsGroup.Type = securityv1.RunAsGroupStrategyRunAsAny + } if scc.Users == nil { scc.Users = []string{} diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go index 493339867b8c5..727a75efc7c5d 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go @@ -70,6 +70,23 @@ func ValidateSecurityContextConstraints(scc *securityv1.SecurityContextConstrain } allErrs = append(allErrs, validateIDRanges(scc.SupplementalGroups.Ranges, field.NewPath("supplementalGroups"))...) + // ensure the runAsGroup strategy has a valid type + if len(scc.RunAsGroup.Type) > 0 { + if scc.RunAsGroup.Type != securityv1.RunAsGroupStrategyMustRunAs && + scc.RunAsGroup.Type != securityv1.RunAsGroupStrategyRunAsAny { + allErrs = append(allErrs, field.NotSupported(field.NewPath("runAsGroup", "type"), scc.RunAsGroup.Type, + []string{string(securityv1.RunAsGroupStrategyMustRunAs), string(securityv1.RunAsGroupStrategyRunAsAny)})) + } + allErrs = append(allErrs, validateIDRanges(scc.RunAsGroup.Ranges, field.NewPath("runAsGroup"))...) + + // if specified, gid cannot be negative + if scc.RunAsGroup.GID != nil { + if *scc.RunAsGroup.GID < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("runAsGroup").Child("gid"), *scc.RunAsGroup.GID, "gid cannot be negative")) + } + } + } + // validate capabilities allErrs = append(allErrs, validateSCCCapsAgainstDrops(scc.RequiredDropCapabilities, scc.DefaultAddCapabilities, field.NewPath("defaultAddCapabilities"))...) allErrs = append(allErrs, validateSCCCapsAgainstDrops(scc.RequiredDropCapabilities, scc.AllowedCapabilities, field.NewPath("allowedCapabilities"))...) diff --git a/vendor/github.com/openshift/api/security/v1/types.go b/vendor/github.com/openshift/api/security/v1/types.go index fb491480d7629..f0be520da1d5a 100644 --- a/vendor/github.com/openshift/api/security/v1/types.go +++ b/vendor/github.com/openshift/api/security/v1/types.go @@ -125,6 +125,10 @@ type SecurityContextConstraints struct { // runAsUser is the strategy that will dictate what RunAsUser is used in the SecurityContext. // +nullable RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty" protobuf:"bytes,14,opt,name=runAsUser"` + // runAsGroup is the strategy that will dictate what RunAsGroup is used in the SecurityContext. + // +nullable + // +optional + RunAsGroup RunAsGroupStrategyOptions `json:"runAsGroup,omitempty" protobuf:"bytes,27,opt,name=runAsGroup"` // supplementalGroups is the strategy that will dictate what supplemental groups are used by the SecurityContext. // +nullable SupplementalGroups SupplementalGroupsStrategyOptions `json:"supplementalGroups,omitempty" protobuf:"bytes,15,opt,name=supplementalGroups"` @@ -268,6 +272,18 @@ type SupplementalGroupsStrategyOptions struct { Ranges []IDRange `json:"ranges,omitempty" protobuf:"bytes,2,rep,name=ranges"` } +// RunAsGroupStrategyOptions defines the strategy type and any options used to create the strategy. +type RunAsGroupStrategyOptions struct { + // type is the strategy that will dictate what RunAsGroup is used in the SecurityContext. + Type RunAsGroupStrategyType `json:"type,omitempty" protobuf:"bytes,1,opt,name=type,casttype=RunAsGroupStrategyType"` + // gid is the group id that containers must run as. Required for the MustRunAs strategy if not using + // namespace/service account allocated gids. + GID *int64 `json:"gid,omitempty" protobuf:"varint,2,opt,name=gid"` + // ranges are the allowed ranges of gids that may be used. + // +listType=atomic + Ranges []IDRange `json:"ranges,omitempty" protobuf:"bytes,3,rep,name=ranges"` +} + // IDRange provides a min/max of an allowed range of IDs. // TODO: this could be reused for UIDs. type IDRange struct { @@ -296,6 +312,10 @@ type SupplementalGroupsStrategyType string // SecurityContext type FSGroupStrategyType string +// RunAsGroupStrategyType denotes strategy types for generating RunAsGroup values for a +// SecurityContext +type RunAsGroupStrategyType string + const ( // NamespaceLevelAllowHost allows a pod to set `hostUsers` field to either `true` or `false` NamespaceLevelAllowHost NamespaceLevelType = "AllowHostLevel" @@ -325,6 +345,11 @@ const ( SupplementalGroupsStrategyMustRunAs SupplementalGroupsStrategyType = "MustRunAs" // container may make requests for any gid. SupplementalGroupsStrategyRunAsAny SupplementalGroupsStrategyType = "RunAsAny" + + // container must run as a particular gid. + RunAsGroupStrategyMustRunAs RunAsGroupStrategyType = "MustRunAs" + // container may make requests for any gid. + RunAsGroupStrategyRunAsAny RunAsGroupStrategyType = "RunAsAny" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go index 6acd3913d2c2d..2689fb4ddbfa0 100644 --- a/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go +++ b/vendor/github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/sccmatching/provider.go @@ -26,6 +26,7 @@ import ( const ( fsGroupField = "fsGroup" supplementalGroupsField = "supplementalGroups" + runAsGroupField = "runAsGroup" ) // simpleProvider is the default implementation of SecurityContextConstraintsProvider @@ -35,6 +36,7 @@ type simpleProvider struct { seLinuxStrategy selinux.SELinuxSecurityContextConstraintsStrategy fsGroupStrategy group.GroupSecurityContextConstraintsStrategy supplementalGroupStrategy group.GroupSecurityContextConstraintsStrategy + runAsGroupStrategy group.GroupSecurityContextConstraintsStrategy capabilitiesStrategy capabilities.CapabilitiesSecurityContextConstraintsStrategy seccompStrategy seccomp.SeccompStrategy sysctlsStrategy sysctl.SysctlsStrategy @@ -69,6 +71,11 @@ func NewSimpleProvider(scc *securityv1.SecurityContextConstraints) (SecurityCont return nil, err } + runAsGroupStrat, err := createRunAsGroupStrategy(&scc.RunAsGroup) + if err != nil { + return nil, err + } + capStrat, err := createCapabilitiesStrategy(scc.DefaultAddCapabilities, scc.RequiredDropCapabilities, scc.AllowedCapabilities) if err != nil { return nil, err @@ -90,6 +97,7 @@ func NewSimpleProvider(scc *securityv1.SecurityContextConstraints) (SecurityCont seLinuxStrategy: seLinuxStrat, fsGroupStrategy: fsGroupStrat, supplementalGroupStrategy: supGroupStrat, + runAsGroupStrategy: runAsGroupStrat, capabilitiesStrategy: capStrat, seccompStrategy: seccompStrat, sysctlsStrategy: sysctlsStrat, @@ -120,6 +128,14 @@ func (s *simpleProvider) CreatePodSecurityContext(pod *api.Pod) (*api.PodSecurit sc.SetFSGroup(fsGroup) } + if sc.RunAsGroup() == nil { + runAsGroup, err := s.runAsGroupStrategy.GenerateSingle(pod) + if err != nil { + return nil, nil, err + } + sc.SetRunAsGroup(runAsGroup) + } + if sc.SELinuxOptions() == nil { seLinux, err := s.seLinuxStrategy.Generate(pod, nil) if err != nil { @@ -161,6 +177,14 @@ func (s *simpleProvider) CreateContainerSecurityContext(pod *api.Pod, container sc.SetRunAsUser(uid) } + if sc.RunAsGroup() == nil { + gid, err := s.runAsGroupStrategy.GenerateSingle(pod) + if err != nil { + return nil, err + } + sc.SetRunAsGroup(gid) + } + if sc.SELinuxOptions() == nil { seLinux, err := s.seLinuxStrategy.Generate(pod, container) if err != nil { @@ -265,6 +289,13 @@ func (s *simpleProvider) ValidatePodSecurityContext(pod *api.Pod, fldPath *field } allErrs = append(allErrs, s.fsGroupStrategy.Validate(fldPath, pod, fsGroups)...) allErrs = append(allErrs, s.supplementalGroupStrategy.Validate(fldPath, pod, sc.SupplementalGroups())...) + + runAsGroups := []int64{} + if runAsGroup := sc.RunAsGroup(); runAsGroup != nil { + runAsGroups = append(runAsGroups, *runAsGroup) + } + allErrs = append(allErrs, s.runAsGroupStrategy.Validate(fldPath, pod, runAsGroups)...) + allErrs = append(allErrs, s.seccompStrategy.ValidatePod(pod)...) allErrs = append(allErrs, s.seLinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...) @@ -337,6 +368,13 @@ func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, containe sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext)) allErrs = append(allErrs, s.runAsUserStrategy.Validate(fldPath, pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...) + + runAsGroups := []int64{} + if runAsGroup := sc.RunAsGroup(); runAsGroup != nil { + runAsGroups = append(runAsGroups, *runAsGroup) + } + allErrs = append(allErrs, s.runAsGroupStrategy.Validate(fldPath, pod, runAsGroups)...) + allErrs = append(allErrs, s.seLinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...) allErrs = append(allErrs, s.seccompStrategy.ValidateContainer(pod, container)...) @@ -469,6 +507,18 @@ func createSupplementalGroupStrategy(opts *securityv1.SupplementalGroupsStrategy } } +// createRunAsGroupStrategy creates a new runAsGroup strategy +func createRunAsGroupStrategy(opts *securityv1.RunAsGroupStrategyOptions) (group.GroupSecurityContextConstraintsStrategy, error) { + switch opts.Type { + case securityv1.RunAsGroupStrategyRunAsAny: + return group.NewRunAsAny() + case securityv1.RunAsGroupStrategyMustRunAs: + return group.NewMustRunAs(opts.Ranges, runAsGroupField) + default: + return nil, fmt.Errorf("Unrecognized RunAsGroup strategy type %s", opts.Type) + } +} + // createCapabilitiesStrategy creates a new capabilities strategy. func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []corev1.Capability) (capabilities.CapabilitiesSecurityContextConstraintsStrategy, error) { return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps) From 521c397ba61a115e2ae3f7176876964b648e8cda Mon Sep 17 00:00:00 2001 From: Rohit Patil Date: Thu, 13 Nov 2025 17:22:38 +0530 Subject: [PATCH 3/4] change default --- .../securitycontextconstraints/defaulting_scc_test.go | 8 +++++++- .../securitycontextconstraints/defaults.go | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go index 4fd327939ed85..a86763e0c761b 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaulting_scc_test.go @@ -82,7 +82,13 @@ func TestDefaultingHappens(t *testing.T) { "readOnlyRootFilesystem": false, "requiredDropCapabilities": null, "runAsGroup": { - "type": "RunAsAny" + "ranges": [ + { + "max": 65534, + "min": 1000 + } + ], + "type": "MustRunAs" }, "runAsUser": { "type": "RunAsAny" diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go index c0029fa09aa3c..db6bfa51dac41 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/defaults.go @@ -7,8 +7,9 @@ import ( sccutil "github.com/openshift/apiserver-library-go/pkg/securitycontextconstraints/util" ) -// Default SCCs for new fields. FSGroup, SupplementalGroups, and RunAsGroup are +// Default SCCs for new fields. FSGroup and SupplementalGroups are // set to the RunAsAny strategy if they are unset on the scc. +// RunAsGroup is set to the MustRunAs strategy with ranges [1000, 65534] if unset. func SetDefaults_SCC(scc *securityv1.SecurityContextConstraints) { if len(scc.FSGroup.Type) == 0 { scc.FSGroup.Type = securityv1.FSGroupStrategyRunAsAny @@ -17,7 +18,13 @@ func SetDefaults_SCC(scc *securityv1.SecurityContextConstraints) { scc.SupplementalGroups.Type = securityv1.SupplementalGroupsStrategyRunAsAny } if len(scc.RunAsGroup.Type) == 0 { - scc.RunAsGroup.Type = securityv1.RunAsGroupStrategyRunAsAny + scc.RunAsGroup.Type = securityv1.RunAsGroupStrategyMustRunAs + scc.RunAsGroup.Ranges = []securityv1.IDRange{ + { + Min: 1000, + Max: 65534, + }, + } } if scc.Users == nil { From 8b27caf7e4efcb7596bf6df98360e51f1c06c392 Mon Sep 17 00:00:00 2001 From: Rohit Patil Date: Thu, 13 Nov 2025 17:46:54 +0530 Subject: [PATCH 4/4] add mustrunasrange --- .../securitycontextconstraints/validation/validation.go | 3 ++- vendor/github.com/openshift/api/security/v1/types.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go index 727a75efc7c5d..a61a6e647efbf 100644 --- a/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go +++ b/openshift-kube-apiserver/admission/customresourcevalidation/securitycontextconstraints/validation/validation.go @@ -73,9 +73,10 @@ func ValidateSecurityContextConstraints(scc *securityv1.SecurityContextConstrain // ensure the runAsGroup strategy has a valid type if len(scc.RunAsGroup.Type) > 0 { if scc.RunAsGroup.Type != securityv1.RunAsGroupStrategyMustRunAs && + scc.RunAsGroup.Type != securityv1.RunAsGroupStrategyMustRunAsRange && scc.RunAsGroup.Type != securityv1.RunAsGroupStrategyRunAsAny { allErrs = append(allErrs, field.NotSupported(field.NewPath("runAsGroup", "type"), scc.RunAsGroup.Type, - []string{string(securityv1.RunAsGroupStrategyMustRunAs), string(securityv1.RunAsGroupStrategyRunAsAny)})) + []string{string(securityv1.RunAsGroupStrategyMustRunAs), string(securityv1.RunAsGroupStrategyMustRunAsRange), string(securityv1.RunAsGroupStrategyRunAsAny)})) } allErrs = append(allErrs, validateIDRanges(scc.RunAsGroup.Ranges, field.NewPath("runAsGroup"))...) diff --git a/vendor/github.com/openshift/api/security/v1/types.go b/vendor/github.com/openshift/api/security/v1/types.go index f0be520da1d5a..67a8fea03a6e7 100644 --- a/vendor/github.com/openshift/api/security/v1/types.go +++ b/vendor/github.com/openshift/api/security/v1/types.go @@ -348,6 +348,8 @@ const ( // container must run as a particular gid. RunAsGroupStrategyMustRunAs RunAsGroupStrategyType = "MustRunAs" + // container must run with a gid in a range. + RunAsGroupStrategyMustRunAsRange RunAsGroupStrategyType = "MustRunAsRange" // container may make requests for any gid. RunAsGroupStrategyRunAsAny RunAsGroupStrategyType = "RunAsAny" )