generated from coulomb/repo-seed
feat(warden): WARDEN-WP-0003 — test coverage, permissions, status --state-dir
- File permissions: os.chmod(cert, 0o600) after every sign in LocalCA and VaultCA; chmod(privkey, 0o600) and chmod(pubkey, 0o644) after generate_keypair - Scorecard: add check_file_permissions() that flags world/group-readable cert and key files; run_scorecard now returns 6 checks - warden status --state-dir: bypasses config loading entirely for operators who have a cert but no warden.yaml installed - tests/test_vault.py: 11 VaultCA unit tests covering success, HTTP 403, RequestError, missing token, missing role, missing pubkey, TTL enforcement, eviction, signatures log, and cert mode 600 - tests/test_ca.py: generate_keypair tests (paths, args, overwrite, error, permissions) and cert mode 600 assertion after sign - tests/test_scorecard.py: file_permissions check tests (pass, fail cert, fail keys dir); scorecard count updated to 6 - tests/test_cli.py: covers sign, issue, status, scorecard, inventory, log, cleanup commands using CliRunner and tmp config/inventory files - tests/test_integration.py: @pytest.mark.integration tests against real ssh-keygen; excluded from default suite via pyproject addopts - pyproject.toml: addopts = "-m 'not integration'", integration marker declared All 100 unit tests pass; 3 integration tests pass; ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
121
tests/test_ca.py
121
tests/test_ca.py
@@ -335,6 +335,28 @@ def test_local_ca_sign_evicts_existing_cert(tmp_path):
|
||||
assert len(list(state.glob("agt-state-hub-bridge-cert.pub"))) == 1
|
||||
|
||||
|
||||
def test_local_ca_sign_cert_mode_600(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
pubkey = tmp_path / "key.pub"
|
||||
pubkey.write_text("ssh-ed25519 AAAA actor-key")
|
||||
state = tmp_path / "state"
|
||||
|
||||
spec = CertSpec(
|
||||
actor_name="agt-state-hub-bridge",
|
||||
actor_type=ActorType.AGT,
|
||||
pubkey_path=pubkey,
|
||||
ttl_hours=24,
|
||||
principals=["agt-task-bridge"],
|
||||
identity="agt-state-hub-bridge",
|
||||
)
|
||||
with patch("warden.ca.subprocess.run", side_effect=_mock_run_factory(CERT_CONTENT)):
|
||||
ca = LocalCA(ca_key, state)
|
||||
record = ca.sign(spec)
|
||||
|
||||
assert oct(record.cert_path.stat().st_mode & 0o777) == oct(0o600)
|
||||
|
||||
|
||||
def test_local_ca_sign_writes_signature_log(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
@@ -360,3 +382,102 @@ def test_local_ca_sign_writes_signature_log(tmp_path):
|
||||
assert entry["actor"] == "agt-state-hub-bridge"
|
||||
assert entry["backend"] == "local"
|
||||
assert entry["ttl_hours"] == 24
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LocalCA.generate_keypair
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _mock_keygen_gen(cmd, **kwargs):
|
||||
"""Mock for generate_keypair: writes privkey and pubkey based on -f arg."""
|
||||
result = MagicMock()
|
||||
result.returncode = 0
|
||||
result.stdout = ""
|
||||
result.stderr = ""
|
||||
if "-f" in cmd:
|
||||
idx = cmd.index("-f")
|
||||
privkey = Path(cmd[idx + 1])
|
||||
privkey.parent.mkdir(parents=True, exist_ok=True)
|
||||
privkey.write_text("fake private key")
|
||||
(privkey.parent / (privkey.name + ".pub")).write_text("ssh-ed25519 AAAA pubkey")
|
||||
return result
|
||||
|
||||
|
||||
def test_generate_keypair_returns_paths(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
ca = LocalCA(ca_key, tmp_path / "state")
|
||||
|
||||
with patch("warden.ca.subprocess.run", side_effect=_mock_keygen_gen):
|
||||
privkey, pubkey = ca.generate_keypair("agt-test")
|
||||
|
||||
assert privkey.name == "agt-test_ed25519"
|
||||
assert pubkey.name == "agt-test_ed25519.pub"
|
||||
assert str(privkey).endswith("state/keys/agt-test_ed25519")
|
||||
|
||||
|
||||
def test_generate_keypair_ed25519_no_passphrase(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
ca = LocalCA(ca_key, tmp_path / "state")
|
||||
|
||||
calls = []
|
||||
|
||||
def capturing_mock(cmd, **kwargs):
|
||||
calls.append(cmd)
|
||||
return _mock_keygen_gen(cmd, **kwargs)
|
||||
|
||||
with patch("warden.ca.subprocess.run", side_effect=capturing_mock):
|
||||
ca.generate_keypair("agt-test")
|
||||
|
||||
assert len(calls) == 1
|
||||
cmd = calls[0]
|
||||
assert "-t" in cmd and cmd[cmd.index("-t") + 1] == "ed25519"
|
||||
assert "-N" in cmd and cmd[cmd.index("-N") + 1] == ""
|
||||
assert "-C" in cmd and cmd[cmd.index("-C") + 1] == "agt-test"
|
||||
|
||||
|
||||
def test_generate_keypair_overwrites_existing(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
state = tmp_path / "state"
|
||||
keys_dir = state / "keys"
|
||||
keys_dir.mkdir(parents=True)
|
||||
old_priv = keys_dir / "agt-test_ed25519"
|
||||
old_pub = keys_dir / "agt-test_ed25519.pub"
|
||||
old_priv.write_text("old key")
|
||||
old_pub.write_text("old pubkey")
|
||||
|
||||
ca = LocalCA(ca_key, state)
|
||||
with patch("warden.ca.subprocess.run", side_effect=_mock_keygen_gen):
|
||||
privkey, pubkey = ca.generate_keypair("agt-test")
|
||||
|
||||
assert privkey.read_text() == "fake private key"
|
||||
|
||||
|
||||
def test_generate_keypair_ca_error_on_failure(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
ca = LocalCA(ca_key, tmp_path / "state")
|
||||
|
||||
def fail_run(cmd, **kwargs):
|
||||
result = MagicMock()
|
||||
result.returncode = 1
|
||||
result.stderr = "failed to generate key"
|
||||
return result
|
||||
|
||||
with patch("warden.ca.subprocess.run", side_effect=fail_run):
|
||||
with pytest.raises(CAError, match="Key generation failed"):
|
||||
ca.generate_keypair("agt-test")
|
||||
|
||||
|
||||
def test_generate_keypair_sets_permissions(tmp_path):
|
||||
ca_key = tmp_path / "ca_key"
|
||||
ca_key.write_text("fake-ca")
|
||||
ca = LocalCA(ca_key, tmp_path / "state")
|
||||
|
||||
with patch("warden.ca.subprocess.run", side_effect=_mock_keygen_gen):
|
||||
privkey, pubkey = ca.generate_keypair("agt-test")
|
||||
|
||||
assert oct(privkey.stat().st_mode & 0o777) == oct(0o600)
|
||||
assert oct(pubkey.stat().st_mode & 0o777) == oct(0o644)
|
||||
|
||||
Reference in New Issue
Block a user