Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion api/v1/mdbmulti/mongodb_multi_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,8 @@ func (m *MongoDBMultiCluster) BuildConnectionString(username, password string, s
SetIsReplicaSet(true).
SetIsTLSEnabled(m.Spec.IsSecurityTLSConfigEnabled()).
SetHostnames(hostnames).
SetScheme(scheme)
SetScheme(scheme).
SetConnectionParams(connectionParams)

return builder.Build()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
kind: fix
date: 2026-03-24
---

* **MongoDBUser**: Correctly set `authSource` in the generated connection string secret to reflect `spec.db` instead of hardcoding it to `admin`.
5 changes: 5 additions & 0 deletions controllers/operator/connectionstring/connectionstring.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ func (b *builder) Build() string {
for k, v := range b.connectionParams {
connectionParams[k] = v
}

// authSource is only meaningful when an authMechanism is set.
if connectionParams["authMechanism"] == "" {
delete(connectionParams, "authSource")
}
var keys []string
for k := range connectionParams {
keys = append(keys, k)
Expand Down
4 changes: 2 additions & 2 deletions controllers/operator/mongodbuser_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ func (r *MongoDBUserReconciler) updateConnectionStringSecret(ctx context.Context
return xerrors.Errorf("connection string secret %s already exists and is not managed by the operator", secretName)
}

mongoAuthUserURI := connectionBuilder.BuildConnectionString(user.Spec.Username, password, connectionstring.SchemeMongoDB, map[string]string{})
mongoAuthUserSRVURI := connectionBuilder.BuildConnectionString(user.Spec.Username, password, connectionstring.SchemeMongoDBSRV, map[string]string{})
mongoAuthUserURI := connectionBuilder.BuildConnectionString(user.Spec.Username, password, connectionstring.SchemeMongoDB, map[string]string{"authSource": user.Spec.Database})
mongoAuthUserSRVURI := connectionBuilder.BuildConnectionString(user.Spec.Username, password, connectionstring.SchemeMongoDBSRV, map[string]string{"authSource": user.Spec.Database})

connectionStringSecret := secret.Builder().
SetName(secretName).
Expand Down
21 changes: 21 additions & 0 deletions controllers/operator/mongodbuser_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,27 @@ func TestFinalizerIsAdded_WhenUserIsCreated(t *testing.T) {
assert.Contains(t, user.GetFinalizers(), util.UserFinalizer)
}

func TestConnectionStringSecret_UsesSpecDb_AsAuthSource(t *testing.T) {
ctx := context.Background()
user := DefaultMongoDBUserBuilder().SetMongoDBResourceName("my-rs").SetDatabase("mydb").Build()
reconciler, client, _ := userReconcilerWithAuthMode(ctx, user, util.AutomationConfigScramSha256Option)

_ = client.Create(ctx, DefaultReplicaSetBuilder().EnableSCRAM().AgentAuthMode("SCRAM").SetName("my-rs").Build())
createUserControllerConfigMap(ctx, client)
createPasswordSecret(ctx, client, user.Spec.PasswordSecretKeyRef, "password")
Comment on lines +478 to +480
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

This test ignores the error returned by client.Create(...). If the replica set object fails to create, the test could proceed and fail later in a less clear way. Please assert require.NoError(t, client.Create(...)) (or similar) here for clearer failures.

Copilot uses AI. Check for mistakes.

_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: kube.ObjectKey(user.Namespace, user.Name)})
require.NoError(t, err)

secret := &corev1.Secret{}
err = client.Get(ctx, kube.ObjectKey(user.Namespace, user.GetConnectionStringSecretName()), secret)
require.NoError(t, err)

connectionString := string(secret.Data["connectionString.standard"])
assert.Contains(t, connectionString, "authSource=mydb", "authSource should be set to spec.db, not hardcoded 'admin'")
assert.NotContains(t, connectionString, "authSource=admin")
}

func TestUserReconciler_SavesConnectionStringForMultiShardedCluster(t *testing.T) {
// Define the details of the member clusters for the sharded cluster setup
memberClusters := test.NewMemberClusters(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
apiVersion: mongodb.com/v1
kind: MongoDBUser
metadata:
name: mms-user-2
spec:
passwordSecretKeyRef:
name: mms-user-2-password
key: password
username: "mms-user-2"
db: "testdb"
mongodbResourceRef:
name: "my-replica-set"
roles:
- db: "testdb"
name: "readWrite"
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
USER_PASSWORD = "my-password"
USER_DATABASE = "admin"

NON_ADMIN_USER_NAME = "mms-user-2"
NON_ADMIN_PASSWORD_SECRET_NAME = "mms-user-2-password"
NON_ADMIN_USER_PASSWORD = "my-password-2"
NON_ADMIN_USER_DATABASE = "testdb"


def create_password_secret(namespace: str) -> str:
create_or_update_secret(
Expand Down Expand Up @@ -65,6 +70,12 @@ def standard_secret(replica_set: MongoDB):
return read_secret(replica_set.namespace, secret_name)


@fixture(scope="function")
def non_admin_standard_secret(replica_set: MongoDB):
secret_name = "{}-{}-{}".format(replica_set.name, NON_ADMIN_USER_NAME, NON_ADMIN_USER_DATABASE)
return read_secret(replica_set.namespace, secret_name)


@fixture(scope="function")
def connection_string_secret(replica_set: MongoDB):
return read_secret(replica_set.namespace, CONNECTION_STRING_SECRET_NAME)
Expand Down Expand Up @@ -161,6 +172,30 @@ def test_credentials_secret_is_created(replica_set: MongoDB, standard_secret: Di
assert "password" in standard_secret
assert "connectionString.standard" in standard_secret
assert "connectionString.standardSrv" in standard_secret
# authSource in the connection string must match the user's spec.db
assert f"authSource={USER_DATABASE}" in standard_secret["connectionString.standard"]
assert f"authSource={USER_DATABASE}" in standard_secret["connectionString.standardSrv"]


@mark.e2e_replica_set_scram_sha_256_user_connectivity
def test_create_non_admin_db_user(replica_set: MongoDB, namespace: str):
create_or_update_secret(namespace, NON_ADMIN_PASSWORD_SECRET_NAME, {"password": NON_ADMIN_USER_PASSWORD})
resource = MongoDBUser.from_yaml(find_fixture("scram-sha-user-non-admin-db.yaml"), namespace=namespace)
resource["spec"]["mongodbResourceRef"]["name"] = replica_set.name
try_load(resource)
resource.update()
resource.assert_reaches_phase(Phase.Updated, timeout=150)


@mark.e2e_replica_set_scram_sha_256_user_connectivity
def test_non_admin_db_credentials_secret_is_created(replica_set: MongoDB, non_admin_standard_secret: Dict[str, str]):
assert "username" in non_admin_standard_secret
assert "password" in non_admin_standard_secret
assert "connectionString.standard" in non_admin_standard_secret
assert "connectionString.standardSrv" in non_admin_standard_secret
# authSource in the connection string must match the user's spec.db (non-admin database)
assert f"authSource={NON_ADMIN_USER_DATABASE}" in non_admin_standard_secret["connectionString.standard"]
assert f"authSource={NON_ADMIN_USER_DATABASE}" in non_admin_standard_secret["connectionString.standardSrv"]


@mark.e2e_replica_set_scram_sha_256_user_connectivity
Expand Down Expand Up @@ -213,7 +248,7 @@ def ac_updated() -> bool:
tester.assert_has_user(USER_NAME)
tester.assert_authentication_mechanism_enabled("SCRAM-SHA-256")
tester.assert_authentication_enabled()
tester.assert_expected_users(1)
tester.assert_expected_users(2)
tester.assert_authoritative_set(False)
return True
except AssertionError:
Expand All @@ -235,7 +270,7 @@ def auth_disabled() -> bool:
# we have explicitly set authentication to be disabled
try:
tester.assert_has_user(USER_NAME)
tester.assert_authentication_disabled(remaining_users=1)
tester.assert_authentication_disabled(remaining_users=2)
return True
except AssertionError:
return False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
from typing import Dict

import pytest
from kubetester import create_or_update_secret, find_fixture, read_secret, try_load
from kubetester.automation_config_tester import AutomationConfigTester
from kubetester.kubetester import KubernetesTester
from kubetester.kubetester import fixture as load_fixture
from kubetester.mongodb import MongoDB
from kubetester.mongodb_user import MongoDBUser
from kubetester.mongotester import ShardedClusterTester
from kubetester.phase import Phase
from pytest import fixture

MDB_RESOURCE = "sharded-cluster-scram-sha-256"
USER_NAME = "mms-user-1"
PASSWORD_SECRET_NAME = "mms-user-1-password"
USER_PASSWORD = "my-password"
USER_DATABASE = "admin"

NON_ADMIN_USER_NAME = "mms-user-2"
NON_ADMIN_PASSWORD_SECRET_NAME = "mms-user-2-password"
NON_ADMIN_USER_PASSWORD = "my-password-2"
NON_ADMIN_USER_DATABASE = "testdb"


@fixture(scope="function")
def standard_secret(namespace: str):
secret_name = "{}-{}-{}".format(MDB_RESOURCE, USER_NAME, USER_DATABASE)
return read_secret(namespace, secret_name)


@fixture(scope="function")
def non_admin_standard_secret(namespace: str):
secret_name = "{}-{}-{}".format(MDB_RESOURCE, NON_ADMIN_USER_NAME, NON_ADMIN_USER_DATABASE)
return read_secret(namespace, secret_name)


@pytest.mark.e2e_sharded_cluster_scram_sha_256_user_connectivity
Expand Down Expand Up @@ -126,3 +149,35 @@ def test_user_cannot_authenticate_with_old_password(self):
username="mms-user-1",
auth_mechanism="SCRAM-SHA-256",
)


@pytest.mark.e2e_sharded_cluster_scram_sha_256_user_connectivity
def test_credentials_secret_is_created(standard_secret: Dict[str, str]):
assert "username" in standard_secret
assert "password" in standard_secret
assert "connectionString.standard" in standard_secret
assert "connectionString.standardSrv" in standard_secret
# authSource in the connection string must match the user's spec.db
assert f"authSource={USER_DATABASE}" in standard_secret["connectionString.standard"]
assert f"authSource={USER_DATABASE}" in standard_secret["connectionString.standardSrv"]


@pytest.mark.e2e_sharded_cluster_scram_sha_256_user_connectivity
def test_create_non_admin_db_user(namespace: str):
create_or_update_secret(namespace, NON_ADMIN_PASSWORD_SECRET_NAME, {"password": NON_ADMIN_USER_PASSWORD})
resource = MongoDBUser.from_yaml(find_fixture("scram-sha-user-non-admin-db.yaml"), namespace=namespace)
resource["spec"]["mongodbResourceRef"]["name"] = MDB_RESOURCE
try_load(resource)
resource.update()
resource.assert_reaches_phase(Phase.Updated, timeout=150)


@pytest.mark.e2e_sharded_cluster_scram_sha_256_user_connectivity
def test_non_admin_db_credentials_secret_is_created(non_admin_standard_secret: Dict[str, str]):
assert "username" in non_admin_standard_secret
assert "password" in non_admin_standard_secret
assert "connectionString.standard" in non_admin_standard_secret
assert "connectionString.standardSrv" in non_admin_standard_secret
# authSource in the connection string must match the user's spec.db (non-admin database)
assert f"authSource={NON_ADMIN_USER_DATABASE}" in non_admin_standard_secret["connectionString.standard"]
assert f"authSource={NON_ADMIN_USER_DATABASE}" in non_admin_standard_secret["connectionString.standardSrv"]
Loading