Skip to content

Commit

Permalink
Fix salt-ssh master access during pillar rendering
Browse files Browse the repository at this point in the history
This also ports #50489 into the present
  • Loading branch information
lkubb committed Oct 30, 2023
1 parent ae2ae33 commit 38ba57d
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/60002.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed gpg pillar rendering with salt-ssh
4 changes: 3 additions & 1 deletion salt/client/ssh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,9 +1196,11 @@ def run_wfunc(self):
for grain in self.target["grains"]:
opts_pkg["grains"][grain] = self.target["grains"][grain]

# Pillar compilation needs the master opts primarily,
# same as during regular operation.
popts = {}
popts.update(opts_pkg["__master_opts__"])
popts.update(opts_pkg)
popts.update(opts_pkg["__master_opts__"])
pillar = salt.pillar.Pillar(
popts,
opts_pkg["grains"],
Expand Down
4 changes: 3 additions & 1 deletion salt/client/ssh/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ def _gather_pillar(self):
"""
_opts = self.opts
popts = {}
popts.update(_opts.get("__master_opts__", {}))
# Pillar compilation needs the master opts primarily,
# same as during regular operation.
popts.update(_opts)
popts.update(_opts.get("__master_opts__", {}))
self.opts = popts
pillar = super()._gather_pillar()
self.opts = _opts
Expand Down
238 changes: 238 additions & 0 deletions tests/pytests/integration/ssh/test_pillar_compilation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import logging
import pathlib
import shutil
import subprocess
import textwrap

import pytest
from pytestshellutils.utils.processes import ProcessResult

log = logging.getLogger(__name__)


# The following fixtures are copied from pytests/functional/pillar/test_gpg.py


@pytest.fixture(scope="module")
def test_key():
"""
Private key for setting up GPG pillar environment.
"""
return textwrap.dedent(
"""\
-----BEGIN PGP PRIVATE KEY BLOCK-----
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
=06Tz
-----END PGP PRIVATE KEY BLOCK-----
"""
)


@pytest.fixture(scope="module")
def gpg_pillar_yaml():
"""
Yaml data for testing GPG pillar.
"""
return textwrap.dedent(
"""
#!yaml|gpg
secrets:
foo: |
-----BEGIN PGP MESSAGE-----
hQEMAw2B674HRhwSAQgAhTrN8NizwUv/VunVrqa4/X8t6EUulrnhKcSeb8sZS4th
W1Qz3K2NjL4lkUHCQHKZVx/VoZY7zsddBIFvvoGGfj8+2wjkEDwFmFjGE4DEsS74
ZLRFIFJC1iB/O0AiQ+oU745skQkU6OEKxqavmKMrKo3rvJ8ZCXDC470+i2/Hqrp7
+KWGmaDOO422JaSKRm5D9bQZr9oX7KqnrPG9I1+UbJyQSJdsdtquPWmeIpamEVHb
VMDNQRjSezZ1yKC4kCWm3YQbBF76qTHzG1VlLF5qOzuGI9VkyvlMaLfMibriqY73
zBbPzf6Bkp2+Y9qyzuveYMmwS4sEOuZL/PetqisWe9JGAWD/O+slQ2KRu9hNww06
KMDPJRdyj5bRuBVE4hHkkP23KrYr7SuhW2vpe7O/MvWEJ9uDNegpMLhTWruGngJh
iFndxegN9w==
=bAuo
-----END PGP MESSAGE-----
"""
)


@pytest.fixture(scope="module")
def gpg_homedir(salt_master, test_key):
"""
Setup gpg environment
"""
_gpg_homedir = pathlib.Path(salt_master.config_dir) / "gpgkeys"
_gpg_homedir.mkdir(0o700)
agent_started = False
try:
cmd_prefix = ["gpg", "--homedir", str(_gpg_homedir)]

cmd = cmd_prefix + ["--list-keys"]
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
universal_newlines=True,
)
ret = ProcessResult(
returncode=proc.returncode,
stdout=proc.stdout,
stderr=proc.stderr or "",
cmdline=proc.args,
)
log.debug("Instantiating gpg keyring...\n%s", ret)

cmd = cmd_prefix + ["--import", "--allow-secret-key-import"]
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
universal_newlines=True,
input=test_key,
)
ret = ProcessResult(
returncode=proc.returncode,
stdout=proc.stdout,
stderr=proc.stderr or "",
cmdline=proc.args,
)
log.debug("Importing keypair...:\n%s", ret)

agent_started = True

yield _gpg_homedir
finally:
if agent_started:
try:
cmd = ["gpg-connect-agent", "--homedir", str(_gpg_homedir)]
proc = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
universal_newlines=True,
input="KILLAGENT",
)
ret = ProcessResult(
returncode=proc.returncode,
stdout=proc.stdout,
stderr=proc.stderr or "",
cmdline=proc.args,
)
log.debug("Killed gpg-agent...\n%s", ret)
except (OSError, subprocess.CalledProcessError):
log.debug("No need to kill: old gnupg doesn't start the agent.")
shutil.rmtree(str(_gpg_homedir), ignore_errors=True)


@pytest.fixture(scope="module")
def pillar_setup(base_env_pillar_tree_root_dir, gpg_pillar_yaml, salt_minion):
"""
Setup gpg pillar
"""
saltutil_contents = f"""
saltutil: {{{{ salt["saltutil.runner"]("mine.get", tgt="{salt_minion.id}", fun="test.ping") | json }}}}
"""
top_file_contents = """
base:
'*':
- gpg
- saltutil
"""
with pytest.helpers.temp_file(
"top.sls", top_file_contents, base_env_pillar_tree_root_dir
), pytest.helpers.temp_file(
"gpg.sls", gpg_pillar_yaml, base_env_pillar_tree_root_dir
), pytest.helpers.temp_file(
"saltutil.sls", saltutil_contents, base_env_pillar_tree_root_dir
):
yield


@pytest.mark.skip_if_binaries_missing("gpg")
@pytest.mark.usefixtures("pillar_setup", "gpg_homedir")
def test_gpg_pillar(salt_ssh_cli):
"""
Ensure that GPG-encrypted pillars can be decrypted, i.e. the
gpg_keydir should not be overridden. This is issue #60002,
which has the same cause as the one below.
"""
ret = salt_ssh_cli.run("pillar.items")
assert ret.returncode == 0
assert isinstance(ret.data, dict)
assert ret.data
assert "secrets" in ret.data
assert "foo" in ret.data["secrets"]
assert "BEGIN PGP MESSAGE" not in ret.data["secrets"]["foo"]


@pytest.mark.usefixtures("pillar_setup")
def test_saltutil_runner(salt_ssh_cli, salt_minion, salt_run_cli):
"""
Ensure that during pillar compilation, the cache dir is not
overridden. For a history, see PR #50489 and issue #36796,
notice that the initial description is probably unrelated
to this.
"""
ret = salt_ssh_cli.run("pillar.items")
assert ret.returncode == 0
assert isinstance(ret.data, dict)
assert ret.data
assert "saltutil" in ret.data
assert isinstance(ret.data["saltutil"], dict)
assert ret.data["saltutil"]
assert salt_minion.id in ret.data["saltutil"]
assert ret.data["saltutil"][salt_minion.id] is True

0 comments on commit 38ba57d

Please sign in to comment.