diff --git a/workplans/BRIDGE-WP-0004-directive-alignment.md b/workplans/BRIDGE-WP-0004-directive-alignment.md index 976b7d9..449d451 100644 --- a/workplans/BRIDGE-WP-0004-directive-alignment.md +++ b/workplans/BRIDGE-WP-0004-directive-alignment.md @@ -4,7 +4,7 @@ type: workplan title: "AccessManagementDirective Alignment" domain: custodian repo: ops-bridge -status: draft +status: active owner: Bernd topic_slug: custodian created: "2026-03-28" @@ -118,6 +118,14 @@ SIEM auditability. ## Tasks ### T1 — ActorType enum + +```task +id: BRIDGE-WP-0004-T1 +state_hub_task_id: 40c7f818-8233-4b84-9a0e-5f5359a47504 +status: todo +priority: high +``` + - [ ] `models.py`: replace `actor_class: str` in `ActorInfo` with `actor_type: ActorType` - [ ] `config.py`: accept legacy `"human"` → `ActorType.ADM` and `"automation"` → `ActorType.ATM` with a `DeprecationWarning`; reject unknown values @@ -127,12 +135,28 @@ SIEM auditability. - [ ] Update tests ### T2 — cert_command config field + +```task +id: BRIDGE-WP-0004-T2 +state_hub_task_id: d69ac3b8-6c68-4da0-976f-0cce2ee626d6 +status: todo +priority: high +``` + - [ ] `models.py`: add `cert_command: Optional[str] = None` to `TunnelConfig` - [ ] `config.py`: parse `cert_command` from tunnel YAML; no validation of the string content (shell-level freedom intentional) - [ ] Document in config example / SCOPE.md ### T3 — Cert acquisition in manager + +```task +id: BRIDGE-WP-0004-T3 +state_hub_task_id: b93be1e4-dd32-4e9c-a085-c5bf81108d97 +status: todo +priority: high +``` + - [ ] `manager.py`: extract cert acquisition into `_acquire_cert(cfg) -> Optional[Path]` - If `cfg.cert_command` is None: return None (static key mode) - Run `cert_command` via `subprocess.run(shell=True, capture_output=True)` @@ -144,6 +168,14 @@ SIEM auditability. so every reconnect gets a fresh cert ### T4 — cert_identity in audit log + +```task +id: BRIDGE-WP-0004-T4 +state_hub_task_id: bc29cc2a-1d77-48d8-97d3-54a49de0550e +status: todo +priority: high +``` + - [ ] `manager.py`: after cert acquisition, parse `ssh-keygen -L -f ` output to extract `Key ID` (the `-I` value from signing time) - [ ] Add `cert_identity: Optional[str]` to `AuditLogger.log()` signature; include in @@ -152,6 +184,14 @@ SIEM auditability. - [ ] `AuditEvent`: no new events needed; `cert_identity` is metadata on existing events ### T5 — TTL-aware cert refresh + +```task +id: BRIDGE-WP-0004-T5 +state_hub_task_id: cc3aee49-7821-4a11-a331-be562aa88d91 +status: todo +priority: high +``` + - [ ] `manager.py`: after successful cert acquisition, parse `Valid before:` timestamp from `ssh-keygen -L` output → `cert_expires_at: datetime` - [ ] In the health-check/wait loop, check `datetime.now(utc) >= cert_expires_at - timedelta(minutes=5)` @@ -164,6 +204,14 @@ SIEM auditability. - [ ] If `cert_command` is absent, skip all TTL logic entirely ### T6 — `bridge cert-status` command + +```task +id: BRIDGE-WP-0004-T6 +state_hub_task_id: b10275fc-bfe2-49a9-a83e-dd0dec796efd +status: todo +priority: medium +``` + - [ ] `cli.py`: add `cert-status [TUNNEL]` subcommand - [ ] For each tunnel (or the named one): read cert file from state dir if present, run `ssh-keygen -L`, display: identity, principals, valid-from, valid-until, @@ -172,6 +220,14 @@ SIEM auditability. - [ ] `--json` flag for machine-readable output ### T7 — CertAcquisitionError handling + +```task +id: BRIDGE-WP-0004-T7 +state_hub_task_id: de355a7c-f07e-452e-974f-4ddf362b24a6 +status: todo +priority: high +``` + - [ ] New exception `CertAcquisitionError` in `models.py` - [ ] In `_run_loop`: catch `CertAcquisitionError`, log `AuditEvent.BRIDGE_DISCONNECTED` with `detail="cert acquisition failed: "`, apply normal backoff and retry @@ -179,6 +235,14 @@ SIEM auditability. - [ ] After `max_attempts` consecutive cert failures, transition to `FAILED` state ### T8 — SCOPE.md and documentation updates + +```task +id: BRIDGE-WP-0004-T8 +state_hub_task_id: 40f5364b-f9e1-41cb-90e5-2b19511108f1 +status: todo +priority: medium +``` + - [ ] Update `SCOPE.md`: replace "Identity/credential management (uses existing SSH keys)" with the pluggable cert_command model; add ops-warden as related repo; update actor terminology to adm/agt/atm; update Current State @@ -189,6 +253,14 @@ SIEM auditability. - [ ] Update `.claude/rules/architecture.md`: add cert lifecycle to architecture description ### T9 — Tests + +```task +id: BRIDGE-WP-0004-T9 +state_hub_task_id: fc1d1321-c1d0-4a0a-ae2e-d9ec9939dd6a +status: todo +priority: high +``` + - [ ] `test_config.py`: actor name prefix validation (adm/agt/atm); legacy class mapping; cert_command parse - [ ] `test_manager.py`: mock `cert_command` subprocess; verify cert path appended to SSH diff --git a/workplans/WARDEN-WP-0001-initial-implementation.md b/workplans/WARDEN-WP-0001-initial-implementation.md index 064d218..c673631 100644 --- a/workplans/WARDEN-WP-0001-initial-implementation.md +++ b/workplans/WARDEN-WP-0001-initial-implementation.md @@ -4,7 +4,7 @@ type: workplan title: "OpsWarden Initial Implementation" domain: custodian repo: ops-warden -status: draft +status: active owner: Bernd topic_slug: custodian created: "2026-03-28" @@ -91,6 +91,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu ## Tasks ### T1 — Repository bootstrap + +```task +id: WARDEN-WP-0001-T1 +state_hub_task_id: 6d643e9d-5e97-4224-9d82-87267b5ba6bc +status: todo +priority: high +``` + - [ ] Create `ops-warden` repo; copy CLAUDE.md template from `ops-bridge`; add `workplans/WARDEN-WP-0001-initial-implementation.md` (this file) - [ ] Write `SCOPE.md` (see template in §SCOPE below) @@ -99,6 +107,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] Create state-hub workstream for this workplan ### T2 — Models and config + +```task +id: WARDEN-WP-0001-T2 +state_hub_task_id: c66fc65a-0b16-4ba2-9e70-a83d875572ec +status: todo +priority: high +``` + - [ ] `models.py`: `ActorType` enum (`adm | agt | atm`); `CertSpec` (actor_name, pubkey_path, ttl_hours, principals); `CertRecord` (identity, valid_before, cert_path, signed_at) - [ ] `config.py`: load `~/.config/warden/warden.yaml`; required fields: `backend`, @@ -107,6 +123,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] Validate actor name prefix matches `ActorType` (`adm-*`, `agt-*`, `atm-*`) ### T3 — LocalCA backend + +```task +id: WARDEN-WP-0001-T3 +state_hub_task_id: a5a41e58-1c6d-42a9-9b11-2088f17c29b5 +status: todo +priority: high +``` + - [ ] `ca.py`: `LocalCA.sign(spec: CertSpec) -> CertRecord` - Calls `ssh-keygen -s -I -n -V +h ` - Parses `ssh-keygen -L -f ` output to extract `Valid before`, `Key ID`, @@ -118,6 +142,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu actors that do not bring their own key ### T4 — VaultCA backend + +```task +id: WARDEN-WP-0001-T4 +state_hub_task_id: b2067ee6-c9ce-423b-9d60-0d28069fb304 +status: todo +priority: medium +``` + - [ ] `vault.py`: `VaultCA.sign(spec: CertSpec) -> CertRecord` - `POST /v1/ssh/sign/` with `public_key`, `valid_principals`, `ttl` - Parse response `signed_key` field; write to state dir; extract metadata via @@ -126,6 +158,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] Graceful error message when Vault is unreachable (with `--backend local` fallback hint) ### T5 — Principals inventory + +```task +id: WARDEN-WP-0001-T5 +state_hub_task_id: 6d13f8cd-1850-44c9-b769-b21250348319 +status: todo +priority: high +``` + - [ ] `inventory.py`: load/save `inventory.yaml` (format mirrors §4.1 of directive): ```yaml actors: @@ -145,6 +185,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] `warden inventory remove ` ### T6 — CLI commands + +```task +id: WARDEN-WP-0001-T6 +state_hub_task_id: 656a4615-92bb-4b5d-9406-e86d24fa15d0 +status: todo +priority: high +``` + - [ ] `warden sign --pubkey ` — sign existing pubkey; write cert to stdout (the `cert_command` interface for ops-bridge) - [ ] `warden issue ` — generate keypair + sign; output JSON with @@ -155,6 +203,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] `warden inventory ` (list / add / remove) ### T7 — Scorecard runner + +```task +id: WARDEN-WP-0001-T7 +state_hub_task_id: 7818bcc5-f40e-4793-b117-d36f653ffeed +status: todo +priority: medium +``` + - [ ] `scorecard.py`: implement each §5 row as a named check function returning `CheckResult(name, passed, detail)` - [ ] Checks in scope for `ops-warden` (local checks, not host-side): @@ -167,6 +223,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] `warden scorecard --json` for machine-readable output ### T8 — ops-ssh-wrapper script + +```task +id: WARDEN-WP-0001-T8 +state_hub_task_id: e9c28152-5785-4995-83a5-439985ed3db9 +status: todo +priority: medium +``` + - [ ] Ship `scripts/ops-ssh-wrapper` (the Python snippet from §4.1, hardened): - Reads `WARDEN_ACTOR` and `SSH_PUBKEY` env vars - Calls `warden sign $WARDEN_ACTOR --pubkey $SSH_PUBKEY` @@ -174,6 +238,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] Install as part of `uv tool install` entry points ### T9 — Tests + +```task +id: WARDEN-WP-0001-T9 +state_hub_task_id: 950139ab-cc17-4f1d-9a17-d5744e402ddf +status: todo +priority: high +``` + - [ ] Unit tests for `LocalCA` (mock `ssh-keygen` subprocess) - [ ] Unit tests for inventory YAML round-trip - [ ] Unit tests for actor name prefix validation @@ -181,6 +253,14 @@ Writes the signed certificate to stdout (the cert text). Exits non-zero on failu - [ ] Scorecard unit tests (mock cert records) ### T10 — Documentation + +```task +id: WARDEN-WP-0001-T10 +state_hub_task_id: 271d6759-e359-41ce-80e4-76c574634a87 +status: todo +priority: medium +``` + - [ ] `SCOPE.md` (see below) - [ ] `wiki/AccessManagementDirective.md` — copy from `ops-bridge/wiki/` - [ ] `wiki/OpsWardenConfig.md` — annotated `warden.yaml` reference