Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K8SPSMDB-1164: Allow creating user with $external database #1690

Merged
merged 27 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
281ea28
Update crds.
inelpandzic Oct 30, 2024
6e85b24
Generate custom user pass.
inelpandzic Oct 30, 2024
b91a8a1
Update e2e test.
inelpandzic Oct 30, 2024
2e90cb9
Fix check
inelpandzic Oct 30, 2024
f204fad
Remove check
inelpandzic Oct 30, 2024
1780fb8
Log
inelpandzic Oct 30, 2024
5dec493
Fix
inelpandzic Oct 30, 2024
b9c3932
Propper pass key.
inelpandzic Oct 30, 2024
579342f
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Oct 30, 2024
1de4b2c
Refactor and fix external db user creation.
inelpandzic Oct 30, 2024
7eeb1ba
Update e2e test.
inelpandzic Oct 30, 2024
75c1edc
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 1, 2024
79d8181
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 13, 2024
0c83355
Update e2e tests.
inelpandzic Nov 13, 2024
0bc1914
Make sure if external DB user is created that we don't handle user
inelpandzic Nov 13, 2024
42e16f1
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 13, 2024
965cc40
Fix custom-users-roles-sharded test.
inelpandzic Nov 14, 2024
5e4d011
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 14, 2024
1c33033
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 17, 2024
5330f06
Order roles for getUser.
inelpandzic Nov 21, 2024
9be37ca
Fix getRole order.
inelpandzic Nov 21, 2024
9a99799
Fix getRole roles order.
inelpandzic Nov 21, 2024
a4fa535
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 21, 2024
6ea2dda
Refactor
inelpandzic Nov 21, 2024
d05e2e1
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 22, 2024
6394cce
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Nov 25, 2024
e7ddd29
Merge branch 'main' into K8SPSMDB-1164-external-user-db
inelpandzic Dec 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18731,7 +18731,6 @@ spec:
type: array
required:
- name
- passwordSecretRef
- roles
type: object
type: array
Expand Down
1 change: 0 additions & 1 deletion deploy/bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19413,7 +19413,6 @@ spec:
type: array
required:
- name
- passwordSecretRef
- roles
type: object
type: array
Expand Down
1 change: 0 additions & 1 deletion deploy/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19413,7 +19413,6 @@ spec:
type: array
required:
- name
- passwordSecretRef
- roles
type: object
type: array
Expand Down
1 change: 0 additions & 1 deletion deploy/cw-bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19413,7 +19413,6 @@ spec:
type: array
required:
- name
- passwordSecretRef
- roles
type: object
type: array
Expand Down
20 changes: 20 additions & 0 deletions e2e-tests/custom-users-roles/compare/user-external.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
switched to db $external
{
"_id" : "$external.user-external",
"user" : "user-external",
"db" : "$external",
"roles" : [
{
"role" : "clusterAdmin",
"db" : "admin"
},
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
],
"mechanisms" : [
"external"
]
}
bye
21 changes: 21 additions & 0 deletions e2e-tests/custom-users-roles/compare/user-gen.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
switched to db admin
{
"_id" : "admin.user-gen",
"user" : "user-gen",
"db" : "admin",
"roles" : [
{
"role" : "clusterAdmin",
"db" : "admin"
},
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
],
"mechanisms" : [
"SCRAM-SHA-1",
"SCRAM-SHA-256"
]
}
bye
15 changes: 15 additions & 0 deletions e2e-tests/custom-users-roles/conf/some-name-rs0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ spec:
db: admin
- name: userAdminAnyDatabase
db: admin
- name: user-gen
db: admin
roles:
- name: clusterAdmin
db: admin
- name: userAdminAnyDatabase
db: admin
- name: user-external
db: $external
roles:
- name: clusterAdmin
db: admin
- name: userAdminAnyDatabase
db: admin

backup:
enabled: true
image: perconalab/percona-server-mongodb-operator:1.1.0-backup
Expand Down
11 changes: 10 additions & 1 deletion e2e-tests/custom-users-roles/run
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ userOnePass=$(getSecretData "user-one" "userOnePassKey")
compare 'admin' 'db.getUser("user-one")' "$mongoUri" "user-one"
check_mongo_auth "$userOne:$userOnePass@$cluster-0.$cluster.$namespace"

