--- id: FLEX-WP-0006 type: workplan title: "Ops-Warden SSH Signing Policy Gate" domain: infotech repo: flex-auth status: finished owner: codex topic_slug: flex-auth planning_priority: P0 planning_order: 60 depends_on_workplans: - FLEX-WP-0002 - FLEX-WP-0005 related_workplans: - WARDEN-WP-0009 created: "2026-06-23" updated: "2026-06-23" state_hub_workstream_id: "bbea4049-8acc-4d7c-8cf5-3106c6b93f7f" --- # FLEX-WP-0006: Ops-Warden SSH Signing Policy Gate ## Purpose Publish the flex-auth policy package, registry fixtures, and service contract evidence needed for ops-warden's opt-in pre-sign gate. Ops-warden already shipped the caller side in `WARDEN-WP-0007`: `policy.enabled: true` makes `warden sign` and local-backend `warden issue` call `POST /v1/check` before signing. Production OpenBao-backed signing was verified in `WARDEN-WP-0008`. The remaining blocked work is flex-auth-owned: policies for `resource.type: ssh-certificate` and `action: sign`. This workplan unblocks `ops-warden/workplans/WARDEN-WP-0009-flex-auth-policy-gate-production.md`. ## Gate Contract The shipped ops-warden gate sends this flex-auth decision request: | Field | Meaning | | --- | --- | | `subject.id` | `WARDEN_POLICY_SUBJECT` when set, otherwise actor name | | `subject.type` | Actor type: `adm`, `agt`, or `atm` | | `tenant` | `policy.tenant`, default `tenant:platform` | | `resource.id` | `ssh-cert:actor/` | | `resource.type` | `ssh-certificate` | | `resource.system` | `ops-warden` | | `action` | `sign` | | `context.principals` | Requested SSH certificate principals from inventory | | `context.actor_type` | Actor type: `adm`, `agt`, or `atm` | | `context.pubkey_fingerprint` | SHA256 fingerprint of the submitted public key text | | `context.ttl_hours` | Requested certificate TTL | Allow responses must return `effect: allow` plus a decision `id` or `request_id`; ops-warden records that value as `policy_decision_id` in `signatures.log`. Deny responses must include a human-readable `reason` that the ops-warden CLI can surface. ## Policy Boundary flex-auth decides whether this specific signing request is allowed now. ops-warden remains responsible for SSH CA operation, OpenBao integration, actor inventory, host documentation, and local scorecard checks. The flex-auth policy must not request or store SSH private keys, OpenBao tokens, database credentials, or other secrets. Acceptance evidence should use fixtures, non-secret request bodies, decision ids, and sanitized logs only. ## T1 - Pin the ops-warden protected-system contract ```task id: FLEX-WP-0006-T01 status: done priority: high state_hub_task_id: "8831b904-dbef-4d55-8eb5-053c939c86b3" ``` Add an ops-warden protected-system manifest and registry fixture slice that declares: - protected system `ops-warden` - resource type `ssh-certificate` - action `sign` - tenant `tenant:platform` - supported actor subject types `adm`, `agt`, and `atm` - required context fields: `principals`, `actor_type`, `pubkey_fingerprint`, and `ttl_hours` Output should live under `examples/ops-warden/` unless implementation reveals a more idiomatic local path. The manifest and fixture must validate with the existing `flex-auth validate` and `flex-auth load-registry` commands. ## T2 - Author the ssh-certificate sign policy package ```task id: FLEX-WP-0006-T02 status: done priority: high state_hub_task_id: "9ea206fa-f93d-46b6-8b8e-e8669dd502d4" ``` Create the Rego-in-Markdown policy package for ops-warden signing decisions. Minimum allow criteria: - request action is `sign` - resource system is `ops-warden` - resource type is `ssh-certificate` - tenant is `tenant:platform` unless the fixture explicitly tests another configured tenant - subject and `context.actor_type` are aligned with the actor resource - requested TTL is positive and within the policy's configured maximum for the actor type - requested principals are non-empty and allowed for the actor fixture - `context.pubkey_fingerprint` is present Minimum deny criteria: - unknown subject or actor resource - mismatched `subject.type`, `context.actor_type`, or `resource.id: ssh-cert:actor/` - TTL outside policy bounds - missing fingerprint - disallowed or empty principals - wrong action, system, resource type, or tenant The package should produce clear deny reasons and preserve the standard decision envelope so ops-warden can surface `reason` without special casing. ## T3 - Add allow and deny fixtures ```task id: FLEX-WP-0006-T03 status: done priority: high state_hub_task_id: "6bf5cb9b-d46c-49cf-aec0-12f6e864a1f8" ``` Add fixture requests and expected decisions for the gate. Required fixtures: - allow: valid `adm` sign request - allow: valid `agt` sign request, if an agent actor fixture exists - deny: unknown subject - deny: actor type mismatch - deny: TTL above policy max - deny: missing or disallowed principal - deny: missing `pubkey_fingerprint` Wire the fixtures into Go tests or the existing policy fixture runner so `make test` proves the package behavior. Fixture data must remain non-secret. ## T4 - Verify the `/v1/check` service contract ```task id: FLEX-WP-0006-T04 status: done priority: high state_hub_task_id: "077d29db-30e0-4447-90fd-620c0884306c" ``` Run `flex-auth serve` with the ops-warden registry and policy package, then exercise `POST /v1/check` with ops-warden-shaped JSON. Acceptance evidence: - allow response includes `effect: allow` and a stable decision `id` - deny response includes `effect: deny` and a useful `reason` - decision log records the allow and deny decisions without secrets - method mismatch and malformed JSON fail predictably - the documented behavior is compatible with ops-warden `fail_closed: true` If the current service shape needs a small compatibility adjustment, keep it inside the stable v1 API instead of adding an ops-warden-specific route. ## T5 - Hand off production-readiness evidence to ops-warden ```task id: FLEX-WP-0006-T05 status: done priority: medium state_hub_task_id: "06cac0b1-51c0-4ae0-b605-c940f7821ac7" ``` Publish the handoff notes ops-warden needs to close `WARDEN-WP-0009 T01` and start its production enablement smoke. Include: - policy package path and version - registry fixture path - local service command - allow and deny fixture names - non-secret decision ids from local smoke - expected `policy.enabled` production sequence - reminder that OpenBao SSH signing and SSH CA custody remain ops-warden-owned Record progress in State Hub and ask the custodian operator to run `make fix-consistency REPO=flex-auth` from `~/state-hub` after this workplan is merged or otherwise accepted. ## Implementation Summary Implemented on 2026-06-23. - Added examples/ops-warden/ with the protected-system manifest, subject and resource manifests, combined registry snapshot, policy package, check requests, and allow/deny fixtures. - Added top-level tenant support to CheckRequest and BatchCheckRequest so the shipped ops-warden policy gate request shape is accepted by /v1/check. - Added CLI and HTTP service tests for the ops-warden allow path, deny path, malformed JSON, method mismatch, and decision-log recording. - Added docs/ops-warden-policy-gate-handoff.md with non-secret smoke evidence and the ops-warden production enablement sequence. ## Exit Criteria - flex-auth contains a validated ops-warden protected-system manifest and registry fixture. - flex-auth contains an `ssh-certificate` / `sign` policy package with allow and deny fixtures for `adm`, `agt`, and `atm`-style actors where applicable. - `make test` passes. - `POST /v1/check` accepts the shipped ops-warden request shape and returns decision envelopes compatible with `policy.enabled: true`. - A sanitized handoff note exists for ops-warden `WARDEN-WP-0009`. - State Hub progress is logged and the operator is told to run `make fix-consistency REPO=flex-auth`.