Skip to content

Fix runas when using the onedir bundled packages #62617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
37 changes: 29 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,50 @@ 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)
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
232 changes: 232 additions & 0 deletions tests/pytests/unit/modules/test_cmdmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import tempfile

import pytest
import salt.grains.extra
import salt.modules.cmdmod as cmdmod
import salt.utils.files
import salt.utils.platform
Expand Down Expand Up @@ -809,3 +810,234 @@ 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"
same_shell = False
if salt.grains.extra.shell()["shell"] == shell:
same_shell = True

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