diff --git a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml index 782a146401..67f73c7bf2 100644 --- a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml +++ b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml @@ -18764,7 +18764,6 @@ spec: type: array required: - name - - passwordSecretRef - roles type: object type: array diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index bcbeb7865e..d01c0265f6 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -19460,7 +19460,6 @@ spec: type: array required: - name - - passwordSecretRef - roles type: object type: array diff --git a/deploy/crd.yaml b/deploy/crd.yaml index cc552748de..a8a01573c7 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -19460,7 +19460,6 @@ spec: type: array required: - name - - passwordSecretRef - roles type: object type: array diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 2e863b6097..39b8b9dc1d 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -19460,7 +19460,6 @@ spec: type: array required: - name - - passwordSecretRef - roles type: object type: array diff --git a/e2e-tests/custom-users-roles-sharded/compare/user-external.json b/e2e-tests/custom-users-roles-sharded/compare/user-external.json new file mode 100644 index 0000000000..9a80fc294b --- /dev/null +++ b/e2e-tests/custom-users-roles-sharded/compare/user-external.json @@ -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 diff --git a/e2e-tests/custom-users-roles-sharded/compare/user-five.json b/e2e-tests/custom-users-roles-sharded/compare/user-five.json index a25a7b96c9..f0dd32df9a 100644 --- a/e2e-tests/custom-users-roles-sharded/compare/user-five.json +++ b/e2e-tests/custom-users-roles-sharded/compare/user-five.json @@ -4,13 +4,13 @@ switched to db testAdmin "user" : "user-five", "db" : "testAdmin", "roles" : [ - { - "role" : "role-four", - "db" : "testAdmin1" - }, { "role" : "role-five", "db" : "testAdmin2" + }, + { + "role" : "role-four", + "db" : "testAdmin1" } ], "mechanisms" : [ diff --git a/e2e-tests/custom-users-roles-sharded/compare/user-gen.json b/e2e-tests/custom-users-roles-sharded/compare/user-gen.json new file mode 100644 index 0000000000..56798db308 --- /dev/null +++ b/e2e-tests/custom-users-roles-sharded/compare/user-gen.json @@ -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 diff --git a/e2e-tests/custom-users-roles-sharded/compare/user-two.json b/e2e-tests/custom-users-roles-sharded/compare/user-two.json index 768466e384..c5c9521063 100644 --- a/e2e-tests/custom-users-roles-sharded/compare/user-two.json +++ b/e2e-tests/custom-users-roles-sharded/compare/user-two.json @@ -5,11 +5,11 @@ switched to db admin "db" : "admin", "roles" : [ { - "role" : "userAdminAnyDatabase", + "role" : "clusterAdmin", "db" : "admin" }, { - "role" : "clusterAdmin", + "role" : "userAdminAnyDatabase", "db" : "admin" } ], diff --git a/e2e-tests/custom-users-roles-sharded/conf/some-name-rs0.yml b/e2e-tests/custom-users-roles-sharded/conf/some-name-rs0.yml index bf6682bd62..f69b7413ff 100644 --- a/e2e-tests/custom-users-roles-sharded/conf/some-name-rs0.yml +++ b/e2e-tests/custom-users-roles-sharded/conf/some-name-rs0.yml @@ -43,6 +43,20 @@ 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: false diff --git a/e2e-tests/custom-users-roles-sharded/run b/e2e-tests/custom-users-roles-sharded/run index 22025b0854..5e6988a69f 100755 --- a/e2e-tests/custom-users-roles-sharded/run +++ b/e2e-tests/custom-users-roles-sharded/run @@ -31,6 +31,43 @@ check_auth() { fi } +get_user_cmd() { + local user="$1" + + cmd="(function() { + var user = db.getUser(${user}); + var roles = user.roles; + roles.sort((a, b) => { + if (a.role < b.role) return -1; + if (a.role > b.role) return 1; + return 0; + }); + user.roles = roles; + printjson(user); + })();" + + echo "$cmd" +} + + +get_role_cmd() { + local role="$1" + + cmd="(function() { + var role = db.getRole(${role}, {showPrivileges: true, showAuthenticationRestrictions: true}); + var roles = role.roles; + roles.sort((a, b) => { + if (a.role < b.role) return -1; + if (a.role > b.role) return 1; + return 0; + }); + role.roles = roles; + printjson(role); + })();" + + echo "$cmd" +} + test_dir=$(realpath $(dirname $0)) . ${test_dir}/../functions set_debug @@ -75,9 +112,18 @@ desc 'check user created on cluster creation' userOne="user-one" userOnePass=$(getSecretData "user-one" "userOnePassKey") -compare 'admin' 'db.getUser("user-one")' "$mongosUri" "user-one" +compare 'admin' "$(get_user_cmd \"user-one\")" "$mongosUri" "user-one" check_auth "$userOne:$userOnePass@$cluster-mongos.$namespace" +generatedUserSecret="$cluster-custom-user-secret" +generatedPass=$(kubectl_bin get secret $generatedUserSecret -o jsonpath="{.data.user-gen}" | base64 -d) +compare 'admin' "$(get_user_cmd \"user-gen\")" "$mongosUri" "user-gen" +check_auth "user-gen:$generatedPass@$cluster-mongos.$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' "$(get_user_cmd \"user-external\")" "$mongosUri" "user-external" + desc 'delete initial user from CR and create a new one' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ "spec": {"users":[ @@ -97,7 +143,7 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getUser("user-two")' "$mongosUri" "user-two" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongosUri" "user-two" userTwo="user-two" userTwoPass=$(getSecretData "user-two" "userTwoPassKey") @@ -131,7 +177,7 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getUser("user-two")' "$mongosUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongosUri" "user-two-update-roles" desc 'check user roles update from DB' @@ -139,14 +185,14 @@ run_mongos \ 'use admin\n db.updateUser("user-two", { roles : [{ role : "userAdminAnyDatabase", db: "admin"}]})' \ "$mongosUri" sleep 15 -compare 'admin' 'db.getUser("user-two")' "$mongosUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongosUri" "user-two-update-roles" desc 'check user recreated after deleted from DB' run_mongos \ 'use admin\n db.dropUser("user-two")' \ "$mongosUri" sleep 15 -compare 'admin' 'db.getUser("user-two")' "$mongosUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongosUri" "user-two-update-roles" desc 'check new user created after updated user name via CR' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -166,8 +212,8 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getUser("user-three")' "$mongosUri" "user-three-admin-db" -compare 'admin' 'db.getUser("user-two")' "$mongosUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-three\")" "$mongosUri" "user-three-admin-db" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongosUri" "user-two-update-roles" # user-three and user-two should be in the DB check_auth "$userTwo:$userTwoNewPass@$cluster-mongos.$namespace" @@ -191,8 +237,8 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }' wait_for_running $cluster-rs0 3 -compare 'newDb' 'db.getUser("user-three")' "$mongosUri" "user-three-newDb-db" -compare 'admin' 'db.getUser("user-three")' "$mongosUri" "user-three-admin-db" +compare 'newDb' "$(get_user_cmd \"user-three\")" "$mongosUri" "user-three-newDb-db" +compare 'admin' "$(get_user_cmd \"user-three\")" "$mongosUri" "user-three-admin-db" desc 'check new user created with default db and secret password key' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -210,21 +256,19 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getUser("user-four")' "$mongosUri" "user-four" +compare 'admin' "$(get_user_cmd \"user-four\")" "$mongosUri" "user-four" # ======================== Roles ======================== desc 'check user role on cluster initialization' -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-one\")" "$mongosUri" "role-one" desc 'check role recreated after deleted from DB' run_mongos \ 'use admin\n db.dropRole("role-one")' \ "$mongosUri" sleep 15 -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-one\" )" "$mongosUri" "role-one" desc 'delete initial role from CR and create a new one' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -255,10 +299,8 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-one" -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-two" +compare 'admin' "$(get_role_cmd \"role-one\" )" "$mongosUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-two\" )" "$mongosUri" "role-two" desc 'check role update from CR' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -282,16 +324,14 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }}' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-two-updated" +compare 'admin' "$(get_role_cmd \"role-two\" )" "$mongosUri" "role-two-updated" desc 'check role update from DB' run_mongos \ 'use admin\n db.updateRole( "role-two",{privileges:[{resource: {db:"config", collection:"" }, actions: ["find", "update"]}]})' \ "$mongosUri" sleep 15 -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-two-updated" +compare 'admin' "$(get_role_cmd \"role-two\" )" "$mongosUri" "role-two-updated" desc 'check new role created after updated role name via CR' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -315,8 +355,7 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ }}' wait_for_running $cluster-rs0 3 -compare 'admin' 'db.getRole("role-three", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-three" +compare 'admin' "$(get_role_cmd \"role-three\" )" "$mongosUri" "role-three" desc 'check creating multiple roles and the users in a single CR apply' kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ @@ -433,12 +472,10 @@ kubectl_bin patch psmdb ${cluster} --type=merge --patch '{ ] }}' wait_for_running $cluster-rs0 3 -compare 'testAdmin1' 'db.getRole("role-four", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-four" -compare 'testAdmin2' 'db.getRole("role-five", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongosUri" "role-five" -compare 'testAdmin' 'db.getUser("user-five")' "$mongosUri" "user-five" -compare 'testAdmin' 'db.getUser("user-six")' "$mongosUri" "user-six" +compare 'testAdmin1' "$(get_role_cmd \"role-four\" )" "$mongosUri" "role-four" +compare 'testAdmin2' "$(get_role_cmd \"role-five\" )" "$mongosUri" "role-five" +compare 'testAdmin' "$(get_user_cmd \"user-five\")" "$mongosUri" "user-five" +compare 'testAdmin' "$(get_user_cmd \"user-six\")" "$mongosUri" "user-six" destroy $namespace diff --git a/e2e-tests/custom-users-roles/compare/user-external.json b/e2e-tests/custom-users-roles/compare/user-external.json new file mode 100644 index 0000000000..9a80fc294b --- /dev/null +++ b/e2e-tests/custom-users-roles/compare/user-external.json @@ -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 diff --git a/e2e-tests/custom-users-roles/compare/user-five.json b/e2e-tests/custom-users-roles/compare/user-five.json index a25a7b96c9..f0dd32df9a 100644 --- a/e2e-tests/custom-users-roles/compare/user-five.json +++ b/e2e-tests/custom-users-roles/compare/user-five.json @@ -4,13 +4,13 @@ switched to db testAdmin "user" : "user-five", "db" : "testAdmin", "roles" : [ - { - "role" : "role-four", - "db" : "testAdmin1" - }, { "role" : "role-five", "db" : "testAdmin2" + }, + { + "role" : "role-four", + "db" : "testAdmin1" } ], "mechanisms" : [ diff --git a/e2e-tests/custom-users-roles/compare/user-gen.json b/e2e-tests/custom-users-roles/compare/user-gen.json new file mode 100644 index 0000000000..56798db308 --- /dev/null +++ b/e2e-tests/custom-users-roles/compare/user-gen.json @@ -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 diff --git a/e2e-tests/custom-users-roles/compare/user-two.json b/e2e-tests/custom-users-roles/compare/user-two.json index 768466e384..c5c9521063 100644 --- a/e2e-tests/custom-users-roles/compare/user-two.json +++ b/e2e-tests/custom-users-roles/compare/user-two.json @@ -5,11 +5,11 @@ switched to db admin "db" : "admin", "roles" : [ { - "role" : "userAdminAnyDatabase", + "role" : "clusterAdmin", "db" : "admin" }, { - "role" : "clusterAdmin", + "role" : "userAdminAnyDatabase", "db" : "admin" } ], diff --git a/e2e-tests/custom-users-roles/conf/some-name-rs0.yml b/e2e-tests/custom-users-roles/conf/some-name-rs0.yml index 0002a19ce9..98f8e8725f 100644 --- a/e2e-tests/custom-users-roles/conf/some-name-rs0.yml +++ b/e2e-tests/custom-users-roles/conf/some-name-rs0.yml @@ -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 diff --git a/e2e-tests/custom-users-roles/run b/e2e-tests/custom-users-roles/run index 965db48b65..ae7ce54f31 100755 --- a/e2e-tests/custom-users-roles/run +++ b/e2e-tests/custom-users-roles/run @@ -18,7 +18,44 @@ compare() { | sed '/"userId"/d' \ >$tmp_dir/${target} - diff ${test_dir}/compare/${target}.json $tmp_dir/${target} + diff ${test_dir}/compare/${target}.json $tmp_dir/${target} +} + +get_user_cmd() { + local user="$1" + + cmd="(function() { + var user = db.getUser(${user}); + var roles = user.roles; + roles.sort((a, b) => { + if (a.role < b.role) return -1; + if (a.role > b.role) return 1; + return 0; + }); + user.roles = roles; + printjson(user); + })();" + + echo "$cmd" +} + + +get_role_cmd() { + local role="$1" + + cmd="(function() { + var role = db.getRole(${role}, {showPrivileges: true, showAuthenticationRestrictions: true}); + var roles = role.roles; + roles.sort((a, b) => { + if (a.role < b.role) return -1; + if (a.role > b.role) return 1; + return 0; + }); + role.roles = roles; + printjson(role); + })();" + + echo "$cmd" } test_dir=$(realpath $(dirname $0)) @@ -47,10 +84,19 @@ desc 'check user created on cluster creation' userOne="user-one" userOnePass=$(getSecretData "user-one" "userOnePassKey") -compare 'admin' 'db.getUser("user-one")' "$mongoUri" "user-one" +compare 'admin' "$(get_user_cmd \"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' "$(get_user_cmd \"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' "$(get_user_cmd \"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":[ { @@ -69,7 +115,7 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ }' wait_for_running $cluster 3 -compare 'admin' 'db.getUser("user-two")' "$mongoUri" "user-two" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongoUri" "user-two" userTwo="user-two" userTwoPass=$(getSecretData "user-two" "userTwoPassKey") @@ -102,7 +148,7 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ]} }' wait_for_running $cluster 3 -compare 'admin' 'db.getUser("user-two")' "$mongoUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongoUri" "user-two-update-roles" desc 'check user roles update from DB' @@ -110,14 +156,14 @@ run_mongo \ 'use admin\n db.updateUser("user-two", { roles : [{ role : "userAdminAnyDatabase", db: "admin"}]})' \ "$mongoUri" sleep 15 -compare 'admin' 'db.getUser("user-two")' "$mongoUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongoUri" "user-two-update-roles" desc 'check user recreated after deleted from DB' run_mongo \ 'use admin\n db.dropUser("user-two")' \ "$mongoUri" sleep 15 -compare 'admin' 'db.getUser("user-two")' "$mongoUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongoUri" "user-two-update-roles" desc 'check new user created after updated user name via CR' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -137,8 +183,8 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ }' wait_for_running $cluster 3 -compare 'admin' 'db.getUser("user-three")' "$mongoUri" "user-three-admin-db" -compare 'admin' 'db.getUser("user-two")' "$mongoUri" "user-two-update-roles" +compare 'admin' "$(get_user_cmd \"user-three\")" "$mongoUri" "user-three-admin-db" +compare 'admin' "$(get_user_cmd \"user-two\")" "$mongoUri" "user-two-update-roles" # user-three and user-two should be in the DB check_mongo_auth "$userTwo:$userTwoNewPass@$cluster-0.$cluster.$namespace" @@ -161,8 +207,8 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ]} }' wait_for_running $cluster 3 -compare 'newDb' 'db.getUser("user-three")' "$mongoUri" "user-three-newDb-db" -compare 'admin' 'db.getUser("user-three")' "$mongoUri" "user-three-admin-db" +compare 'newDb' "$(get_user_cmd \"user-three\")" "$mongoUri" "user-three-newDb-db" +compare 'admin' "$(get_user_cmd \"user-three\")" "$mongoUri" "user-three-admin-db" desc 'check new user created with default db and secret password key' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -179,21 +225,19 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ]} }' wait_for_running $cluster 3 -compare 'admin' 'db.getUser("user-four")' "$mongoUri" "user-four" +compare 'admin' "$(get_user_cmd \"user-four\")" "$mongoUri" "user-four" # ======================== Roles ======================== desc 'check user role on cluster initialization' -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-one\")" "$mongoUri" "role-one" desc 'check role recreated after deleted from DB' run_mongo \ 'use admin\n db.dropRole("role-one")' \ "$mongoUri" sleep 15 -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-one\")" "$mongoUri" "role-one" desc 'delete initial role from CR and create a new one' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -222,10 +266,8 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ] }}' wait_for_running $cluster 3 -compare 'admin' 'db.getRole("role-one", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-one" -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-two" +compare 'admin' "$(get_role_cmd \"role-one\")" "$mongoUri" "role-one" +compare 'admin' "$(get_role_cmd \"role-two\")" "$mongoUri" "role-two" desc 'check role update from CR' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -248,16 +290,14 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ] }}' wait_for_running $cluster 3 -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-two-updated" +compare 'admin' "$(get_role_cmd \"role-two\")" "$mongoUri" "role-two-updated" desc 'check role update from DB' run_mongo \ 'use admin\n db.updateRole( "role-two",{privileges:[{resource: {db:"config", collection:"" }, actions: ["find", "update"]}]})' \ "$mongoUri" sleep 15 -compare 'admin' 'db.getRole("role-two", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-two-updated" +compare 'admin' "$(get_role_cmd \"role-two\")" "$mongoUri" "role-two-updated" desc 'check new role created after updated role name via CR' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -280,8 +320,7 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ] }}' wait_for_running $cluster 3 -compare 'admin' 'db.getRole("role-three", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-three" +compare 'admin' "$(get_role_cmd \"role-three\")" "$mongoUri" "role-three" desc 'check creating multiple roles and the users in a single CR apply' kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ @@ -398,12 +437,10 @@ kubectl_bin patch psmdb ${psmdb} --type=merge --patch '{ ] }}' wait_for_running $cluster 3 -compare 'testAdmin1' 'db.getRole("role-four", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-four" -compare 'testAdmin2' 'db.getRole("role-five", {showPrivileges: true, showAuthenticationRestrictions: true})' \ - "$mongoUri" "role-five" -compare 'testAdmin' 'db.getUser("user-five")' "$mongoUri" "user-five" -compare 'testAdmin' 'db.getUser("user-six")' "$mongoUri" "user-six" +compare 'testAdmin1' "$(get_role_cmd \"role-four\")" "$mongoUri" "role-four" +compare 'testAdmin2' "$(get_role_cmd \"role-five\")" "$mongoUri" "role-five" +compare 'testAdmin' "$(get_user_cmd \"user-five\")" "$mongoUri" "user-five" +compare 'testAdmin' "$(get_user_cmd \"user-six\")" "$mongoUri" "user-six" destroy $namespace diff --git a/e2e-tests/version-service/conf/crd.yaml b/e2e-tests/version-service/conf/crd.yaml index cc552748de..a8a01573c7 100644 --- a/e2e-tests/version-service/conf/crd.yaml +++ b/e2e-tests/version-service/conf/crd.yaml @@ -19460,7 +19460,6 @@ spec: type: array required: - name - - passwordSecretRef - roles type: object type: array diff --git a/pkg/apis/psmdb/v1/psmdb_types.go b/pkg/apis/psmdb/v1/psmdb_types.go index 125aec3962..2719b291aa 100644 --- a/pkg/apis/psmdb/v1/psmdb_types.go +++ b/pkg/apis/psmdb/v1/psmdb_types.go @@ -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 { diff --git a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go index 267cbd6a4b..d77f13d4df 100644 --- a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go +++ b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go @@ -1964,7 +1964,11 @@ func (in *UpgradeOptions) DeepCopy() *UpgradeOptions { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *User) DeepCopyInto(out *User) { *out = *in - out.PasswordSecretRef = in.PasswordSecretRef + if in.PasswordSecretRef != nil { + in, out := &in.PasswordSecretRef, &out.PasswordSecretRef + *out = new(SecretKeySelector) + **out = **in + } if in.Roles != nil { in, out := &in.Roles, &out.Roles *out = make([]UserRole, len(*in)) diff --git a/pkg/controller/perconaservermongodb/custom_users.go b/pkg/controller/perconaservermongodb/custom_users.go index d13b2afcea..80b26515b7 100644 --- a/pkg/controller/perconaservermongodb/custom_users.go +++ b/pkg/controller/perconaservermongodb/custom_users.go @@ -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 { @@ -31,30 +33,41 @@ func (r *ReconcilePerconaServerMongoDB) reconcileCustomUsers(ctx context.Context log := logf.FromContext(ctx) var err error - var cli mongo.Client + var mongoCli mongo.Client if cr.Spec.Sharding.Enabled { - cli, err = r.mongosClientWithRole(ctx, cr, api.RoleUserAdmin) + mongoCli, err = r.mongosClientWithRole(ctx, cr, api.RoleUserAdmin) } else { - cli, err = r.mongoClientWithRole(ctx, cr, cr.Spec.Replsets[0], api.RoleUserAdmin) + mongoCli, err = r.mongoClientWithRole(ctx, cr, cr.Spec.Replsets[0], api.RoleUserAdmin) } if err != nil { return errors.Wrap(err, "failed to get mongo client") } defer func() { - err := cli.Disconnect(ctx) + err := mongoCli.Disconnect(ctx) if err != nil { log.Error(err, "failed to close mongo connection") } }() - handleRoles(ctx, cr, cli) + handleRoles(ctx, cr, mongoCli) if len(cr.Spec.Users) == 0 { return nil } + err = handleUsers(ctx, cr, mongoCli, r.client) + if err != nil { + return errors.Wrap(err, "handle users") + } + + return nil +} + +func handleUsers(ctx context.Context, cr *api.PerconaServerMongoDB, mongoCli 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), @@ -82,39 +95,56 @@ 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) + userInfo, err := mongoCli.GetUserInfo(ctx, user.Name, user.DB) if err != nil { - log.Error(err, "failed to get user secret", "user", user) + log.Error(err, "get user info") continue } - userInfo, err := cli.GetUserInfo(ctx, user.Name, user.DB) + if user.DB == "$external" && userInfo == nil { + err = createExternalUser(ctx, mongoCli, &user) + if err != nil { + return errors.Wrapf(err, "create user %s", user.Name) + } + continue + } + + 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, "get user info") + log.Error(err, "failed to get user secret", "user", user) continue } 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, mongoCli, &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, mongoCli, &user, userInfo, sec, annotationKey, userSecretPassKey) if err != nil { log.Error(err, "update user pass", "user", user.Name) continue } - err = updateRoles(ctx, cli, &user, userInfo) + err = updateRoles(ctx, mongoCli, &user, userInfo) if err != nil { log.Error(err, "update user roles", "user", user.Name) continue @@ -260,14 +290,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 { @@ -280,7 +310,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) } @@ -327,13 +357,35 @@ func updateRoles( return nil } +// createExternalUser creates a user with $external database authentication method. +func createExternalUser(ctx context.Context, mongoCli mongo.Client, user *api.User) error { + log := logf.FromContext(ctx) + + roles := make([]map[string]interface{}, 0) + for _, role := range user.Roles { + roles = append(roles, map[string]interface{}{ + "role": role.Name, + "db": role.DB, + }) + } + + log.Info("Creating user", "user", user.UserID()) + err := mongoCli.CreateUser(ctx, user.DB, user.Name, "", roles...) + if err != nil { + return err + } + + log.Info("User created", "user", user.UserID()) + return nil +} + func createUser( ctx context.Context, cli client.Client, 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) @@ -345,7 +397,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 } @@ -354,7 +406,7 @@ 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 } @@ -362,3 +414,72 @@ func createUser( 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") + } + + 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) + } + + _, hasPass := secret.Data[passKey] + if !hasPass && name == defaultName { + 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.Update(ctx, secret) + if err != nil { + return nil, errors.Wrap(err, "failed to update user secret") + } + } + + // pass key should be present in the user provided secret + if !hasPass { + return nil, errors.New("password key not found in secret") + } + + return secret, nil +} diff --git a/pkg/psmdb/mongo/mongo.go b/pkg/psmdb/mongo/mongo.go index 2dbde48923..d07ed161fa 100644 --- a/pkg/psmdb/mongo/mongo.go +++ b/pkg/psmdb/mongo/mongo.go @@ -303,11 +303,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") }