aboutsummaryrefslogtreecommitdiff
path: root/tests/installer-steps/test_replace_sudoers_pacnew.py
blob: 8bfa91491416baf60b11be73b1699c832d1823da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""Test the guarded sudoers.pacnew replacement.

Bug (Minor, hard consequence): the installer did
    [ -f /etc/sudoers.pacnew ] && cp /etc/sudoers.pacnew /etc/sudoers
with no validation. A malformed pacnew replaces /etc/sudoers with a file sudo
refuses to parse, locking out privilege escalation right before the NOPASSWD
rule is appended. replace_sudoers_pacnew now runs `visudo -cf` on the pacnew
first and only copies a file that validates; a failure warns (non-fatal) and
leaves the working sudoers in place.

Method: sed-extract replace_sudoers_pacnew from the real `archsetup`, pass a
temp pacnew + temp target (the function's positional-arg testability seam), and
drive it with a fake visudo (controlled validation result) and a fake
error_warn recorder.

Run from repo root:
    python3 -m unittest tests.installer-steps.test_replace_sudoers_pacnew
"""

import os
import subprocess
import tempfile
import textwrap
import unittest

REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
ARCHSETUP = os.path.join(REPO_ROOT, "archsetup")


def run(*, pacnew_exists, visudo_rc):
    """Run replace_sudoers_pacnew with a temp pacnew/target and fake visudo."""
    with tempfile.TemporaryDirectory() as d:
        pacnew = os.path.join(d, "sudoers.pacnew")
        target = os.path.join(d, "sudoers")
        with open(target, "w") as f:
            f.write("ORIGINAL\n")
        if pacnew_exists:
            with open(pacnew, "w") as f:
                f.write("NEW\n")
        script = textwrap.dedent(f"""\
            logfile=/dev/null
            display() {{ :; }}
            visudo() {{ return {visudo_rc}; }}
            error_warn() {{ echo "WARN: $1"; return 1; }}
            source <(sed -n '/^replace_sudoers_pacnew() {{/,/^}}/p' "{ARCHSETUP}")
            replace_sudoers_pacnew "{pacnew}" "{target}"
            echo "RC=$?"
            echo "TARGET=[$(cat "{target}")]"
        """)
        return subprocess.run(
            ["bash", "-c", script], capture_output=True, text=True, timeout=10,
        )


class ReplaceSudoersPacnew(unittest.TestCase):
    def test_valid_pacnew_is_copied(self):
        r = run(pacnew_exists=True, visudo_rc=0)
        self.assertIn("TARGET=[NEW]", r.stdout)
        self.assertNotIn("WARN", r.stdout)

    def test_invalid_pacnew_warns_and_keeps_original(self):
        r = run(pacnew_exists=True, visudo_rc=1)
        self.assertIn("WARN", r.stdout,
                      "a pacnew that fails visudo must warn, not clobber sudoers")
        self.assertIn("TARGET=[ORIGINAL]", r.stdout,
                      "the working sudoers must survive a malformed pacnew")

    def test_no_pacnew_is_a_noop(self):
        r = run(pacnew_exists=False, visudo_rc=0)
        self.assertIn("RC=0", r.stdout)
        self.assertIn("TARGET=[ORIGINAL]", r.stdout)
        self.assertNotIn("WARN", r.stdout)


if __name__ == "__main__":
    unittest.main()