Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog/68659.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed mongodb tops module authentication to be compatible with pymongo v4+ by passing credentials directly to MongoClient instead of using the deprecated authenticate() method
26 changes: 19 additions & 7 deletions salt/tops/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
you are using mongo authentication. Defaults to ``''``.
* ``mongo.password`` - The password for connecting to mongo. Only required
if you are using mongo authentication. Defaults to ``''``.
* ``mongo.authdb`` - The database to authenticate against. Defaults to ``'admin'``.


Configuring the Mongo Tops Subsystem
Expand Down Expand Up @@ -96,6 +97,9 @@ def top(**kwargs):
host = __opts__["mongo.host"]
port = __opts__["mongo.port"]
ssl = __opts__.get("mongo.ssl") or False
user = __opts__.get("mongo.user")
password = __opts__.get("mongo.password")
authdb = __opts__.get("mongo.authdb", "admin")
collection = __opts__["master_tops"]["mongo"].get("collection", "tops")
id_field = __opts__["master_tops"]["mongo"].get("id_field", "_id")
re_pattern = __opts__["master_tops"]["mongo"].get("re_pattern", "")
Expand All @@ -106,17 +110,25 @@ def top(**kwargs):
)

log.info("connecting to %s:%s for mongo ext_tops", host, port)
conn = pymongo.MongoClient(host=host, port=port, ssl=ssl)

log.debug("using database '%s'", __opts__["mongo.db"])
mdb = conn[__opts__["mongo.db"]]

user = __opts__.get("mongo.user")
password = __opts__.get("mongo.password")
# Build connection parameters for pymongo v4 compatibility
conn_kwargs = {
"host": host,
"port": port,
"ssl": ssl,
}

# Pass authentication credentials directly to MongoClient for pymongo v4 compatibility
if user and password:
log.debug("authenticating as '%s'", user)
mdb.authenticate(user, password)
conn_kwargs["username"] = user
conn_kwargs["password"] = password
conn_kwargs["authSource"] = authdb

conn = pymongo.MongoClient(**conn_kwargs)

log.debug("using database '%s'", __opts__["mongo.db"])
mdb = conn[__opts__["mongo.db"]]

# Do the regex string replacement on the minion id
minion_id = kwargs["opts"]["id"]
Expand Down
88 changes: 88 additions & 0 deletions tests/pytests/unit/tops/test_mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,91 @@ def test_tops_should_correctly_pass_ssl_arg_to_MongoClient(expected_ssl, use_ssl
fake_pymongo.MongoClient.assert_called_with(
host="fnord", port="fnord", ssl=expected_ssl
)


def test_tops_should_pass_auth_to_MongoClient_for_pymongo_v4():
"""
Test that authentication credentials are passed to MongoClient constructor
for pymongo v4 compatibility, not using deprecated mdb.authenticate()
"""
salt.tops.mongo.HAS_PYMONGO = True
with patch("salt.tops.mongo.pymongo", create=True) as fake_pymongo, patch.dict(
"salt.tops.mongo.__opts__",
{
"master_tops": {"mongo": {}},
"mongo.host": "localhost",
"mongo.port": 27017,
"mongo.user": "testuser",
"mongo.password": "testpass",
"mongo.db": "salt",
},
):
salt.tops.mongo.top(opts={"id": "test-minion"})

# Verify MongoClient is called with authentication parameters
fake_pymongo.MongoClient.assert_called_with(
host="localhost",
port=27017,
ssl=False,
username="testuser",
password="testpass",
authSource="admin",
)

# Verify that authenticate() is NOT called (pymongo v4 compatibility)
fake_db = fake_pymongo.MongoClient.return_value.__getitem__.return_value
assert not fake_db.authenticate.called


def test_tops_should_use_custom_authdb():
"""
Test that custom authdb is passed correctly to MongoClient
"""
salt.tops.mongo.HAS_PYMONGO = True
with patch("salt.tops.mongo.pymongo", create=True) as fake_pymongo, patch.dict(
"salt.tops.mongo.__opts__",
{
"master_tops": {"mongo": {}},
"mongo.host": "localhost",
"mongo.port": 27017,
"mongo.user": "testuser",
"mongo.password": "testpass",
"mongo.authdb": "myauthdb",
"mongo.db": "salt",
},
):
salt.tops.mongo.top(opts={"id": "test-minion"})

# Verify custom authSource is used
fake_pymongo.MongoClient.assert_called_with(
host="localhost",
port=27017,
ssl=False,
username="testuser",
password="testpass",
authSource="myauthdb",
)


def test_tops_without_auth_should_not_pass_credentials():
"""
Test that when no credentials are provided, MongoClient is called without auth params
"""
salt.tops.mongo.HAS_PYMONGO = True
with patch("salt.tops.mongo.pymongo", create=True) as fake_pymongo, patch.dict(
"salt.tops.mongo.__opts__",
{
"master_tops": {"mongo": {}},
"mongo.host": "localhost",
"mongo.port": 27017,
"mongo.db": "salt",
},
):
salt.tops.mongo.top(opts={"id": "test-minion"})

# Verify MongoClient is called without authentication parameters
fake_pymongo.MongoClient.assert_called_with(
host="localhost",
port=27017,
ssl=False,
)