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:
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "OpsWarden Test Coverage and Code Quality"
|
||||
domain: custodian
|
||||
repo: ops-warden
|
||||
status: active
|
||||
status: done
|
||||
owner: Bernd
|
||||
topic_slug: custodian
|
||||
planning_priority: medium
|
||||
@@ -84,57 +84,57 @@ received a cert via ops-bridge but have no warden installation.
|
||||
```task
|
||||
id: WARDEN-WP-0003-T1
|
||||
state_hub_task_id: eff074ce-c027-4df5-8006-0990296592ac
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
- [ ] Create `tests/test_vault.py`
|
||||
- [ ] Test `VaultCA.sign()` success: mock `httpx.post` returning a valid
|
||||
- [x] Create `tests/test_vault.py`
|
||||
- [x] 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.HTTPStatusError` → `CAError` with status code in message
|
||||
- [ ] Test unreachable Vault: `httpx.RequestError` → `CAError` 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
|
||||
- [x] Test HTTP 403: `httpx.HTTPStatusError` → `CAError` with status code in message
|
||||
- [x] Test unreachable Vault: `httpx.RequestError` → `CAError` with fallback hint
|
||||
- [x] Test missing `VAULT_TOKEN`: `_token()` raises `CAError` before HTTP call
|
||||
- [x] Test missing role in `role_map`: `CAError` before HTTP call
|
||||
- [x] Test missing pubkey file: `CAError` before HTTP call
|
||||
|
||||
### T2 — LocalCA.generate_keypair tests
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0003-T2
|
||||
state_hub_task_id: ddfe5331-0a3b-4783-bdf4-f5ebcdf7965c
|
||||
status: todo
|
||||
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/`
|
||||
- [x] Add `TestGenerateKeypair` class to `tests/test_ca.py`
|
||||
- [x] Test success: mock `subprocess.run`; assert privkey and pubkey paths returned
|
||||
- [x] Test ssh-keygen called with `-t ed25519`, `-N ""`, `-C actor_name`
|
||||
- [x] Test existing files are unlinked before generation (write dummy files first)
|
||||
- [x] Test `CAError` raised on non-zero ssh-keygen exit code
|
||||
- [x] Test output files land in `state_dir/keys/`
|
||||
|
||||
### T3 — CLI tests
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0003-T3
|
||||
state_hub_task_id: 040ce3a1-0efb-4816-a2d9-357162dd1612
|
||||
status: todo
|
||||
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
|
||||
- [x] Create `tests/test_cli.py` using `typer.testing.CliRunner`
|
||||
- [x] `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;
|
||||
- [x] `warden issue`: exits 1 on vault backend; exits 0 on local backend (mock CA)
|
||||
- [x] `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;
|
||||
- [x] `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
|
||||
- [x] `warden inventory add / list / remove`: round-trip via tmp inventory file
|
||||
- [x] `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
|
||||
- [x] `warden cleanup --dry-run`: exits 0, no files deleted
|
||||
(after WARDEN-WP-0002 T2 adds cleanup)
|
||||
|
||||
### T4 — Real ssh-keygen integration test
|
||||
@@ -142,38 +142,38 @@ priority: high
|
||||
```task
|
||||
id: WARDEN-WP-0003-T4
|
||||
state_hub_task_id: 434fb008-103f-410c-85fd-e77b33e61fe4
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
```
|
||||
|
||||
- [ ] Create `tests/test_integration.py`
|
||||
- [ ] Mark all tests `@pytest.mark.integration`
|
||||
- [ ] Add `pytest.ini_options` to `pyproject.toml`:
|
||||
- [x] Create `tests/test_integration.py`
|
||||
- [x] Mark all tests `@pytest.mark.integration`
|
||||
- [x] 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
|
||||
- [x] 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
|
||||
- [x] Skip test if `shutil.which("ssh-keygen") is None`
|
||||
- [x] Document in README: `uv run pytest -m integration` to run real-CA tests
|
||||
|
||||
### T5 — File permissions enforcement (mode 600)
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0003-T5
|
||||
state_hub_task_id: ac146fe6-d1fd-4186-91bd-6f098de72449
|
||||
status: todo
|
||||
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
|
||||
- [x] `ca.py` `LocalCA.sign()`: call `os.chmod(dest, 0o600)` after `shutil.copy2`
|
||||
- [x] `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
|
||||
- [x] `vault.py` `VaultCA.sign()`: call `os.chmod(dest, 0o600)` after `dest.write_text`
|
||||
- [x] `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
|
||||
- [x] Add `check_file_permissions` to `run_scorecard()`
|
||||
- [x] 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)
|
||||
|
||||
@@ -182,28 +182,28 @@ priority: medium
|
||||
```task
|
||||
id: WARDEN-WP-0003-T6
|
||||
state_hub_task_id: 1c9f1987-7b11-43c1-a5e3-c2fd8d1c1589
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
```
|
||||
|
||||
- [ ] `cli.py` `status()`: add
|
||||
- [x] `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>`
|
||||
- [x] When `--state-dir` is provided: use it directly, skip `_load_cfg()` entirely
|
||||
- [x] When absent: load config as today
|
||||
- [x] 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
|
||||
- [x] `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
|
||||
- [x] `uv run pytest -m integration` succeeds (requires ssh-keygen in PATH)
|
||||
- [x] `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
|
||||
- [x] `ls -la ~/.local/state/warden/*.pub` shows mode 600 for newly signed certs
|
||||
- [x] 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 .`
|
||||
- [x] `warden status --state-dir /tmp/some-dir` runs without a `warden.yaml`
|
||||
- [x] All lints pass: `uv run ruff check .`
|
||||
|
||||
Reference in New Issue
Block a user