Skip to content

Commit fb2a59c

Browse files
authored
Add update validation for User.UserName (#943)
* Add update validation for User.UserName * Fix unit test
1 parent db9a379 commit fb2a59c

File tree

4 files changed

+81
-6
lines changed

4 files changed

+81
-6
lines changed

docs.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,12 @@ When a user is updated or deleted, a check occurs to ensure that the user making
524524

525525
If the user making the request has the verb `manage-users` for the resource `users`, then it is allowed to bypass the check. Note that the wildcard `*` includes the `manage-users` verb.
526526

527+
#### Invalid Fields - Update
528+
529+
Users can update the following fields if they had not been set. But after getting initial values, the fields cannot be changed:
530+
531+
- UserName
532+
527533
## UserAttribute
528534

529535
### Validation Checks

pkg/resources/management.cattle.io/v3/users/User.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44

55
When a user is updated or deleted, a check occurs to ensure that the user making the request has permissions greater than or equal to the user being updated or deleted. To get the user's groups, the user's UserAttributes are checked. This is best effort, because UserAttributes are only updated when a User logs in, so it may not be perfectly up to date.
66

7-
If the user making the request has the verb `manage-users` for the resource `users`, then it is allowed to bypass the check. Note that the wildcard `*` includes the `manage-users` verb.
7+
If the user making the request has the verb `manage-users` for the resource `users`, then it is allowed to bypass the check. Note that the wildcard `*` includes the `manage-users` verb.
8+
9+
### Invalid Fields - Update
10+
11+
Users can update the following fields if they had not been set. But after getting initial values, the fields cannot be changed:
12+
13+
- UserName

pkg/resources/management.cattle.io/v3/users/validator.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
apierrors "k8s.io/apimachinery/pkg/api/errors"
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/runtime/schema"
17+
"k8s.io/apimachinery/pkg/util/validation/field"
1718
"k8s.io/apiserver/pkg/authentication/user"
1819
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
1920
"k8s.io/kubernetes/pkg/registry/rbac/validation"
@@ -81,26 +82,27 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
8182
if hasManageUsers {
8283
return &admissionv1.AdmissionResponse{Allowed: true}, nil
8384
}
84-
userObj, err := objectsv3.UserFromRequest(&request.AdmissionRequest)
85+
86+
oldUser, newUser, err := objectsv3.UserOldAndNewFromRequest(&request.AdmissionRequest)
8587
if err != nil {
8688
return nil, fmt.Errorf("failed to get current User from request: %w", err)
8789
}
8890

8991
// Need the UserAttribute to find the groups
90-
userAttribute, err := a.userAttributeCache.Get(userObj.Name)
92+
userAttribute, err := a.userAttributeCache.Get(oldUser.Name)
9193
if err != nil && !apierrors.IsNotFound(err) {
92-
return nil, fmt.Errorf("failed to get UserAttribute for %s: %w", userObj.Name, err)
94+
return nil, fmt.Errorf("failed to get UserAttribute for %s: %w", oldUser.Name, err)
9395
}
9496

9597
userInfo := &user.DefaultInfo{
96-
Name: userObj.Name,
98+
Name: oldUser.Name,
9799
Groups: getGroupsFromUserAttribute(userAttribute),
98100
}
99101

100102
// Get all rules for the user being modified
101103
rules, err := a.resolver.RulesFor(context.Background(), userInfo, "")
102104
if err != nil {
103-
return nil, fmt.Errorf("failed to get rules for user %v: %w", userObj, err)
105+
return nil, fmt.Errorf("failed to get rules for user %v: %w", oldUser, err)
104106
}
105107

106108
// Ensure that rules of the user being modified aren't greater than the rules of the user making the request
@@ -115,6 +117,14 @@ func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResp
115117
},
116118
}, nil
117119
}
120+
121+
fieldPath := field.NewPath("user")
122+
if request.Operation == admissionv1.Update {
123+
if err := validateUpdateFields(oldUser, newUser, fieldPath); err != nil {
124+
return admission.ResponseBadRequest(err.Error()), nil
125+
}
126+
}
127+
118128
return &admissionv1.AdmissionResponse{Allowed: true}, nil
119129
}
120130

@@ -133,3 +143,12 @@ func getGroupsFromUserAttribute(userAttribute *v3.UserAttribute) []string {
133143
}
134144
return result
135145
}
146+
147+
// validateUpdateFields
148+
func validateUpdateFields(oldUser, newUser *v3.User, fieldPath *field.Path) error {
149+
const reason = "field is immutable"
150+
if oldUser.Username != "" && oldUser.Username != newUser.Username {
151+
return field.Invalid(fieldPath.Child("username"), newUser.Username, reason)
152+
}
153+
return nil
154+
}

pkg/resources/management.cattle.io/v3/users/validator_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var (
3535
ObjectMeta: metav1.ObjectMeta{
3636
Name: defaultUserName,
3737
},
38+
Username: defaultUserName,
3839
}
3940
getPods = []rbacv1.PolicyRule{
4041
{
@@ -196,6 +197,49 @@ func Test_Admit(t *testing.T) {
196197
},
197198
allowed: false,
198199
},
200+
{
201+
name: "changing the username not allowed",
202+
oldUser: defaultUser.DeepCopy(),
203+
newUser: &v3.User{
204+
ObjectMeta: metav1.ObjectMeta{
205+
Name: defaultUserName,
206+
},
207+
Username: "new-username",
208+
},
209+
requestUserName: requesterUserName,
210+
resolverRulesFor: func(string) ([]rbacv1.PolicyRule, error) {
211+
return getPods, nil
212+
},
213+
allowed: false,
214+
},
215+
{
216+
name: "adding a new username allowed",
217+
oldUser: &v3.User{
218+
ObjectMeta: metav1.ObjectMeta{
219+
Name: defaultUserName,
220+
},
221+
},
222+
newUser: defaultUser.DeepCopy(),
223+
requestUserName: requesterUserName,
224+
resolverRulesFor: func(string) ([]rbacv1.PolicyRule, error) {
225+
return getPods, nil
226+
},
227+
allowed: true,
228+
},
229+
{
230+
name: "removing username not allowed",
231+
oldUser: defaultUser.DeepCopy(),
232+
newUser: &v3.User{
233+
ObjectMeta: metav1.ObjectMeta{
234+
Name: defaultUserName,
235+
},
236+
},
237+
requestUserName: requesterUserName,
238+
resolverRulesFor: func(string) ([]rbacv1.PolicyRule, error) {
239+
return getPods, nil
240+
},
241+
allowed: false,
242+
},
199243
}
200244
for _, tt := range tests {
201245
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)