@@ -23086,17 +23086,35 @@ func TestValidateSecurityContext(t *testing.T) {
2308623086 procMountUnmasked := fullValidSC()
2308723087 procMountUnmasked.ProcMount = &umPmt
2308823088
23089+ // Test user namespace limits - valid cases
23090+ validUserNsUser := fullValidSC()
23091+ validUID := int64(65534) // Max valid UID for user namespaces
23092+ validUserNsUser.RunAsUser = &validUID
23093+
23094+ validUserNsGroup := fullValidSC()
23095+ validGID := int64(65534) // Max valid GID for user namespaces
23096+ validUserNsGroup.RunAsGroup = &validGID
23097+
23098+ beyondLimitWithHostUsers := fullValidSC()
23099+ highUID := int64(100000)
23100+ highGID := int64(100000)
23101+ beyondLimitWithHostUsers.RunAsUser = &highUID
23102+ beyondLimitWithHostUsers.RunAsGroup = &highGID
23103+
2308923104 successCases := map[string]struct {
2309023105 sc *core.SecurityContext
2309123106 hostUsers bool
2309223107 }{
23093- "all settings": {allSettings, false},
23094- "no capabilities": {noCaps, false},
23095- "no selinux": {noSELinux, false},
23096- "no priv request": {noPrivRequest, false},
23097- "no run as user": {noRunAsUser, false},
23098- "proc mount set": {procMountSet, true},
23099- "proc mount unmasked": {procMountUnmasked, false},
23108+ "all settings": {allSettings, false},
23109+ "no capabilities": {noCaps, false},
23110+ "no selinux": {noSELinux, false},
23111+ "no priv request": {noPrivRequest, false},
23112+ "no run as user": {noRunAsUser, false},
23113+ "proc mount set": {procMountSet, true},
23114+ "proc mount unmasked": {procMountUnmasked, false},
23115+ "valid user namespace uid at boundary": {validUserNsUser, false},
23116+ "valid user namespace gid at boundary": {validUserNsGroup, false},
23117+ "high uid/gid with hostUsers true": {beyondLimitWithHostUsers, true},
2310023118 }
2310123119 for k, v := range successCases {
2310223120 if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), v.hostUsers); len(errs) != 0 {
@@ -23119,52 +23137,237 @@ func TestValidateSecurityContext(t *testing.T) {
2311923137 capSysAdminWithoutEscalation.Capabilities.Add = []core.Capability{"CAP_SYS_ADMIN"}
2312023138 capSysAdminWithoutEscalation.AllowPrivilegeEscalation = ptr.To(false)
2312123139
23140+ // Test user namespace limits - error cases
23141+ invalidUserNsUserBoundary := fullValidSC()
23142+ invalidUID := int64(65535) // One above max valid UID for user namespaces
23143+ invalidUserNsUserBoundary.RunAsUser = &invalidUID
23144+
23145+ invalidUserNsUserHigh := fullValidSC()
23146+ veryHighUID := int64(100000) // Way above limit
23147+ invalidUserNsUserHigh.RunAsUser = &veryHighUID
23148+
23149+ invalidUserNsGroupBoundary := fullValidSC()
23150+ invalidGID := int64(65535) // One above max valid GID for user namespaces
23151+ invalidUserNsGroupBoundary.RunAsGroup = &invalidGID
23152+
23153+ invalidUserNsGroupHigh := fullValidSC()
23154+ veryHighGID := int64(100000) // Way above limit
23155+ invalidUserNsGroupHigh.RunAsGroup = &veryHighGID
23156+
2312223157 errorCases := map[string]struct {
2312323158 sc *core.SecurityContext
2312423159 errorType field.ErrorType
2312523160 errorDetail string
2312623161 capAllowPriv bool
23162+ hostUsers bool
2312723163 }{
2312823164 "request privileged when capabilities forbids": {
2312923165 sc: privRequestWithGlobalDeny,
2313023166 errorType: "FieldValueForbidden",
2313123167 errorDetail: "disallowed by cluster policy",
23168+ hostUsers: true,
2313223169 },
2313323170 "negative RunAsUser": {
2313423171 sc: negativeRunAsUser,
2313523172 errorType: "FieldValueInvalid",
2313623173 errorDetail: "must be between",
23174+ hostUsers: true,
2313723175 },
2313823176 "with CAP_SYS_ADMIN and allowPrivilegeEscalation false": {
2313923177 sc: capSysAdminWithoutEscalation,
2314023178 errorType: "FieldValueInvalid",
2314123179 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `capabilities.Add` CAP_SYS_ADMIN",
23180+ hostUsers: true,
2314223181 },
2314323182 "with privileged and allowPrivilegeEscalation false": {
2314423183 sc: privWithoutEscalation,
2314523184 errorType: "FieldValueInvalid",
2314623185 errorDetail: "cannot set `allowPrivilegeEscalation` to false and `privileged` to true",
2314723186 capAllowPriv: true,
23187+ hostUsers: true,
2314823188 },
2314923189 "with unmasked proc mount type and no user namespace": {
2315023190 sc: procMountUnmasked,
2315123191 errorType: "FieldValueInvalid",
2315223192 errorDetail: "`hostUsers` must be false to use `Unmasked`",
23193+ hostUsers: true,
23194+ },
23195+ "runAsUser exceeds user namespace boundary": {
23196+ sc: invalidUserNsUserBoundary,
23197+ errorType: "FieldValueInvalid",
23198+ errorDetail: "must be between 0 and 65534 when user namespaces are enabled",
23199+ hostUsers: false,
23200+ },
23201+ "runAsUser exceeds user namespace limit": {
23202+ sc: invalidUserNsUserHigh,
23203+ errorType: "FieldValueInvalid",
23204+ errorDetail: "must be between 0 and 65534 when user namespaces are enabled",
23205+ hostUsers: false,
23206+ },
23207+ "runAsGroup exceeds user namespace boundary": {
23208+ sc: invalidUserNsGroupBoundary,
23209+ errorType: "FieldValueInvalid",
23210+ errorDetail: "must be between 0 and 65534 when user namespaces are enabled",
23211+ hostUsers: false,
23212+ },
23213+ "runAsGroup exceeds user namespace limit": {
23214+ sc: invalidUserNsGroupHigh,
23215+ errorType: "FieldValueInvalid",
23216+ errorDetail: "must be between 0 and 65534 when user namespaces are enabled",
23217+ hostUsers: false,
2315323218 },
2315423219 }
2315523220 for k, v := range errorCases {
2315623221 capabilities.ResetForTest()
2315723222 capabilities.Initialize(capabilities.Capabilities{
2315823223 AllowPrivileged: v.capAllowPriv,
2315923224 })
23160- // note the unconditional `true` here for hostUsers. The failure case to test for ProcMount only includes it being true,
23161- // and the field is ignored if ProcMount isn't set. Thus, we can unconditionally set to `true` and simplify the test matrix setup.
23162- if errs := ValidateSecurityContext(v.sc, field.NewPath("field"), true); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
23225+ 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) {
2316323226 t.Errorf("[%s] Expected error type %q with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
2316423227 }
2316523228 }
2316623229}
2316723230
23231+ func TestValidatePodSecurityContextUserNamespaceLimits(t *testing.T) {
23232+ validUID := int64(65534)
23233+ invalidUID := int64(65535)
23234+ veryHighUID := int64(100000)
23235+ validGID := int64(65534)
23236+ invalidGID := int64(65535)
23237+ veryHighGID := int64(100000)
23238+
23239+ successCases := []struct {
23240+ name string
23241+ sc *core.PodSecurityContext
23242+ hostUsers bool
23243+ }{
23244+ {
23245+ name: "valid fsGroup at boundary with hostUsers false",
23246+ sc: &core.PodSecurityContext{
23247+ FSGroup: &validGID,
23248+ },
23249+ hostUsers: false,
23250+ },
23251+ {
23252+ name: "valid runAsUser at boundary with hostUsers false",
23253+ sc: &core.PodSecurityContext{
23254+ RunAsUser: &validUID,
23255+ },
23256+ hostUsers: false,
23257+ },
23258+ {
23259+ name: "valid runAsGroup at boundary with hostUsers false",
23260+ sc: &core.PodSecurityContext{
23261+ RunAsGroup: &validGID,
23262+ },
23263+ hostUsers: false,
23264+ },
23265+ {
23266+ name: "valid supplementalGroups at boundary with hostUsers false",
23267+ sc: &core.PodSecurityContext{
23268+ SupplementalGroups: []int64{0, 1000, validGID},
23269+ },
23270+ hostUsers: false,
23271+ },
23272+ {
23273+ name: "high values allowed with hostUsers true",
23274+ sc: &core.PodSecurityContext{
23275+ FSGroup: &veryHighGID,
23276+ RunAsUser: &veryHighUID,
23277+ RunAsGroup: &veryHighGID,
23278+ SupplementalGroups: []int64{veryHighGID},
23279+ },
23280+ hostUsers: true,
23281+ },
23282+ }
23283+
23284+ for _, tc := range successCases {
23285+ t.Run(tc.name, func(t *testing.T) {
23286+ spec := &core.PodSpec{
23287+ SecurityContext: tc.sc,
23288+ RestartPolicy: core.RestartPolicyAlways,
23289+ Containers: []core.Container{{Name: "test", Image: "test"}},
23290+ }
23291+ errs := validatePodSpecSecurityContext(tc.sc, spec, field.NewPath("spec"), field.NewPath("spec", "securityContext"), PodValidationOptions{}, tc.hostUsers)
23292+ if len(errs) != 0 {
23293+ t.Errorf("Expected success, got %v", errs)
23294+ }
23295+ })
23296+ }
23297+
23298+ errorCases := []struct {
23299+ name string
23300+ sc *core.PodSecurityContext
23301+ hostUsers bool
23302+ expectedErr string
23303+ }{
23304+ {
23305+ name: "fsGroup exceeds limit with hostUsers false",
23306+ sc: &core.PodSecurityContext{
23307+ FSGroup: &invalidGID,
23308+ },
23309+ hostUsers: false,
23310+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23311+ },
23312+ {
23313+ name: "runAsUser exceeds limit with hostUsers false",
23314+ sc: &core.PodSecurityContext{
23315+ RunAsUser: &invalidUID,
23316+ },
23317+ hostUsers: false,
23318+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23319+ },
23320+ {
23321+ name: "runAsGroup exceeds limit with hostUsers false",
23322+ sc: &core.PodSecurityContext{
23323+ RunAsGroup: &invalidGID,
23324+ },
23325+ hostUsers: false,
23326+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23327+ },
23328+ {
23329+ name: "supplementalGroups exceeds limit with hostUsers false",
23330+ sc: &core.PodSecurityContext{
23331+ SupplementalGroups: []int64{1000, invalidGID, 2000},
23332+ },
23333+ hostUsers: false,
23334+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23335+ },
23336+ {
23337+ name: "very high fsGroup with hostUsers false",
23338+ sc: &core.PodSecurityContext{
23339+ FSGroup: &veryHighGID,
23340+ },
23341+ hostUsers: false,
23342+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23343+ },
23344+ {
23345+ name: "very high runAsUser with hostUsers false",
23346+ sc: &core.PodSecurityContext{
23347+ RunAsUser: &veryHighUID,
23348+ },
23349+ hostUsers: false,
23350+ expectedErr: "must be between 0 and 65534 when user namespaces are enabled",
23351+ },
23352+ }
23353+
23354+ for _, tc := range errorCases {
23355+ t.Run(tc.name, func(t *testing.T) {
23356+ spec := &core.PodSpec{
23357+ SecurityContext: tc.sc,
23358+ RestartPolicy: core.RestartPolicyAlways,
23359+ Containers: []core.Container{{Name: "test", Image: "test"}},
23360+ }
23361+ errs := validatePodSpecSecurityContext(tc.sc, spec, field.NewPath("spec"), field.NewPath("spec", "securityContext"), PodValidationOptions{}, tc.hostUsers)
23362+ if len(errs) == 0 {
23363+ t.Errorf("Expected error, got none")
23364+ } else if !strings.Contains(errs[0].Error(), tc.expectedErr) {
23365+ t.Errorf("Expected error containing %q, got %v", tc.expectedErr, errs[0])
23366+ }
23367+ })
23368+ }
23369+ }
23370+
2316823371func fakeValidSecurityContext(priv bool) *core.SecurityContext {
2316923372 return &core.SecurityContext{
2317023373 Privileged: &priv,
0 commit comments