diff --git a/changelog/66313.changed.md b/changelog/66313.changed.md new file mode 100644 index 00000000000..aea304cbc79 --- /dev/null +++ b/changelog/66313.changed.md @@ -0,0 +1 @@ +Made gpg modules respect user's GNUPGHOME if set in shell environment diff --git a/salt/modules/gpg.py b/salt/modules/gpg.py index bc09631e5dd..8df4b7860b6 100644 --- a/salt/modules/gpg.py +++ b/salt/modules/gpg.py @@ -11,6 +11,13 @@ Sign, encrypt, sign plus encrypt and verify text and files. Be aware that the alternate ``gnupg`` and ``pretty-bad-protocol`` libraries are not supported. +.. versionchanged:: 3008.0 + + When ``gnupghome`` is not set explicitly, this module now tries to + respect a custom ``GNUPGHOME`` environmental variable. + If a ``user`` is not passed, the current process' environment is queried, + otherwise the user's configured shell environment is taken as a reference + in the same way the ``cmd`` modules operate. """ import functools @@ -23,6 +30,7 @@ import salt.utils.data import salt.utils.files import salt.utils.immutabletypes as immutabletypes import salt.utils.path +import salt.utils.platform import salt.utils.stringutils import salt.utils.versions from salt.exceptions import SaltInvocationError @@ -153,11 +161,25 @@ def _get_user_gnupghome(user): Return default GnuPG home directory path for a user """ if user == "salt": - gnupghome = os.path.join(__salt__["config.get"]("config_dir"), "gpgkeys") - else: - gnupghome = os.path.join(_get_user_info(user)["home"], ".gnupg") + return os.path.join(__salt__["config.get"]("config_dir"), "gpgkeys") - return gnupghome + # Try to respect GNUPGHOME environment variable. + if user is None: + gnupghome_env = __salt__["environ.get"]("GNUPGHOME") + else: + cmd = 'echo -n "$GNUPGHOME"' + if salt.utils.platform.is_windows(): + cmd = "echo %GNUPGHOME%" + gnupghome_env = __salt__["cmd.run_stdout"]( + cmd, python_shell=True, runas=user + ).strip() + if gnupghome_env.startswith("~"): + # This does not resolve `~` since that potentially complicates things a lot. + # It should have been resolved by the shell anyways. + log.warning("Found GNUPGHOME beginning with tilde, ignoring") + gnupghome_env = "" + + return gnupghome_env or os.path.join(_get_user_info(user)["home"], ".gnupg") def _restore_ownership(func): diff --git a/tests/pytests/unit/modules/test_gpg.py b/tests/pytests/unit/modules/test_gpg.py index c8b3bcac2c4..aca8b4d3b04 100644 --- a/tests/pytests/unit/modules/test_gpg.py +++ b/tests/pytests/unit/modules/test_gpg.py @@ -10,6 +10,7 @@ import shutil import subprocess import time import types +from pathlib import Path import psutil import pytest @@ -204,7 +205,14 @@ def gpghome(tmp_path): @pytest.fixture def configure_loader_modules(gpghome): - return {gpg: {}} + return { + gpg: { + "__salt__": { + "environ.get": lambda *x: "", + "cmd.run_stdout": lambda *x, **y: "", + } + } + } def test_list_keys(): @@ -1101,7 +1109,9 @@ def _import_result_mock(request): indirect=True, ) def test_gpg_receive_keys_no_user_id(_import_result_mock): - with patch("salt.modules.gpg._create_gpg") as create: + with patch("salt.modules.gpg._create_gpg") as create, patch( + "salt.modules.gpg._create_gnupghome" + ): with patch.dict( gpg.__salt__, {"user.info": MagicMock(), "config.option": Mock()} ): @@ -1123,7 +1133,9 @@ def test_gpg_receive_keys_no_user_id(_import_result_mock): indirect=True, ) def test_gpg_receive_keys_keyserver_unavailable(_import_result_mock): - with patch("salt.modules.gpg._create_gpg") as create: + with patch("salt.modules.gpg._create_gpg") as create, patch( + "salt.modules.gpg._create_gnupghome" + ): with patch.dict( gpg.__salt__, {"user.info": MagicMock(), "config.option": Mock()} ): @@ -1131,3 +1143,42 @@ def test_gpg_receive_keys_keyserver_unavailable(_import_result_mock): res = gpg.receive_keys(keys="abc", user="abc") assert res["res"] is False assert any("No keyserver available" in x for x in res["message"]) + + +@pytest.mark.parametrize( + "user,envvar", + ( + ("testuser", ""), + ("testuser", "/home/testuser/local/share/gnupg"), + (None, ""), + (None, "/home/testuser/local/share/gnupg"), + ("salt", ""), + ("salt", "/this/should/not/matter"), + ), +) +def test_get_user_gnupghome_respects_shell_env_setup(user, envvar): + config_dir = "/etc/salt" # minion_opts["config_dir"] is not set, only conf_dir (?) + user = user or "testuser" + if user == "salt": + homedir = "/opt/saltstack/salt" + expected = str(Path(config_dir) / "gpgkeys") + else: + homedir = f"/home/{user}" + expected = envvar or str(Path(homedir) / ".gnupg") + userinfo = { + "home": homedir, + "uid": 1000, + "gid": 1000, + "shell": "/bin/bash", + } + with patch.dict( + gpg.__salt__, + { + "user.info": lambda *x: userinfo, + "environ.get": lambda *x: envvar, + "cmd.run_stdout": lambda *x, **y: envvar, + "config.get": lambda *x: config_dir, + }, + ): + res = gpg._get_user_gnupghome(user) + assert res == expected