Files
ops-warden/workplans/WARDEN-WP-0003-test-coverage-and-quality.md
tegwick f3547acd0b 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>
2026-05-15 17:05:38 +02:00

7.7 KiB

id, type, title, domain, repo, status, owner, topic_slug, planning_priority, planning_order, created, updated, state_hub_workstream_id
id type title domain repo status owner topic_slug planning_priority planning_order created updated state_hub_workstream_id
WARDEN-WP-0003 workplan OpsWarden Test Coverage and Code Quality custodian ops-warden done Bernd custodian medium 3 2026-05-15 2026-05-15 cb2bbf3c-848a-4af6-ba64-8361e64cd4d7

WARDEN-WP-0003 — Test Coverage and Code Quality

Scope: Close the test coverage gaps left after WARDEN-WP-0001: VaultCA has zero tests, generate_keypair is untested, no CLI tests exist, and no real ssh-keygen integration test was written. Also fix file permission enforcement (security) and add --state-dir override to warden status (usability).

Out of scope: Functional behaviour changes (WARDEN-WP-0002), Vault cluster setup, host-side tooling.


Goal

After this workplan:

  1. VaultCA has a full unit test suite covering success, auth failure, network failure, and role-map misconfiguration.
  2. generate_keypair has direct unit tests alongside the existing sign tests.
  3. A tests/test_cli.py covers every command's exit codes and output shape.
  4. A tests/test_integration.py marked @pytest.mark.integration exercises LocalCA.sign() against a real ssh-keygen without any mocking.
  5. Cert and key files written by warden are always mode 600; a scorecard check flags world-readable files.
  6. warden status --state-dir <path> works without a warden.yaml.

Reference Documents

Document Location
WARDEN-WP-0001 workplans/WARDEN-WP-0001-initial-implementation.md
WARDEN-WP-0002 workplans/WARDEN-WP-0002-correctness-and-completeness.md
CertCommandInterface wiki/CertCommandInterface.md

Design Decisions

Integration tests: separate marker, not separate directory

Use @pytest.mark.integration and skip when ssh-keygen is not in PATH. Tests live in tests/test_integration.py. The unit suite (uv run pytest) excludes integration tests via pytest.ini_options addopts = "-m 'not integration'"; uv run pytest -m integration runs them explicitly. This keeps CI fast while making the real-ssh-keygen path easy to invoke manually.

File permissions: at write time, not at read time

os.chmod(path, 0o600) is called immediately after each path.write_text() or shutil.copy2() that writes a key or cert. No deferred or scheduled chmod. The scorecard check catches files that were written by older versions of warden or by external tools.

warden status --state-dir: config bypass, not config optional

When --state-dir is provided, skip load_config() entirely — don't try to load a partial config. This makes the flag useful on remote machines that have received a cert via ops-bridge but have no warden installation.


Tasks

T1 — VaultCA tests

id: WARDEN-WP-0003-T1
state_hub_task_id: eff074ce-c027-4df5-8006-0990296592ac
status: done
priority: high
  • Create tests/test_vault.py
  • Test VaultCA.sign() success: mock httpx.post returning a valid signed_key; assert CertRecord fields; assert cert file written to state_dir
  • Test HTTP 403: httpx.HTTPStatusErrorCAError with status code in message
  • Test unreachable Vault: httpx.RequestErrorCAError with fallback hint
  • Test missing VAULT_TOKEN: _token() raises CAError before HTTP call
  • Test missing role in role_map: CAError before HTTP call
  • Test missing pubkey file: CAError before HTTP call

T2 — LocalCA.generate_keypair tests

id: WARDEN-WP-0003-T2
state_hub_task_id: ddfe5331-0a3b-4783-bdf4-f5ebcdf7965c
status: done
priority: medium
  • Add TestGenerateKeypair class to tests/test_ca.py
  • Test success: mock subprocess.run; assert privkey and pubkey paths returned
  • Test ssh-keygen called with -t ed25519, -N "", -C actor_name
  • Test existing files are unlinked before generation (write dummy files first)
  • Test CAError raised on non-zero ssh-keygen exit code
  • Test output files land in state_dir/keys/

