Skip to content

Commit ebf29c1

Browse files
author
Rohit Patil
committed
UPSTREAM: 1234: Fix user namespace validation for runAsGroup, fsGroup, and supplementalGroups_2
1 parent 4b816f9 commit ebf29c1

File tree

1 file changed

+213
-10
lines changed

1 file changed

+213
-10
lines changed

pkg/apis/core/validation/validation_test.go

Lines changed: 213 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2316823371
func fakeValidSecurityContext(priv bool) *core.SecurityContext {
2316923372
return &core.SecurityContext{
2317023373
Privileged: &priv,

0 commit comments

Comments
 (0)