fix: keep the sudo password out of files written as root (0.2.1)
CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 27s
CI / test (3.12) (push) Successful in 25s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 25s
CI / test (3.10) (push) Successful in 27s
CI / test (3.11) (push) Successful in 27s
CI / test (3.12) (push) Successful in 25s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 24s
CI / test (3.12) (pull_request) Successful in 25s
run_command prepended the sudo password to the caller's stdin. A sudo that does not prompt left it for the command, and fix_snmp's 'cat > snmpd.conf' wrote it as the configuration's first line before making the file world-readable. run_command now refuses stdin to a privileged command while a sudo password is set. fix_snmp stages the configuration in a mktemp file without privilege, and root installs it next to the target and renames it over, so nothing root runs follows a symlink. Closes #8
This commit is contained in:
1 parent
992bc6d5e9
commit
707e048792
4 files changed
+122
-20
No files matched your search
+54
-6
@@ -9,6 +9,7 @@ from napalm.base import NetworkDriver
|
||||
from napalm_device_types import OSDriver
|
||||
|
||||
from napalm_bsd import FreeBSDDriver, OpenBSDDriver
|
||||
from napalm_bsd import base
|
||||
from napalm_bsd.base import BsdDriver
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
@@ -463,6 +464,7 @@ class TestSnmp:
|
||||
"""net-snmp from packages, as decided in NetOrk/netork#800: UCD-SNMP-MIB,
|
||||
the same health metrics as on Linux."""
|
||||
|
||||
STAGED = "/tmp/napalm-bsd-stage.Ab12Cd"
|
||||
PROBE_OK = "STRING: FreeBSD host 15.1-RELEASE-p4 FreeBSD 15.1-RELEASE-p4 GENERIC amd64"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -483,13 +485,13 @@ class TestSnmp:
|
||||
],
|
||||
)
|
||||
def test_fix_snmp_installs_configures_and_starts_net_snmp(self, cls, install, conf, start):
|
||||
driver = _channel(cls, {BsdDriver.SNMP_PROBE: self.PROBE_OK})
|
||||
stdin_seen = {}
|
||||
driver = _channel(cls, {base._STAGE: self.STAGED, BsdDriver.SNMP_PROBE: self.PROBE_OK})
|
||||
stdin_seen = []
|
||||
run = driver.run_command
|
||||
|
||||
def recording(command, *, privileged=False, timeout=60, stdin=None):
|
||||
if stdin is not None:
|
||||
stdin_seen[command] = stdin
|
||||
stdin_seen.append((command, privileged, stdin))
|
||||
return run(command, privileged=privileged, timeout=timeout, stdin=stdin)
|
||||
|
||||
driver.run_command = recording # type: ignore[method-assign]
|
||||
@@ -498,13 +500,22 @@ class TestSnmp:
|
||||
assert result["success"] is True
|
||||
commands = [c for c, _ in driver.calls]
|
||||
assert commands[0] == install
|
||||
assert any(conf in c and "cat >" in c for c in commands)
|
||||
installed = next(c for c, p in driver.calls if p and "install -m 644" in c)
|
||||
assert conf in installed and self.STAGED in installed
|
||||
assert start in commands
|
||||
assert commands[-1] == BsdDriver.SNMP_PROBE
|
||||
assert all(p for c, p in driver.calls if c != BsdDriver.SNMP_PROBE) # root for the setup
|
||||
written = next(v for k, v in stdin_seen.items() if conf in k).decode()
|
||||
assert all(p for c, p in driver.calls if c not in (BsdDriver.SNMP_PROBE, base._STAGE))
|
||||
# The configuration reaches the host only through the unprivileged stage (#8).
|
||||
assert [(c, p) for c, p, _ in stdin_seen] == [(base._STAGE, False)]
|
||||
written = stdin_seen[0][2].decode()
|
||||
assert "agentAddress udp:161" in written and "rocommunity public" in written
|
||||
|
||||
def test_a_failed_stage_runs_nothing_as_root(self):
|
||||
driver = _channel(FreeBSDDriver, {base._STAGE: ("mktemp: permission denied", 1)})
|
||||
result = driver.run_device_action("fix_snmp")
|
||||
assert result["success"] is False
|
||||
assert not any(p and "install -m 644" in c for c, p in driver.calls)
|
||||
|
||||
def test_a_failed_install_stops_and_says_so(self):
|
||||
driver = _channel(
|
||||
FreeBSDDriver,
|
||||
@@ -550,6 +561,43 @@ class TestSnmp:
|
||||
assert driver.get_snmp_config() is None
|
||||
|
||||
|
||||
class TestPrivilegedStdin:
|
||||
"""A privileged command never gets stdin while a sudo password is set (#8).
|
||||
|
||||
``sudo -S`` reads the password from stdin; a sudo that does not prompt
|
||||
(NOPASSWD, a cached timestamp) leaves it for the command, which would write
|
||||
it into whatever the data goes to."""
|
||||
|
||||
def _driver(self, monkeypatch, *, sudo_password, root=False):
|
||||
sent = []
|
||||
monkeypatch.setattr(
|
||||
base, "run_on_transport",
|
||||
lambda transport, line, stdin=None, timeout=60: sent.append((line, stdin)) or _Result(),
|
||||
)
|
||||
args = {"sudo_password": sudo_password} if sudo_password else {}
|
||||
driver = FreeBSDDriver("host", "netork", "", optional_args=args)
|
||||
driver._transport = lambda: object() # type: ignore[method-assign]
|
||||
driver._root = root
|
||||
return driver, sent
|
||||
|
||||
def test_refuses_stdin_to_a_privileged_command_with_a_sudo_password(self, monkeypatch):
|
||||
driver, sent = self._driver(monkeypatch, sudo_password="s3cret")
|
||||
with pytest.raises(ValueError, match="no stdin"):
|
||||
driver.run_command("cat > /etc/x", privileged=True, stdin=b"data")
|
||||
assert sent == []
|
||||
|
||||
def test_a_privileged_command_without_stdin_gets_only_the_password(self, monkeypatch):
|
||||
driver, sent = self._driver(monkeypatch, sudo_password="s3cret")
|
||||
driver.run_command("id -u", privileged=True)
|
||||
assert sent == [("sudo -S -p '' sh -c 'id -u'", b"s3cret\n")]
|
||||
|
||||
@pytest.mark.parametrize("sudo_password, root", [(None, False), ("s3cret", True)])
|
||||
def test_stdin_is_fine_where_no_password_goes_first(self, monkeypatch, sudo_password, root):
|
||||
driver, sent = self._driver(monkeypatch, sudo_password=sudo_password, root=root)
|
||||
driver.run_command("cat > /tmp/x", privileged=True, stdin=b"data")
|
||||
assert sent[0][1] == b"data"
|
||||
|
||||
|
||||
class TestHostStatus:
|
||||
"""napalm-device-types' host status, carried over the exec channel; on
|
||||
FreeBSD it compares the installed with the running kernel (4.1.0)."""
|
||||
|
||||
Reference in new issue
Block a user