T3 — CLI tests

id: WARDEN-WP-0003-T3
state_hub_task_id: 040ce3a1-0efb-4816-a2d9-357162dd1612
status: done
priority: high
  • Create tests/test_cli.py using typer.testing.CliRunner
  • warden sign: exits 0 and stdout is cert text (mock CA); exits 1 on unknown actor; exits 1 on config error
  • warden issue: exits 1 on vault backend; exits 0 on local backend (mock CA)
  • warden status: exits 0 and prints "no cert" message when state_dir empty; exits 1 when cert is expired (mock parse_cert_metadata)
  • warden scorecard: exits 0 on clean inventory + empty state_dir; exits 1 when a check fails
  • warden inventory add / list / remove: round-trip via tmp inventory file
  • warden log: exits 0 with empty output when no log; --json is valid JSON (after WARDEN-WP-0002 T3 adds the log command)
  • warden cleanup --dry-run: exits 0, no files deleted (after WARDEN-WP-0002 T2 adds cleanup)

T4 — Real ssh-keygen integration test

id: WARDEN-WP-0003-T4
state_hub_task_id: 434fb008-103f-410c-85fd-e77b33e61fe4
status: done
priority: medium
  • Create tests/test_integration.py
  • Mark all tests @pytest.mark.integration
  • Add pytest.ini_options to pyproject.toml: addopts = "-m 'not integration'" so unit suite skips them by default
  • Test LocalCA.sign() end-to-end: generate a real CA keypair and actor keypair via subprocess ssh-keygen in tmp_path; call LocalCA.sign(); assert CertRecord.valid_before > datetime.now(utc); assert cert file exists; assert parse_cert_metadata() succeeds on it without mocking
  • Skip test if shutil.which("ssh-keygen") is None
  • Document in README: uv run pytest -m integration to run real-CA tests

T5 — File permissions enforcement (mode 600)

id: WARDEN-WP-0003-T5
state_hub_task_id: ac146fe6-d1fd-4186-91bd-6f098de72449
status: done
priority: medium
  • ca.py LocalCA.sign(): call os.chmod(dest, 0o600) after shutil.copy2
  • ca.py LocalCA.generate_keypair(): call os.chmod(privkey, 0o600) and os.chmod(pubkey, 0o644) after generation
  • vault.py VaultCA.sign(): call os.chmod(dest, 0o600) after dest.write_text
  • scorecard.py: add check_file_permissions(state_dir) — flag any *-cert.pub or keys/* file where stat().st_mode & 0o044 != 0
  • Add check_file_permissions to run_scorecard()
  • Update test_ca.py: assert os.chmod called with correct mode after sign and generate_keypair (patch os.chmod or check stat on actual files in tmp_path)

T6 — warden status --state-dir override

id: WARDEN-WP-0003-T6
state_hub_task_id: 1c9f1987-7b11-43c1-a5e3-c2fd8d1c1589
status: done
priority: low
  • cli.py status(): add state_dir_override: Annotated[Optional[Path], typer.Option("--state-dir")] = None
  • When --state-dir is provided: use it directly, skip _load_cfg() entirely
  • When absent: load config as today
  • Add test in test_cli.py: invoke warden status --state-dir <tmp_path> without a config file; assert exit 0

Acceptance Criteria

  • uv run pytest runs unit suite only; all pass; VaultCA and generate_keypair covered
  • uv run pytest -m integration succeeds (requires ssh-keygen in PATH)
  • test_cli.py covers all commands; no mocked subprocess in CLI tests where avoidable (use tmp inventory files and mocked CA)
  • ls -la ~/.local/state/warden/*.pub shows mode 600 for newly signed certs
  • Scorecard file_permissions check passes on a clean state dir; fails on a world-readable cert
  • warden status --state-dir /tmp/some-dir runs without a warden.yaml
  • All lints pass: uv run ruff check .