Skip to content

Commit

Permalink
Fix runas when using the onedir bundled packages
Browse files Browse the repository at this point in the history
  • Loading branch information
Megan Wilhite authored and s0undt3ch committed Sep 14, 2022
1 parent d2dfdc0 commit f42caab
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 8 deletions.
1 change: 1 addition & 0 deletions changelog/62565.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix runas with cmd module when using the onedir bundled packages
38 changes: 30 additions & 8 deletions salt/modules/cmdmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import salt.utils.files
import salt.utils.json
import salt.utils.path
import salt.utils.pkg
import salt.utils.platform
import salt.utils.powershell
import salt.utils.stringutils
Expand Down Expand Up @@ -508,30 +509,51 @@ def _run(
env_cmd.extend(["-s", "--", shell, "-c"])
else:
env_cmd.extend(["-i", "--"])
env_cmd.extend([sys.executable])
elif __grains__["os"] in ["FreeBSD"]:
env_cmd = (
env_cmd = [
"su",
"-",
runas,
"-c",
"{} -c {}".format(shell, sys.executable),
)
]
elif __grains__["os_family"] in ["Solaris"]:
env_cmd = ("su", "-", runas, "-c", sys.executable)
env_cmd = ["su", "-", runas, "-c"]
elif __grains__["os_family"] in ["AIX"]:
env_cmd = ("su", "-", runas, "-c", sys.executable)
env_cmd = ["su", "-", runas, "-c"]
else:
env_cmd = ["su", "-s", shell, "-", runas, "-c"]

if not salt.utils.pkg.check_bundled():
if __grains__["os"] in ["FreeBSD"]:
env_cmd.extend(["{} -c {}".format(shell, sys.executable)])
else:
env_cmd.extend([sys.executable])
else:
env_cmd = ("su", "-s", shell, "-", runas, "-c", sys.executable)
with tempfile.NamedTemporaryFile("w", delete=False) as fp:
if __grains__["os"] in ["FreeBSD"]:
env_cmd.extend(
[
"{} -c {} python {}".format(
shell, sys.executable, fp.name
)
]
)
else:
env_cmd.extend(["{} python {}".format(sys.executable, fp.name)])
fp.write(py_code)
fp.seek(0)
shutil.chown(fp.name, runas)

msg = "env command: {}".format(env_cmd)
log.debug(log_callback(msg))

env_bytes, env_encoded_err = subprocess.Popen(
env_cmd,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
).communicate(salt.utils.stringutils.to_bytes(py_code))
if salt.utils.pkg.check_bundled():
os.remove(fp.name)
marker_count = env_bytes.count(marker_b)
if marker_count == 0:
# Possibly PAM prevented the login
Expand Down
10 changes: 10 additions & 0 deletions salt/utils/pkg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os
import re
import sys

import salt.utils.data
import salt.utils.files
Expand Down Expand Up @@ -92,3 +93,12 @@ def match_version(desired, available, cmp_func=None, ignore_epoch=False):
):
return candidate
return None


def check_bundled():
"""
Gather run-time information to indicate if we are running from source or bundled.
"""
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
return True
return False
25 changes: 25 additions & 0 deletions tests/pytests/functional/modules/cmd/test_runas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest
import salt.modules.cmdmod as cmdmod


@pytest.fixture(scope="module")
def account():
with pytest.helpers.create_account(create_group=True) as _account:
yield _account


@pytest.fixture(scope="module")
def configure_loader_modules():
return {
cmdmod: {
"__grains__": {"os": "linux", "os_family": "linux"},
}
}


@pytest.mark.skip_on_windows
@pytest.mark.skip_if_not_root
def test_run_as(account, caplog):
ret = cmdmod.run("id", runas=account.username)
assert "gid={}".format(account.info.gid) in ret
assert "uid={}".format(account.info.uid) in ret
202 changes: 202 additions & 0 deletions tests/pytests/unit/modules/test_cmdmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,3 +809,205 @@ def test_cmd_script_saltenv_from_config_windows():
assert mock_cp_get_template.call_args[0][3] == "base"
assert mock_run.call_count == 2
assert mock_run.call_args[1]["saltenv"] == "base"


