atlas-iac/testing/tests/test_node_admin_access.py

76 lines
3.6 KiB
Python
Raw Normal View History

"""Node credential repair must be targeted, idempotent and silent about secrets."""
import importlib.util
from pathlib import Path
import unittest
from unittest.mock import patch, Mock
spec = importlib.util.spec_from_file_location(
"node_admin_access", Path(__file__).resolve().parents[2] / "scripts/node_admin_access.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
class NodeAccessTests(unittest.TestCase):
def setUp(self):
self.payload = {"hostname": "titan-test", "passwords": {"atlas": "synthetic-only"}}
self.root = patch.object(module.os, "geteuid", return_value=0)
self.host = patch.object(module.socket, "gethostname", return_value="titan-test")
self.account = patch.object(module.pwd, "getpwnam")
for item in [self.root, self.host, self.account]:
item.start()
self.addCleanup(item.stop)
def test_wrong_host_never_changes_password(self):
with patch.object(module.subprocess, "run") as run:
self.payload["hostname"] = "wrong-node"
with self.assertRaisesRegex(ValueError, "hostname_mismatch"):
module.run(self.payload, True)
run.assert_not_called()
def test_validate_all_accounts_before_mutation(self):
with patch.object(module.subprocess, "run") as run:
self.payload["passwords"]["other"] = "synthetic"
with self.assertRaisesRegex(ValueError, "account_not_allowed"):
module.run(self.payload, True)
run.assert_not_called()
def test_password_record_injection_rejected(self):
self.payload["passwords"]["atlas"] = "bad\nroot:injected"
with self.assertRaisesRegex(ValueError, "invalid_password"):
module.run(self.payload, True)
def test_audit_is_read_only(self):
with patch.object(module, "password_status", return_value={"vault_password_matches": False}), \
patch.object(module.subprocess, "run") as run:
result = module.run(self.payload)
run.assert_not_called()
self.assertEqual(result["changed_accounts"], [])
def test_matching_password_is_unchanged(self):
with patch.object(module, "password_status", return_value={"vault_password_matches": True}), \
patch.object(module.subprocess, "run") as run:
module.run(self.payload, True)
run.assert_not_called()
def test_restore_uses_stdin_and_returns_no_secret(self):
with patch.object(module, "password_status", side_effect=[
{"vault_password_matches": False, "locked": True},
{"vault_password_matches": True, "locked": False}]), \
patch.object(module.subprocess, "run", return_value=Mock(returncode=0)) as run:
result = module.run(self.payload, True)
self.assertEqual(run.call_args.args[0], ["/usr/sbin/chpasswd"])
self.assertEqual(run.call_args.kwargs["input"], "atlas:synthetic-only\n")
self.assertNotIn("synthetic-only", str(result))
self.assertEqual(result["changed_accounts"], ["atlas"])
self.assertTrue(result["after"]["atlas"]["vault_password_matches"])
def test_failed_update_does_not_echo_subprocess(self):
with patch.object(module, "password_status", return_value={"vault_password_matches": False}), \
patch.object(module.subprocess, "run", return_value=Mock(returncode=1, stderr="SECRET")):
with self.assertRaisesRegex(ValueError, "^password_update_failed$"):
module.run(self.payload, True)
if __name__ == "__main__":
unittest.main()