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:
2026-05-15 17:05:38 +02:00
parent cd1e385bc1
commit f3547acd0b
11 changed files with 974 additions and 55 deletions

View File

@@ -173,16 +173,22 @@ def issue(
def status(
actor_name: Annotated[Optional[str], typer.Argument(help="Actor name (omit for all)")] = None,
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
state_dir_override: Annotated[Optional[Path], typer.Option("--state-dir", help="State dir path (bypasses config)")] = None,
) -> None:
"""Show certificate status. Exits 1 if any cert is expired."""
cfg = _load_cfg()
now = datetime.now(timezone.utc)
if state_dir_override is not None:
state_dir = state_dir_override
else:
cfg = _load_cfg()
state_dir = cfg.state_dir
if actor_name:
cert_path = cfg.state_dir / f"{actor_name}-cert.pub"
cert_path = state_dir / f"{actor_name}-cert.pub"
paths = [cert_path] if cert_path.exists() else []
else:
paths = sorted(cfg.state_dir.glob("*-cert.pub")) if cfg.state_dir.exists() else []
paths = sorted(state_dir.glob("*-cert.pub")) if state_dir.exists() else []
if not paths:
msg = (