@pytest.mark.parametrize(
"test_os,test_family",
[
("FreeBSD", "FreeBSD"),
("linux", "Solaris"),
("linux", "AIX"),
("linux", "linux"),
],
)
@pytest.mark.skip_on_darwin
@pytest.mark.skip_on_windows
def test_runas_env_all_os(test_os, test_family):
"""
cmd.run executes command and the environment is returned
when the runas parameter is specified
on all different OS types and os_family
"""
bundled = [False, True]

for _bundled in bundled:
with patch("pwd.getpwnam") as getpwnam_mock:
with patch("subprocess.Popen") as popen_mock:
popen_mock.return_value = Mock(
communicate=lambda *args, **kwags: [b"", None],
pid=lambda: 1,
retcode=0,
)
file_name = "/tmp/doesnotexist"

with patch.dict(
cmdmod.__grains__, {"os": test_os, "os_family": test_family}
):
with patch("salt.utils.pkg.check_bundled", return_value=_bundled):
with patch("shutil.chown"):
with patch("os.remove"):
with patch.object(
tempfile, "NamedTemporaryFile"
) as mock_fp:
mock_fp.return_value.__enter__.return_value.name = (
file_name
)
if sys.platform.startswith(("freebsd", "openbsd")):
shell = "/bin/sh"
else:
shell = "/bin/bash"
_user = "foobar"
cmdmod._run(
"ls",
cwd=tempfile.gettempdir(),
runas=_user,
shell=shell,
)
if not _bundled:
if test_family in ("Solaris", "AIX"):
env_cmd = ["su", "-", _user, "-c"]
elif test_os == "FreeBSD":
env_cmd = ["su", "-", _user, "-c"]
else:
env_cmd = [
"su",
"-s",
shell,
"-",
_user,
"-c",
]
if test_os == "FreeBSD":
env_cmd.extend(
[
"{} -c {}".format(
shell, sys.executable
)
]
)
else:
env_cmd.extend([sys.executable])
assert (
popen_mock.call_args_list[0][0][0]
== env_cmd
)
else:
if test_family in ("Solaris", "AIX"):
env_cmd = ["su", "-", _user, "-c"]
elif test_os == "FreeBSD":
env_cmd = ["su", "-", _user, "-c"]
else:
env_cmd = [
"su",
"-s",
shell,
"-",
_user,
"-c",
]
if test_os == "FreeBSD":
env_cmd.extend(
[
"{} -c {} python {}".format(
shell, sys.executable, file_name
)
]
)
else:
env_cmd.extend(
[
"{} python {}".format(
sys.executable, file_name
)
]
)
assert (
popen_mock.call_args_list[0][0][0]
== env_cmd
)


@pytest.mark.skip_on_darwin
@pytest.mark.skip_on_windows
def test_runas_env_sudo_group():
"""
cmd.run executes command and the environment is returned
when the runas parameter is specified
when group is passed and use_sudo=True
"""
bundled = [False, True]

for _bundled in bundled:

with patch("pwd.getpwnam") as getpwnam_mock:
with patch("subprocess.Popen") as popen_mock:
popen_mock.return_value = Mock(
communicate=lambda *args, **kwags: [b"", None],
pid=lambda: 1,
retcode=0,
)
file_name = "/tmp/doesnotexist"

with patch.dict(
cmdmod.__grains__, {"os": "linux", "os_family": "linux"}
):
with patch("grp.getgrnam"):
with patch(
"salt.utils.pkg.check_bundled", return_value=_bundled
):
with patch("shutil.chown"):
with patch("os.remove"):
with patch.object(
tempfile, "NamedTemporaryFile"
) as mock_fp:
mock_fp.return_value.__enter__.return_value.name = (
file_name
)
if sys.platform.startswith(
("freebsd", "openbsd")
):
shell = "/bin/sh"
else:
shell = "/bin/bash"
_user = "foobar"
_group = "foobar"

cmdmod._run(
"ls",
cwd=tempfile.gettempdir(),
runas=_user,
shell=shell,
group=_group,
)
if not _bundled:
assert popen_mock.call_args_list[0][0][
0
] == [
"sudo",
"-u",
_user,
"-g",
_group,
"-s",
"--",
shell,
"-c",
sys.executable,
]
else:
assert popen_mock.call_args_list[0][0][
0
] == [
"sudo",
"-u",
_user,
"-g",
_group,
"-s",
"--",
shell,
"-c",
"{} python {}".format(
sys.executable, file_name
),
]

0 comments on commit f42caab

Please sign in to comment.