diff --git a/changelog/61433.added b/changelog/61433.added new file mode 100644 index 000000000000..9cc27679ecee --- /dev/null +++ b/changelog/61433.added @@ -0,0 +1 @@ +Add postgres.timeout option to postgres module for limiting postgres query times diff --git a/salt/modules/postgres.py b/salt/modules/postgres.py index f9c559902377..0e29640ad95a 100644 --- a/salt/modules/postgres.py +++ b/salt/modules/postgres.py @@ -16,6 +16,14 @@ This data can also be passed into pillar. Options passed into opts will overwrite options passed into pillar +To prevent Postgres commands from running arbitrarily long, a timeout (in seconds) can be set + + .. code-block:: yaml + + postgres.timeout: 60 + + .. versionadded:: 3006.0 + :note: This module uses MD5 hashing which may not be compliant with certain security audits. @@ -68,6 +76,7 @@ _DEFAULT_PASSWORDS_ENCRYPTION = "md5" +_DEFAULT_COMMAND_TIMEOUT_SECS = 0 _EXTENSION_NOT_INSTALLED = "EXTENSION NOT INSTALLED" _EXTENSION_INSTALLED = "EXTENSION INSTALLED" _EXTENSION_TO_UPGRADE = "EXTENSION TO UPGRADE" @@ -154,6 +163,9 @@ def _run_psql(cmd, runas=None, password=None, host=None, port=None, user=None): kwargs = { "reset_system_locale": False, "clean_env": True, + "timeout": __salt__["config.option"]( + "postgres.timeout", default=_DEFAULT_COMMAND_TIMEOUT_SECS + ), } if runas is None: if not host: @@ -254,7 +266,13 @@ def _run_initdb( __salt__["file.chown"](pgpassfile, runas, "") cmd.extend(["--pwfile={}".format(pgpassfile)]) - kwargs = dict(runas=runas, clean_env=True) + kwargs = dict( + runas=runas, + clean_env=True, + timeout=__salt__["config.option"]( + "postgres.timeout", default=_DEFAULT_COMMAND_TIMEOUT_SECS + ), + ) cmdstr = " ".join([pipes.quote(c) for c in cmd]) ret = __salt__["cmd.run_all"](cmdstr, python_shell=False, **kwargs) @@ -1205,7 +1223,7 @@ def _verify_password(role, password, verifier, method): def _md5_password(role, password): return "md5{}".format( - hashlib.md5( + hashlib.md5( # nosec salt.utils.stringutils.to_bytes("{}{}".format(password, role)) ).hexdigest() ) diff --git a/tests/pytests/unit/modules/test_postgres.py b/tests/pytests/unit/modules/test_postgres.py index ec25e4d59187..393c087d1644 100644 --- a/tests/pytests/unit/modules/test_postgres.py +++ b/tests/pytests/unit/modules/test_postgres.py @@ -1,5 +1,6 @@ import pytest +import salt.modules.config as configmod import salt.modules.postgres as postgres from tests.support.mock import MagicMock, patch @@ -28,7 +29,8 @@ def configure_loader_modules(): "file.chown": MagicMock(), "file.remove": MagicMock(), }, - } + }, + configmod: {}, } @@ -132,3 +134,50 @@ def test_has_privileges_with_function(): user="testuser", runas="user", ) + + +def test__runpsql_with_timeout(): + cmd_run_mock = MagicMock() + postgres_opts = { + "config.option": configmod.option, + "cmd.run_all": cmd_run_mock, + } + kwargs = { + "reset_system_locale": False, + "clean_env": True, + "runas": "saltuser", + "python_shell": False, + } + with patch.dict(postgres.__salt__, postgres_opts): + with patch.dict( + configmod.__opts__, {"postgres.timeout": 60, "postgres.pass": None} + ): + postgres._run_psql("fakecmd", runas="saltuser") + cmd_run_mock.assert_called_with("fakecmd", timeout=60, **kwargs) + with patch.dict(configmod.__opts__, {"postgres.pass": None}): + postgres._run_psql("fakecmd", runas="saltuser") + cmd_run_mock.assert_called_with("fakecmd", timeout=0, **kwargs) + + +def test__run_initdb_with_timeout(): + cmd_run_mock = MagicMock(return_value={}) + postgres_opts = { + "config.option": configmod.option, + "cmd.run_all": cmd_run_mock, + } + kwargs = { + "clean_env": True, + "runas": "saltuser", + "python_shell": False, + } + cmd_str = "/fake/path --pgdata=fakename --username=saltuser --auth=password --encoding=UTF8" + with patch.dict(postgres.__salt__, postgres_opts): + with patch.object(postgres, "_find_pg_binary", return_value="/fake/path"): + with patch.dict( + configmod.__opts__, {"postgres.timeout": 60, "postgres.pass": None} + ): + postgres._run_initdb("fakename", runas="saltuser") + cmd_run_mock.assert_called_with(cmd_str, timeout=60, **kwargs) + with patch.dict(configmod.__opts__, {"postgres.pass": None}): + postgres._run_initdb("fakename", runas="saltuser") + cmd_run_mock.assert_called_with(cmd_str, timeout=0, **kwargs)