Skip to content

Commit 1b69d20

Browse files
authored
K8SPSMDB-1236 Check for custom users name uniqueness (#1813)
1 parent 7149757 commit 1b69d20

File tree

2 files changed

+125
-34
lines changed

2 files changed

+125
-34
lines changed

pkg/controller/perconaservermongodb/custom_users.go

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context
5151

5252
handleRoles(ctx, cr, mongoCli)
5353

54-
if len(cr.Spec.Users) == 0 {
55-
return nil
56-
}
57-
5854
err = handleUsers(ctx, cr, mongoCli, r.client)
5955
if err != nil {
6056
return errors.Wrap(err, "handle users")
@@ -66,39 +62,24 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context
6662
func handleUsers(ctx context.Context, cr *api.PerconaServerMongoDB, mongoCli mongo.Client, client client.Client) error {
6763
log := logf.FromContext(ctx)
6864

69-
sysUsersSecret := corev1.Secret{}
70-
err := client.Get(ctx,
71-
types.NamespacedName{
72-
Namespace: cr.Namespace,
73-
Name: api.InternalUserSecretName(cr),
74-
},
75-
&sysUsersSecret,
76-
)
77-
if err != nil && !k8serrors.IsNotFound(err) {
78-
return errors.Wrap(err, "get internal sys users secret")
65+
if len(cr.Spec.Users) == 0 {
66+
return nil
7967
}
8068

81-
sysUserNames := sysUserNames(sysUsersSecret)
69+
systemUserNames, err := fetchSystemUserNames(ctx, cr, client)
70+
if err != nil {
71+
return err
72+
}
8273

83-
for _, user := range cr.Spec.Users {
84-
if _, ok := sysUserNames[user.Name]; ok {
85-
log.Error(nil, "creating user with reserved user name is forbidden", "user", user.Name)
86-
continue
87-
}
74+
uniqueUserNames := make(map[string]struct{}, len(cr.Spec.Users))
8875

89-
if len(user.Roles) == 0 {
90-
log.Error(nil, "user must have at least one role", "user", user.Name)
76+
for _, user := range cr.Spec.Users {
77+
err := validateUser(&user, systemUserNames, uniqueUserNames)
78+
if err != nil {
79+
log.Error(err, "invalid user", "user", user)
9180
continue
9281
}
9382

94-
if user.DB == "" {
95-
user.DB = "admin"
96-
}
97-
98-
if user.PasswordSecretRef != nil && user.PasswordSecretRef.Key == "" {
99-
user.PasswordSecretRef.Key = "password"
100-
}
101-
10283
userInfo, err := mongoCli.GetUserInfo(ctx, user.Name, user.DB)
10384
if err != nil {
10485
log.Error(err, "get user info")
@@ -150,6 +131,49 @@ func handleUsers(ctx context.Context, cr *api.PerconaServerMongoDB, mongoCli mon
150131
return nil
151132
}
152133

134+
func validateUser(user *api.User, sysUserNames, uniqueUserNames map[string]struct{}) error {
135+
if sysUserNames == nil || uniqueUserNames == nil {
136+
return errors.New("invalid sys or unique usernames config")
137+
}
138+
139+
if _, reserved := sysUserNames[user.Name]; reserved {
140+
return fmt.Errorf("creating user with reserved user name %s is forbidden", user.Name)
141+
}
142+
143+
if _, exists := uniqueUserNames[user.Name]; exists {
144+
return fmt.Errorf("username %s should be unique", user.Name)
145+
}
146+
uniqueUserNames[user.Name] = struct{}{}
147+
148+
if len(user.Roles) == 0 {
149+
return fmt.Errorf("user %s must have at least one role", user.Name)
150+
}
151+
152+
if user.DB == "" {
153+
user.DB = "admin"
154+
}
155+
156+
if user.PasswordSecretRef != nil && user.PasswordSecretRef.Key == "" {
157+
user.PasswordSecretRef.Key = "password"
158+
}
159+
160+
return nil
161+
}
162+
163+
func fetchSystemUserNames(ctx context.Context, cr *api.PerconaServerMongoDB, client client.Client) (map[string]struct{}, error) {
164+
sysUsersSecret := corev1.Secret{}
165+
err := client.Get(ctx, types.NamespacedName{
166+
Namespace: cr.Namespace,
167+
Name: api.InternalUserSecretName(cr),
168+
}, &sysUsersSecret)
169+
170+
if err != nil && !k8serrors.IsNotFound(err) {
171+
return nil, errors.Wrap(err, "get internal sys users secret")
172+
}
173+
174+
return sysUserNames(sysUsersSecret), nil
175+
}
176+
153177
func handleRoles(ctx context.Context, cr *api.PerconaServerMongoDB, cli mongo.Client) {
154178
log := logf.FromContext(ctx)
155179
if len(cr.Spec.Roles) == 0 {
@@ -268,15 +292,15 @@ func toMongoRoleModel(role api.Role) (*mongo.Role, error) {
268292
return mr, nil
269293
}
270294

271-
// sysUserNames returns a set of system user names from the sysUsersSecret.
295+
// sysUserNames returns a set of system usernames from the sysUsersSecret.
272296
func sysUserNames(sysUsersSecret corev1.Secret) map[string]struct{} {
273-
sysUserNames := make(map[string]struct{}, len(sysUsersSecret.Data))
297+
names := make(map[string]struct{}, len(sysUsersSecret.Data))
274298
for k, v := range sysUsersSecret.Data {
275299
if strings.Contains(k, "_USER") {
276-
sysUserNames[string(v)] = struct{}{}
300+
names[string(v)] = struct{}{}
277301
}
278302
}
279-
return sysUserNames
303+
return names
280304
}
281305

282306
func updatePass(

pkg/controller/perconaservermongodb/custom_users_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package perconaservermongodb
33
import (
44
"testing"
55

6+
"github.com/pkg/errors"
7+
"github.com/stretchr/testify/assert"
8+
9+
api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1"
610
"github.com/percona/percona-server-mongodb-operator/pkg/psmdb/mongo"
711
)
812

@@ -232,3 +236,66 @@ func TestRolesChanged(t *testing.T) {
232236
})
233237
}
234238
}
239+
240+
func TestValidateUser(t *testing.T) {
241+
242+
tests := map[string]struct {
243+
user *api.User
244+
actualUser *api.User
245+
sysUserNames map[string]struct{}
246+
uniqueUserNames map[string]struct{}
247+
expectedErr error
248+
}{
249+
"invalid input for sysUserNames and uniqueUserNames": {
250+
user: &api.User{Name: "john", Roles: []api.UserRole{{Name: "rolename", DB: "testdb"}}, DB: "testdb"},
251+
expectedErr: errors.New("invalid sys or unique usernames config"),
252+
},
253+
"valid non-existing username": {
254+
user: &api.User{Name: "john", Roles: []api.UserRole{{Name: "rolename", DB: "testdb"}}, DB: "testdb"},
255+
actualUser: &api.User{Name: "john", Roles: []api.UserRole{{Name: "rolename", DB: "testdb"}}, DB: "testdb"},
256+
sysUserNames: map[string]struct{}{},
257+
uniqueUserNames: map[string]struct{}{},
258+
},
259+
"valid non-existing username, missing db and password secret ref": {
260+
user: &api.User{Name: "john", Roles: []api.UserRole{{Name: "rolename"}}, PasswordSecretRef: &api.SecretKeySelector{}},
261+
actualUser: &api.User{
262+
Name: "john",
263+
Roles: []api.UserRole{{Name: "rolename"}},
264+
DB: "admin",
265+
PasswordSecretRef: &api.SecretKeySelector{Key: "password"},
266+
},
267+
sysUserNames: map[string]struct{}{},
268+
uniqueUserNames: map[string]struct{}{},
269+
},
270+
"sys reserved username": {
271+
user: &api.User{Name: "root", Roles: []api.UserRole{{Name: "rolename", DB: "testdb"}}, DB: "testdb"},
272+
sysUserNames: map[string]struct{}{"root": {}},
273+
uniqueUserNames: map[string]struct{}{},
274+
expectedErr: errors.New("creating user with reserved user name root is forbidden"),
275+
},
276+
"not unique username": {
277+
user: &api.User{Name: "useradmin", Roles: []api.UserRole{{Name: "rolename", DB: "testdb"}}, DB: "testdb"},
278+
sysUserNames: map[string]struct{}{},
279+
uniqueUserNames: map[string]struct{}{"useradmin": {}},
280+
expectedErr: errors.New("username useradmin should be unique"),
281+
},
282+
"no roles defined": {
283+
user: &api.User{Name: "john", Roles: []api.UserRole{}, DB: "testdb"},
284+
sysUserNames: map[string]struct{}{},
285+
uniqueUserNames: map[string]struct{}{},
286+
expectedErr: errors.New("user john must have at least one role"),
287+
},
288+
}
289+
290+
for name, tt := range tests {
291+
t.Run(name, func(t *testing.T) {
292+
err := validateUser(tt.user, tt.sysUserNames, tt.uniqueUserNames)
293+
if tt.expectedErr != nil {
294+
assert.EqualError(t, err, tt.expectedErr.Error())
295+
} else {
296+
assert.Equal(t, tt.user, tt.actualUser)
297+
assert.NoError(t, err)
298+
}
299+
})
300+
}
301+
}

0 commit comments

Comments
 (0)