desc 'delete initial user from CR and create a new one'
generatedUserSecret="$psmdb-custom-user-secret"
generatedPass=$(kubectl_bin get secret $generatedUserSecret -o jsonpath="{.data.user-gen}" | base64 -d)
compare 'admin' 'db.getUser("user-gen")' "$mongoUri" "user-gen"
check_mongo_auth "user-gen:$generatedPass@$cluster-0.$cluster.$namespace"

# Only check if $external.user-external user exists, as the password is not known
# since we don't have a external provider set in this test
compare '$external' 'db.getUser("user-external")' "$mongoUri" "user-external"

desc 'delete initial users from CR and create a new one'
kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{
"spec": {"users":[
{
Expand Down
1 change: 0 additions & 1 deletion e2e-tests/version-service/conf/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19413,7 +19413,6 @@ spec:
type: array
required:
- name
- passwordSecretRef
- roles
type: object
type: array
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/psmdb/v1/psmdb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ type SecretKeySelector struct {
}

type User struct {
Name string `json:"name"`
DB string `json:"db,omitempty"`
PasswordSecretRef SecretKeySelector `json:"passwordSecretRef"`
Roles []UserRole `json:"roles"`
Name string `json:"name"`
DB string `json:"db,omitempty"`
PasswordSecretRef *SecretKeySelector `json:"passwordSecretRef,omitempty"`
Roles []UserRole `json:"roles"`
}

func (u *User) UserID() string {
Expand Down
6 changes: 5 additions & 1 deletion pkg/apis/psmdb/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 78 additions & 11 deletions pkg/controller/perconaservermongodb/custom_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"

api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1"
"github.com/percona/percona-server-mongodb-operator/pkg/psmdb/mongo"
s "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/secret"
)

func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context, cr *api.PerconaServerMongoDB) error {
Expand Down Expand Up @@ -56,8 +58,19 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context
return nil
}

err = handleUsers(ctx, cr, cli, r.client)
if err != nil {
return errors.Wrap(err, "handle users")
}

return nil
}

func handleUsers(ctx context.Context, cr *api.PerconaServerMongoDB, cli mongo.Client, client client.Client) error {
log := logf.FromContext(ctx)

sysUsersSecret := corev1.Secret{}
err = r.client.Get(ctx,
err := client.Get(ctx,
types.NamespacedName{
Namespace: cr.Namespace,
Name: api.InternalUserSecretName(cr),
Expand Down Expand Up @@ -85,11 +98,20 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context
user.DB = "admin"
}

if user.PasswordSecretRef.Key == "" {
if user.PasswordSecretRef != nil && user.PasswordSecretRef.Key == "" {
user.PasswordSecretRef.Key = "password"
}

sec, err := getUserSecret(ctx, r.client, cr, user.PasswordSecretRef.Name)
defaultUserSecretName := fmt.Sprintf("%s-custom-user-secret", cr.Name)

userSecretName := defaultUserSecretName
userSecretPassKey := user.Name
if user.PasswordSecretRef != nil {
userSecretName = user.PasswordSecretRef.Name
userSecretPassKey = user.PasswordSecretRef.Key
}

sec, err := getCustomUserSecret(ctx, client, cr, userSecretName, defaultUserSecretName, userSecretPassKey)
if err != nil {
log.Error(err, "failed to get user secret", "user", user)
continue
Expand All @@ -104,14 +126,14 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context
annotationKey := fmt.Sprintf("percona.com/%s-%s-hash", cr.Name, user.Name)

if userInfo == nil {
err = createUser(ctx, r.client, cli, &user, &sec, annotationKey)
err = createUser(ctx, client, cli, &user, sec, annotationKey, userSecretPassKey)
if err != nil {
return errors.Wrapf(err, "create user %s", user.Name)
}
continue
}

err = updatePass(ctx, r.client, cli, &user, userInfo, &sec, annotationKey)
err = updatePass(ctx, client, cli, &user, userInfo, sec, annotationKey, userSecretPassKey)
if err != nil {
log.Error(err, "update user pass", "user", user.Name)
continue
Expand Down Expand Up @@ -261,14 +283,14 @@ func updatePass(
user *api.User,
userInfo *mongo.User,
secret *corev1.Secret,
annotationKey string) error {
annotationKey, passKey string) error {
log := logf.FromContext(ctx)

if userInfo == nil {
return nil
}

newHash := sha256Hash(secret.Data[user.PasswordSecretRef.Key])
newHash := sha256Hash(secret.Data[passKey])

hash, ok := secret.Annotations[annotationKey]
if ok && hash == newHash {
Expand All @@ -281,7 +303,7 @@ func updatePass(

log.Info("User password changed, updating it.", "user", user.UserID())

err := mongoCli.UpdateUserPass(ctx, user.DB, user.Name, string(secret.Data[user.PasswordSecretRef.Key]))
err := mongoCli.UpdateUserPass(ctx, user.DB, user.Name, string(secret.Data[passKey]))
if err != nil {
return errors.Wrapf(err, "update user %s password", user.Name)
}
Expand Down Expand Up @@ -334,7 +356,7 @@ func createUser(
mongoCli mongo.Client,
user *api.User,
secret *corev1.Secret,
annotationKey string) error {
annotationKey, passKey string) error {
log := logf.FromContext(ctx)

roles := make([]map[string]interface{}, 0)
Expand All @@ -346,7 +368,7 @@ func createUser(
}

log.Info("Creating user", "user", user.UserID())
err := mongoCli.CreateUser(ctx, user.DB, user.Name, string(secret.Data[user.PasswordSecretRef.Key]), roles...)
err := mongoCli.CreateUser(ctx, user.DB, user.Name, string(secret.Data[passKey]), roles...)
if err != nil {
return err
}
Expand All @@ -355,11 +377,56 @@ func createUser(
secret.Annotations = make(map[string]string)
}

secret.Annotations[annotationKey] = string(sha256Hash(secret.Data[user.PasswordSecretRef.Key]))
secret.Annotations[annotationKey] = string(sha256Hash(secret.Data[passKey]))
if err := cli.Update(ctx, secret); err != nil {
return err
}

log.Info("User created", "user", user.UserID())
return nil
}

// getCustomUserSecret gets secret by name defined by `user.PasswordSecretRef.Name` or returns a secret
// with newly generated password if name matches defaultName
func getCustomUserSecret(ctx context.Context, cl client.Client, cr *api.PerconaServerMongoDB, name, defaultName, passKey string) (*corev1.Secret, error) {
log := logf.FromContext(ctx)

secret := &corev1.Secret{}
err := cl.Get(ctx, types.NamespacedName{Name: name, Namespace: cr.Namespace}, secret)

if err != nil && name != defaultName {
return nil, errors.Wrap(err, "failed to get user secret")
}
Comment on lines +426 to +428
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we don't create the secret object if user just wants to customize its name. Was it like this in the spec? @spron-in @eleo007

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what do you mean by "user just wants to customize its name", but how this behaves is like this:

  • If in the spec the user sets passwordSecretRef.name we will look for that secret. If we don't find it we will fail creating that user.
  • If the user does not set passwordSecretRef.name, we will create secret {cluster-name}-custom-user-secret, generate a password for the user and set it by the key named after user name.

And yes, I'll add this to the spec as well.


if err != nil && !k8serrors.IsNotFound(err) && name == defaultName {
return nil, errors.Wrap(err, "failed to get user secret")
}

if err != nil && k8serrors.IsNotFound(err) {
secret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
},
}

pass, err := s.GeneratePassword()
if err != nil {
return nil, errors.Wrap(err, "generate custom user password")
}

if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
secret.Data[passKey] = pass

err = cl.Create(ctx, secret)
if err != nil {
return nil, errors.Wrap(err, "create custom users secret")
}

log.Info("Created custom user secrets", "secrets", secret.Name)
}

return secret, nil
}
11 changes: 8 additions & 3 deletions pkg/psmdb/mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,16 @@ func (client *mongoClient) GetRole(ctx context.Context, db, role string) (*Role,
func (client *mongoClient) CreateUser(ctx context.Context, db, user, pwd string, roles ...map[string]interface{}) error {
resp := OKResponse{}

res := client.Database(db).RunCommand(ctx, bson.D{
d := bson.D{
{Key: "createUser", Value: user},
{Key: "pwd", Value: pwd},
{Key: "roles", Value: roles},
})
}

if db != "$external" {
d = append(d, bson.E{Key: "pwd", Value: pwd})
}

res := client.Database(db).RunCommand(ctx, d)
if res.Err() != nil {
return errors.Wrap(res.Err(), "failed to create user")
}
Expand Down
Loading