add postgres.timeout option to stop unintentionally long queries

This commit is contained in:
MKLeb 2022-10-31 21:15:25 -04:00 committed by Megan Wilhite
parent 29d88513b9
commit 637530a4ce
3 changed files with 71 additions and 5 deletions

1
changelog/61433.added Normal file
View file

@ -0,0 +1 @@
Add postgres.timeout option to postgres module for limiting postgres query times

View file

@ -16,6 +16,14 @@ Module to provide Postgres compatibility to salt.
This data can also be passed into pillar. Options passed into opts will This data can also be passed into pillar. Options passed into opts will
overwrite options passed into pillar 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 :note: This module uses MD5 hashing which may not be compliant with certain
security audits. security audits.
@ -68,7 +76,7 @@ log = logging.getLogger(__name__)
_DEFAULT_PASSWORDS_ENCRYPTION = "md5" _DEFAULT_PASSWORDS_ENCRYPTION = "md5"
_DEFAULT_COMMAND_TIMEOUT_SECS = 60 _DEFAULT_COMMAND_TIMEOUT_SECS = 0
_EXTENSION_NOT_INSTALLED = "EXTENSION NOT INSTALLED" _EXTENSION_NOT_INSTALLED = "EXTENSION NOT INSTALLED"
_EXTENSION_INSTALLED = "EXTENSION INSTALLED" _EXTENSION_INSTALLED = "EXTENSION INSTALLED"
_EXTENSION_TO_UPGRADE = "EXTENSION TO UPGRADE" _EXTENSION_TO_UPGRADE = "EXTENSION TO UPGRADE"
@ -155,7 +163,9 @@ def _run_psql(cmd, runas=None, password=None, host=None, port=None, user=None):
kwargs = { kwargs = {
"reset_system_locale": False, "reset_system_locale": False,
"clean_env": True, "clean_env": True,
"timeout": _DEFAULT_COMMAND_TIMEOUT_SECS, "timeout": __salt__["config.option"](
"postgres.timeout", default=_DEFAULT_COMMAND_TIMEOUT_SECS
),
} }
if runas is None: if runas is None:
if not host: if not host:
@ -256,7 +266,13 @@ def _run_initdb(
__salt__["file.chown"](pgpassfile, runas, "") __salt__["file.chown"](pgpassfile, runas, "")
cmd.extend(["--pwfile={}".format(pgpassfile)]) cmd.extend(["--pwfile={}".format(pgpassfile)])
kwargs = dict(runas=runas, clean_env=True, timeout=_DEFAULT_COMMAND_TIMEOUT_SECS) 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]) cmdstr = " ".join([pipes.quote(c) for c in cmd])
ret = __salt__["cmd.run_all"](cmdstr, python_shell=False, **kwargs) ret = __salt__["cmd.run_all"](cmdstr, python_shell=False, **kwargs)
@ -1207,7 +1223,7 @@ def _verify_password(role, password, verifier, method):
def _md5_password(role, password): def _md5_password(role, password):
return "md5{}".format( return "md5{}".format(
hashlib.md5( hashlib.md5( # nosec
salt.utils.stringutils.to_bytes("{}{}".format(password, role)) salt.utils.stringutils.to_bytes("{}{}".format(password, role))
).hexdigest() ).hexdigest()
) )

View file

@ -1,5 +1,6 @@
import pytest import pytest
import salt.modules.config as configmod
import salt.modules.postgres as postgres import salt.modules.postgres as postgres
from tests.support.mock import MagicMock, patch from tests.support.mock import MagicMock, patch
@ -28,7 +29,8 @@ def configure_loader_modules():
"file.chown": MagicMock(), "file.chown": MagicMock(),
"file.remove": MagicMock(), "file.remove": MagicMock(),
}, },
} },
configmod: {},
} }
@ -132,3 +134,50 @@ def test_has_privileges_with_function():
user="testuser", user="testuser",
runas="user", 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)