Compare commits

..

14 Commits

Author SHA1 Message Date
99869b39fb docs(scope): reflect baseline complete — all FLEX-WP-0001..0007 done
Some checks are pending
CI / Build and Test (push) Waiting to run
CI / Lint (push) Waiting to run
Correct a stale Current State paragraph: FLEX-WP-0002 (standalone core),
0003 (Markitect integration), and 0004 (delegated PDP/directory adapters)
were completed in May 2026, not "planned". Record FLEX-WP-0007 closure:
ops-warden ran the joint OpenBao smoke (2026-06-29, decision
032b096c433ad80c allow; ttl_out_of_bounds deny), with production
policy.enabled deliberately left off while the ecosystem is build-stage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:40:19 +02:00
339c35e876 Close ops-warden policy gate deployment
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
2026-06-30 00:52:56 +02:00
8124367e1d chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-30:
  - update .custodian-brief.md for flex-auth
2026-06-30 00:51:58 +02:00
b4520bd731 docs(intent/scope): align with ops-warden as first shipped consumer
Some checks failed
CI / Lint (push) Has been cancelled
CI / Build and Test (push) Has been cancelled
ops-warden's SSH signing policy gate (FLEX-WP-0006 finished, FLEX-WP-0007
deploying) makes it flex-auth's first shipped protected-system consumer.
Update the intent baseline to match the implemented reality:

- SCOPE Current State: standalone Go core + /v1/check is implemented;
  FLEX-WP-0001/0005/0006 complete, 0007 blocked only on T4 VAULT_TOKEN.
- SCOPE Related/Overlapping + Disjoint From: ops-warden is now a consumer,
  not merely disjoint; the once-hypothetical "agt as flex-auth subject"
  flow is realized through the signing gate. Disjointness narrowed to the
  identity surface (warden issues certs, flex-auth never does).
- INTENT Consumer Patterns: lead with the shipped action-gate shape
  (ops-warden), keep Markitect as the planned knowledge-pipeline consumer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:37:07 +02:00
941501c590 FLEX-WP-0007: production registry fixture, tests, and sync runbook
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Add production_registry_snapshot.json from ops-warden inventory with CI
coverage for real actors, IAM subject binding, ttl_out_of_bounds, and
unknown_actor_resource. Extend serve contract tests with /healthz and
publish the registry sync contract for operator deployment.
2026-06-24 14:52:35 +02:00
fae0f00a69 chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-24:
  - update .custodian-brief.md for flex-auth
2026-06-24 13:11:03 +02:00
77bcd55ddb chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-24:
  - update .custodian-brief.md for flex-auth
2026-06-24 01:45:33 +02:00
f0d1afa237 chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-23:
  - update .custodian-brief.md for flex-auth
2026-06-23 21:19:00 +02:00
0fde95a87c FLEX-WP-0006: implement ops-warden signing gate policy
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
2026-06-23 21:17:42 +02:00
53e0d055c9 chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-23:
  - update .custodian-brief.md for flex-auth
2026-06-23 17:35:12 +02:00
e1c141234a chore(consistency): sync task status from DB [auto]
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Updated by fix-consistency on 2026-06-22:
  - update .custodian-brief.md for flex-auth
2026-06-22 23:21:50 +02:00
8a913d6163 Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:25 +02:00
1be449dae8 Human-review .repo-classification.yaml (CUST-WP-0050 follow-up)
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
2026-06-22 17:56:17 +02:00
1b899cd41c Add .repo-classification.yaml (CUST-WP-0050 T11 agent first-pass) 2026-06-22 17:47:36 +02:00
42 changed files with 2942 additions and 65 deletions

View File

@@ -1,11 +1,11 @@
## First Session Protocol
Triggered when `get_domain_summary("netkingdom")` shows **no workstreams**.
Triggered when `get_domain_summary("infotech")` shows **no workstreams**.
The project is registered but work has not yet been structured.
**Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/netkingdom/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/netkingdom/roadmap_v0.1.md` — planned phases
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work**
@@ -17,20 +17,20 @@ roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)**
```
workplans/flex-auth-WP-NNNN-<slug>.md ← write this first
workplans/FLEX-WP-NNNN-<slug>.md ← write this first
```
Then register in the hub:
```
create_workstream(topic_id="a6c6e745-bf54-4465-9340-1534a2be493e", title="...", owner="...", description="...")
create_workstream(topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", title="...", owner="...", description="...")
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
```
**Step 5 — Record the setup**
```
add_progress_event(
summary="First session: structured netkingdom into N workstreams, M tasks",
summary="First session: structured infotech into N workstreams, M tasks",
event_type="milestone",
topic_id="a6c6e745-bf54-4465-9340-1534a2be493e",
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
detail={"workstreams": [...], "tasks_created": M}
)
```

View File

@@ -1,5 +1,5 @@
**Purpose:** flex-auth - (fill in purpose)
**Domain:** netkingdom
**Domain:** infotech
**Repo slug:** flex-auth
**Topic ID:** a6c6e745-bf54-4465-9340-1534a2be493e
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a

View File

@@ -1,6 +1,7 @@
## Session Protocol
State Hub: http://127.0.0.1:8000
Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient**
@@ -10,7 +11,7 @@ cat .custodian-brief.md
```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
```
get_domain_summary("netkingdom")
get_domain_summary("infotech")
```
If MCP tools are unavailable in the current agent session, use the REST API:
```bash
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/
```
For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks.
`wait`/`todo`/`progress` tasks.
**Step 4 — Present brief**
1. **Active workstreams** for `netkingdom` — title, task counts, blocking decisions
1. **Active workstreams** for `infotech` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:flex-auth]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
@@ -61,13 +62,13 @@ If no workstreams: follow First Session Protocol (`first-session.md`).
**Session close:**
With MCP tools:
```
add_progress_event(summary="...", topic_id="a6c6e745-bf54-4465-9340-1534a2be493e", workstream_id="<uuid>")
add_progress_event(summary="...", topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", workstream_id="<uuid>")
```
Without MCP tools:
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{"topic_id":"a6c6e745-bf54-4465-9340-1534a2be493e","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
-d '{"topic_id":"cee7bedf-2b48-46ef-8601-006474f2ad7a","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
```
If workplan files were modified, ensure the local copy is up to date first:
```bash

View File

@@ -1,7 +1,7 @@
## Workplan Convention (ADR-001)
File location: `workplans/flex-auth-WP-NNNN-<slug>.md`
ID prefix: `FLEX-WP`
File location: `workplans/FLEX-WP-NNNN-<slug>.md`
ID prefix: `FLEX-WP-`
Work items originate as files in this repo **before** being registered in the hub.
@@ -12,7 +12,7 @@ repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-flex-auth-WP-NNNN-<slug>.md`. The frontmatter id remains
prefix: `YYMMDD-FLEX-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
@@ -25,4 +25,16 @@ Ecosystem todos from other agents arrive as `[repo:flex-auth]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering
the workstream.
Task blocks use this shape:
```task
id: FLEX-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

View File

@@ -1,8 +1,8 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — flex-auth
**Domain:** netkingdom
**Last synced:** 2026-05-17 05:28 UTC
**Domain:** communication
**Last synced:** 2026-06-29 22:51 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
@@ -13,6 +13,6 @@
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("netkingdom")`
`get_domain_summary("communication")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

25
.repo-classification.yaml Normal file
View File

@@ -0,0 +1,25 @@
repo_classification:
standard: Repo Classification Standard
version: '1.0'
classified_at: '2026-06-22'
classified_by: human
category: product
domain: infotech
secondary_domains:
- government
capability_tags:
- identity
- access-control
- policy
- governance
- audit
business_stake:
- technology
- legal
- operations
- product
business_mechanics:
- control
- coordination
- adaptation
notes: Policy-as-code authorization registry; human corrected domain from communication→infotech.

View File

@@ -4,9 +4,9 @@
**Purpose:** flex-auth - (fill in purpose)
**Domain:** netkingdom
**Domain:** infotech
**Repo slug:** flex-auth
**Topic ID:** `a6c6e745-bf54-4465-9340-1534a2be493e`
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `FLEX-WP-`
---
@@ -28,7 +28,7 @@ there is no MCP server for Codex agents.
cat .custodian-brief.md
# Active workstreams for this domain
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=a6c6e745-bf54-4465-9340-1534a2be493e&status=active" \
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
@@ -63,8 +63,8 @@ Omit `workstream_id` / `task_id` when not applicable.
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "in_progress"}'
# values: todo | in_progress | done | blocked
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
@@ -83,7 +83,7 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
2. Check inbox: `GET /messages/?to_agent=flex-auth&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check blocked tasks: `GET /tasks/?needs_human=true`
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
@@ -151,6 +151,11 @@ every repo's agent instructions because it is high-frequency, high-risk, and eas
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
@@ -176,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: FLEX-WP-NNNN
type: workplan
title: "..."
domain: netkingdom
domain: infotech
repo: flex-auth
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
@@ -198,7 +203,7 @@ derived health labels, not frontmatter statuses.
` ` `task
id: FLEX-WP-NNNN-T01
status: todo | in_progress | done | blocked
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
@@ -206,7 +211,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
Task description text.
```
Status progression: `todo` → `in_progress` → `done` (or `blocked`)
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
To create a new workplan:
1. Write the file following the format above

View File

@@ -149,9 +149,29 @@ adopting their models as its own:
Flex-auth remains the stable control plane even when the backend changes.
## First Consumer Pattern
## Consumer Patterns
The first concrete consumer is a knowledge and document management pipeline:
Two consumer shapes drive flex-auth, and the first one to ship deliberately
is not a document pipeline — proving the control plane stays generic.
**First shipped consumer — an action gate (ops-warden SSH signing).** A
protected system asks flex-auth a single "may this actor perform this action
now?" question before doing irreversible work:
- it registers a protected system, a resource type (`ssh-certificate`), and an
action (`sign`)
- it sends one policy check per request, passing subject, resource, and
context (actor type, principals, TTL, key fingerprint)
- it enforces the allow/deny decision and records the decision id for audit
- flex-auth owns the policy and durable decision log; the protected system
keeps custody of its own keys and secrets
This first consumer validated that flex-auth's resource/action/context model,
`POST /v1/check` contract, and decision envelope work for a non-document,
high-stakes gate without any consumer-specific routes.
**First knowledge-pipeline consumer (planned) — a document and knowledge
pipeline (Markitect):**
- it registers knowledge bases, repositories, documents, sections, context
packages, workflow artifacts, and exports
@@ -159,8 +179,9 @@ The first concrete consumer is a knowledge and document management pipeline:
- it can redact or drop results based on decisions
- flex-auth owns central policy administration and durable audit
This first consumer should shape flex-auth around real document and knowledge
pipelines without making the policy service consumer-specific.
Together these shape flex-auth around real authorization needs — both
point-in-time action gates and result-filtering pipelines without making the
policy service consumer-specific.
## Non-Goals

View File

@@ -72,15 +72,30 @@ can be coordinated behind a stable flex-auth API.
## Current State
The repository contains the intent baseline, authorization landscape
research, initial workplans, and the pre-implementation assessment and
ADR set produced on 2026-05-15. `FLEX-WP-0001` is complete. Implementation
now proceeds through `FLEX-WP-0005 Foundations and Topaz Alignment`
which lands the Go skeleton, pins the `FlexAuthResourceManifest` schema,
runs the Topaz mapping spike, and records ADR-001/002/003 — before the
standalone policy-as-code core in `FLEX-WP-0002`. Markitect consumer
integration and delegated PDP/directory adapters are planned after the
core contracts stabilize.
The standalone core is implemented and **all seven baseline workplans
(`FLEX-WP-0001` through `FLEX-WP-0007`) are complete.** The repository carries
the intent baseline, authorization landscape research, ADR set, and a working
Go service (`cmd/flex-auth`) with `validate`, `load-registry`, `serve`, and
`POST /v1/check` plus registry, policy, decision, audit, Markitect, and
delegated-adapter internals. The standalone policy-as-code core (`FLEX-WP-0002`),
Markitect consumer integration (`FLEX-WP-0003`, manifest ingest, decisions, and
fixtures), and the delegated PDP/directory adapter shapes (`FLEX-WP-0004`,
Topaz/OpenFGA/OPA/Cedar/Keycloak/Entra tradeoffs documented with at least one
controlled adapter shape) all landed in May 2026.
The **first shipped protected-system consumer is ops-warden**: its opt-in
pre-sign gate calls `POST /v1/check` for `resource.type: ssh-certificate`,
`action: sign` decisions (`examples/ops-warden/`, policy package, allow/deny
fixtures, and tests). `FLEX-WP-0006` published that gate and `FLEX-WP-0007`
deployed flex-auth as a reachable production runtime for it. The joint
OpenBao-backed smoke is verified (2026-06-29: vault-backed allow recorded
`decision:032b096c433ad80c`; TTL-over-max denied `ttl_out_of_bounds` by
flex-auth before OpenBao). Production `policy.enabled` is **deliberately left
off** for now — the ecosystem is still build-stage/pre-testing, so the gate is
verified and banked for later live enforcement rather than forced into premature
production rigor. With the baseline complete, new work (live enforcement
rollout, additional consumers, deeper delegated backends) will open as fresh
workplans.
State Hub integration is present through:
@@ -127,7 +142,17 @@ local diagnostics.
- key-cape / NetKingdom SSO: identity source and coarse claims provider;
flex-auth consumes the **NetKingdom IAM Profile**
(`~/net-kingdom/canon/standards/iam-profile_v0.2.md`).
- Markitect: first protected-system consumer and policy enforcement point.
- ops-warden: first **shipped** protected-system consumer. Its opt-in
pre-sign gate calls flex-auth for `ssh-certificate` / `sign` decisions
before issuing a short-lived SSH certificate (`FLEX-WP-0006`,
`FLEX-WP-0007`). ops-warden owns the SSH CA, OpenBao signing, and actor
inventory; flex-auth owns the policy decision. ops-warden's routing
charter names flex-auth as the owner of every "may I perform action X?"
question.
- Markitect: first **knowledge-pipeline** consumer. Integration is complete on
the flex-auth side (`FLEX-WP-0003` — resource-manifest ingest, Markitect-
compatible decisions, and fixtures); a live Markitect runtime calling the gate
in production is the next consumer milestone after ops-warden.
- Topaz: aligned evaluator. Per ADR-003 the standalone core is shaped
to match Topaz's Rego + directory model from day one; the Topaz
adapter in `FLEX-WP-0004` is therefore a small step rather than a
@@ -143,12 +168,13 @@ local diagnostics.
## Disjoint From
- **ops-warden** signs short-lived SSH certificates for ops actors
(`adm`/`agt`/`atm`). That is a separate identity surface — SSH certs,
not OIDC subjects — and ops-warden disclaims being a resource-policy
engine. flex-auth and ops-warden therefore do not overlap. (A future
flow could surface an `agt` actor as a flex-auth subject; nothing in
the current design requires it.)
- **ops-warden** is a flex-auth *consumer*, not an overlap (see Related /
Overlapping). The two remain disjoint on **identity surface**: ops-warden
issues SSH certificates for ops actors (`adm`/`agt`/`atm`) and is not a
resource-policy engine; flex-auth decides whether a given sign request is
allowed and never issues certificates. The once-hypothetical flow of
surfacing an `agt` actor as a flex-auth subject is now realized through
the signing policy gate.
- **ops-bridge** owns SSH reverse-tunnel connectivity and explicitly
disclaims being a credential authority or policy engine. No overlap.

View File

@@ -337,6 +337,16 @@ func runServe(args []string, stdout, stderr io.Writer) int {
return fail(stderr, err)
}
mux := newServeMux(engine)
fmt.Fprintf(stderr, "flex-auth serving on http://%s\n", *addr)
if err := http.ListenAndServe(*addr, mux); err != nil {
return fail(stderr, err)
}
return 0
}
func newServeMux(engine *decisioncore.Engine) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("content-type", "application/json")
@@ -368,12 +378,7 @@ func runServe(args []string, stdout, stderr io.Writer) int {
decisions, err := engine.BatchCheck(r.Context(), request)
writeHTTP(w, decisions, err)
})
fmt.Fprintf(stderr, "flex-auth serving on http://%s\n", *addr)
if err := http.ListenAndServe(*addr, mux); err != nil {
return fail(stderr, err)
}
return 0
return mux
}
func buildEngine(ctx context.Context, registryPath, policyPath, logPath string) (*decisioncore.Engine, error) {

View File

@@ -2,7 +2,11 @@ package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
@@ -74,6 +78,203 @@ func TestRunBatchCheck(t *testing.T) {
}
}
func TestRunCheckOpsWarden(t *testing.T) {
var stdout, stderr bytes.Buffer
code := run([]string{
"check",
"--registry", opsPath("registry_snapshot.json"),
"--policy", opsPath("policy_package.md"),
"--request", opsPath("check_request_allow_adm.json"),
}, &stdout, &stderr)
if code != 0 {
t.Fatalf("code = %d, stderr = %s", code, stderr.String())
}
var decision api.DecisionEnvelope
if err := json.Unmarshal(stdout.Bytes(), &decision); err != nil {
t.Fatalf("unmarshal decision: %v\n%s", err, stdout.String())
}
if decision.Effect != api.DecisionEffectAllow {
t.Fatalf("decision.Effect = %q; want allow", decision.Effect)
}
if decision.ID == "" {
t.Fatal("decision.ID is empty; ops-warden needs a policy_decision_id")
}
}
func TestServeOpsWardenCheckContract(t *testing.T) {
logPath := filepath.Join(t.TempDir(), "decisions.jsonl")
engine, err := buildEngine(context.Background(), opsPath("registry_snapshot.json"), opsPath("policy_package.md"), logPath)
if err != nil {
t.Fatalf("buildEngine: %v", err)
}
server := httptest.NewServer(newServeMux(engine))
defer server.Close()
resp, err := http.Get(server.URL + "/healthz")
if err != nil {
t.Fatalf("GET /healthz: %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("GET /healthz status = %d; want 200", resp.StatusCode)
}
allow := postCheck(t, server.URL+"/v1/check", opsPath("check_request_allow_adm.json"))
if allow.Effect != api.DecisionEffectAllow || allow.ID == "" {
t.Fatalf("allow decision = %+v; want allow with id", allow)
}
deny := postCheck(t, server.URL+"/v1/check", opsPath("check_request_deny_ttl_above_max.json"))
if deny.Effect != api.DecisionEffectDeny || deny.Reason != "ttl_out_of_bounds" {
t.Fatalf("deny decision = %+v; want ttl_out_of_bounds deny", deny)
}
resp, err = http.Get(server.URL + "/v1/check")
if err != nil {
t.Fatalf("GET /v1/check: %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Fatalf("GET /v1/check status = %d; want 405", resp.StatusCode)
}
resp, err = http.Post(server.URL+"/v1/check", "application/json", strings.NewReader(`{"subject":`))
if err != nil {
t.Fatalf("POST malformed /v1/check: %v", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("malformed POST status = %d; want 400", resp.StatusCode)
}
logData, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("read decision log: %v", err)
}
if !strings.Contains(string(logData), allow.ID) || !strings.Contains(string(logData), deny.ID) {
t.Fatalf("decision log does not contain both decision ids\nlog: %s\nallow: %s deny: %s", string(logData), allow.ID, deny.ID)
}
}
func TestRunLoadRegistryOpsWardenProduction(t *testing.T) {
var stdout, stderr bytes.Buffer
code := run([]string{"load-registry", "--file", opsPath("production_registry_snapshot.json")}, &stdout, &stderr)
if code != 0 {
t.Fatalf("code = %d, stderr = %s", code, stderr.String())
}
var result map[string]any
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
t.Fatalf("unmarshal load-registry output: %v; stdout = %s", err, stdout.String())
}
if result["subjects"] != float64(4) || result["relationships"] != float64(4) || result["resource_manifests"] != float64(1) {
t.Fatalf("load-registry result = %+v; want production actor registry counts", result)
}
}
func TestOpsWardenProductionRegistryActors(t *testing.T) {
engine, err := buildEngine(context.Background(), opsPath("production_registry_snapshot.json"), opsPath("policy_package.md"), "")
if err != nil {
t.Fatalf("buildEngine: %v", err)
}
cases := []struct {
name string
subjectID string
actor string
actorType string
principal string
ttlHours float64
wantEffect api.DecisionEffect
wantReason string
}{
{
name: "state hub bridge agent allow",
subjectID: "agt-state-hub-bridge",
actor: "agt-state-hub-bridge",
actorType: "agt",
principal: "agt-task-bridge",
ttlHours: 1,
wantEffect: api.DecisionEffectAllow,
},
{
name: "state hub bridge IAM subject allow",
subjectID: "iam:agt-state-hub-bridge",
actor: "agt-state-hub-bridge",
actorType: "agt",
principal: "agt-task-bridge",
ttlHours: 1,
wantEffect: api.DecisionEffectAllow,
},
{
name: "codex interhub bootstrap agent allow",
subjectID: "agt-codex-interhub-bootstrap",
actor: "agt-codex-interhub-bootstrap",
actorType: "agt",
principal: "agt-interhub-bootstrap",
ttlHours: 1,
wantEffect: api.DecisionEffectAllow,
},
{
name: "admin actor allow",
subjectID: "adm-example",
actor: "adm-example",
actorType: "adm",
principal: "adm-full",
ttlHours: 4,
wantEffect: api.DecisionEffectAllow,
},
{
name: "automation actor allow",
subjectID: "atm-backup-daily",
actor: "atm-backup-daily",
actorType: "atm",
principal: "atm-backup-daily",
ttlHours: 1,
wantEffect: api.DecisionEffectAllow,
},
{
name: "ttl above production max denies",
subjectID: "agt-state-hub-bridge",
actor: "agt-state-hub-bridge",
actorType: "agt",
principal: "agt-task-bridge",
ttlHours: 999,
wantEffect: api.DecisionEffectDeny,
wantReason: "ttl_out_of_bounds",
},
{
name: "unregistered production actor denies",
subjectID: "agt-missing",
actor: "agt-missing",
actorType: "agt",
principal: "agt-missing",
ttlHours: 1,
wantEffect: api.DecisionEffectDeny,
wantReason: "unknown_actor_resource",
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
decision, err := engine.Check(context.Background(), opsWardenProductionSignRequest(tt.subjectID, tt.actor, tt.actorType, tt.principal, tt.ttlHours))
if err != nil {
t.Fatalf("Check: %v", err)
}
if decision.Effect != tt.wantEffect {
t.Fatalf("decision.Effect = %q; want %q; decision: %+v", decision.Effect, tt.wantEffect, decision)
}
if tt.wantReason != "" && decision.Reason != tt.wantReason {
t.Fatalf("decision.Reason = %q; want %q; decision: %+v", decision.Reason, tt.wantReason, decision)
}
if tt.wantEffect == api.DecisionEffectAllow && decision.ID == "" {
t.Fatal("allow decision ID is empty")
}
})
}
}
func TestRunValidateAccessDescriptor(t *testing.T) {
var stdout, stderr bytes.Buffer
code := run([]string{"validate", "--kind", "access-descriptor", "--file", examplePath("access_descriptor.yaml")}, &stdout, &stderr)
@@ -88,3 +289,52 @@ func TestRunValidateAccessDescriptor(t *testing.T) {
func examplePath(name string) string {
return filepath.Join("..", "..", "examples", "caring", name)
}
func opsPath(name string) string {
return filepath.Join("..", "..", "examples", "ops-warden", name)
}
func opsWardenProductionSignRequest(subjectID, actor, actorType, principal string, ttlHours float64) api.CheckRequest {
return api.CheckRequest{
ID: "check:ops-warden-production-" + actor,
Tenant: "tenant:platform",
Subject: api.SubjectRef{
ID: subjectID,
Type: api.SubjectType(actorType),
},
Action: "sign",
Resource: api.ResourceRef{
ID: "ssh-cert:actor/" + actor,
Type: "ssh-certificate",
System: "ops-warden",
},
Context: map[string]any{
"principals": []string{principal},
"actor_type": actorType,
"ttl_hours": ttlHours,
"pubkey_fingerprint": "SHA256:example-production-fingerprint",
},
}
}
func postCheck(t *testing.T, url, path string) api.DecisionEnvelope {
t.Helper()
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
resp, err := http.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
t.Fatalf("POST %s: %v", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("POST %s status = %d; want 200", path, resp.StatusCode)
}
var decision api.DecisionEnvelope
if err := json.NewDecoder(resp.Body).Decode(&decision); err != nil {
t.Fatalf("decode %s response: %v", path, err)
}
return decision
}

View File

@@ -0,0 +1,114 @@
# Ops-Warden Policy Gate Handoff
Date: 2026-06-23
Workplan: FLEX-WP-0006
Ops-warden unblocker: WARDEN-WP-0009 T01
## Published flex-auth assets
- Policy package: examples/ops-warden/policy_package.md
- Policy fixtures: examples/ops-warden/policy_fixtures.yaml
- Combined registry fixture: examples/ops-warden/registry_snapshot.json
- Protected-system manifest: examples/ops-warden/protected_system_manifest.yaml
- Resource manifest: examples/ops-warden/resource_manifest.yaml
- Subject manifest: examples/ops-warden/subject_manifest.yaml
- Service request fixtures: examples/ops-warden/check_request_*.json
## Local service command
flex-auth serve --addr 127.0.0.1:8080 --registry examples/ops-warden/registry_snapshot.json --policy examples/ops-warden/policy_package.md --log /tmp/flex-auth-ops-warden-decisions.jsonl
Ops-warden can point policy.flex_auth_url at that base URL for local smoke.
Production should keep policy.fail_closed true unless an explicit break-glass
procedure exists.
## Fixture coverage
Allow fixtures:
- fixture:ops-warden-adm-sign-allow
- fixture:ops-warden-agt-sign-allow
- fixture:ops-warden-atm-sign-allow
Deny fixtures:
- fixture:ops-warden-unknown-subject-deny
- fixture:ops-warden-actor-type-mismatch-deny
- fixture:ops-warden-ttl-above-max-deny
- fixture:ops-warden-disallowed-principal-deny
- fixture:ops-warden-missing-fingerprint-deny
## Non-secret smoke evidence
CLI validation on 2026-06-23:
- protected-system manifest: valid
- resource manifest: valid
- subject manifest: valid
- registry snapshot: loaded 1 system, 1 resource manifest, 3 subjects,
3 groups, 3 relationships, and 1 tenant
- policy package: valid with 8 passing fixtures
Local /v1/check service smoke on 2026-06-23:
- allow request: effect allow, reason signing_policy_matched,
decision id decision:706efe49f68d9ef1
- deny request: effect deny, reason ttl_out_of_bounds,
decision id decision:b69bdc25a988f367
- GET /v1/check: HTTP 405
- malformed POST /v1/check: HTTP 400
- decision log contained both decision ids
## Production sequence for ops-warden
1. Deploy the flex-auth registry and policy package above to the selected
flex-auth runtime.
2. Configure ops-warden policy.flex_auth_url to the flex-auth base URL.
3. Set policy.enabled: true.
4. Keep policy.tenant as tenant:platform unless a tenant-specific policy package
is introduced.
5. Run one allow-path sign smoke and confirm signatures.log includes
policy_decision_id.
6. Run one deny-path smoke with fail_closed true and preserve only non-secret
evidence.
## Ownership boundary
flex-auth owns the authorization decision for the signing request. ops-warden
continues to own actor inventory, SSH CA operation, OpenBao SSH engine
integration, host documentation, and signatures.log production evidence.
No SSH private keys, OpenBao tokens, database credentials, or real public-key
material are stored in these fixtures.
## FLEX-WP-0007 Production Update
Additional published assets:
- Production registry fixture: examples/ops-warden/production_registry_snapshot.json
- Registry sync runbook: docs/ops-warden-registry-sync.md
Production runtime command:
flex-auth serve --addr 0.0.0.0:8080 --registry examples/ops-warden/production_registry_snapshot.json --policy examples/ops-warden/policy_package.md --log /var/log/flex-auth/ops-warden-decisions.jsonl
Use http://flex-auth.flex-auth.svc.cluster.local:8080 when cluster DNS is
reachable from warden workstations. Otherwise use the approved operator tunnel
or ingress URL. Always pre-flight GET /healthz from the same workstation before
enabling policy.enabled with fail_closed true.
Production actor coverage now verifies agt-state-hub-bridge,
agt-codex-interhub-bootstrap, adm-example, atm-backup-daily, ttl_out_of_bounds,
unknown_actor_resource, and the iam:agt-state-hub-bridge subject path used by
WARDEN_POLICY_SUBJECT.
## FLEX-WP-0007 Closeout Update
On 2026-06-29 ops-warden reported the production policy-gate smoke as passed
against the deployed flex-auth runtime at `127.0.0.1:18090` from CoulombCore.
Non-secret evidence: allow decision `decision:032b096c433ad80c` for
`agt-state-hub-bridge`, deny reason `ttl_out_of_bounds` for an excessive TTL,
and backend `vault` for the scoped OpenBao signing path. The operator is
keeping `policy.enabled` off during build-stage/pre-testing; this is a maturity
posture decision, not a missing flex-auth artifact.

View File

@@ -0,0 +1,128 @@
# Ops-Warden Registry Sync
Date: 2026-06-23
Workplan: FLEX-WP-0007
This is the flex-auth side of the production policy gate runbook for ops-warden
SSH signing. ops-warden owns actor inventory and generated registry content;
flex-auth hosts that registry, evaluates the policy package, and returns the
decision envelope used by warden sign.
## Production Runtime Target
Use the NetKingdom operator-reachable service URL as the canonical
policy.flex_auth_url. The preferred target is an in-cluster flex-auth Service
fronted by the existing operator access path:
http://flex-auth.flex-auth.svc.cluster.local:8080
If cluster DNS is not reachable from the workstation that runs warden sign, use
an approved operator tunnel or ingress URL with the same base path semantics. Do
not turn on policy.enabled with fail_closed true until this pre-flight succeeds
from the same workstation:
curl -fsS <policy.flex_auth_url>/healthz
Start the runtime with the production registry snapshot and the ops-warden
policy package:
flex-auth serve --addr 0.0.0.0:8080 --registry examples/ops-warden/production_registry_snapshot.json --policy examples/ops-warden/policy_package.md --log /var/log/flex-auth/ops-warden-decisions.jsonl
The checked-in production snapshot is a non-secret fixture and initial load
target. Regenerate it from ops-warden inventory whenever actors, principals, or
TTL defaults change.
## Current Operator Tunnel
As of 2026-06-24, the reachable operator-tunnel URL for CoulombCore is:
http://127.0.0.1:18090
The tunnel name is flex-auth-coulombcore. It forwards CoulombCore
127.0.0.1:18090 to the local flex-auth runtime on 127.0.0.1:18090. Verified
checks from CoulombCore:
- GET /healthz returned HTTP 200.
- POST /v1/check for agt-state-hub-bridge returned allow with decision:873c6c682a52bebc.
This is an operator tunnel pattern, not a substitute for a future in-cluster
Service if flex-auth should run inside the cluster.
## Ownership Contract
| Concern | Owner | Notes |
| --- | --- | --- |
| Actor names and actor types | ops-warden | inventory.yaml defines adm, agt, and atm actors. |
| Default principals and TTLs | ops-warden | Used by warden sign and by generated registry attributes. |
| Registry hosting and reload | flex-auth | Runtime serves the generated snapshot and evaluates it with the policy package. |
| Policy package semantics | flex-auth | examples/ops-warden/policy_package.md owns allow and deny reasons. |
| OpenBao SSH signing | ops-warden | flex-auth never receives SSH private keys or Vault tokens. |
| Production policy.enabled flip | ops-warden operator | Only after healthz and allow/deny smoke pass. |
## Sync Procedure
1. In ops-warden, update the managed inventory source or ~/.config/warden/inventory.yaml.
2. Regenerate the flex-auth snapshot from ops-warden:
python scripts/build_flex_auth_registry.py ~/.config/warden/inventory.yaml -o registry/flex-auth/production_registry_snapshot.json
3. Validate the generated file before handoff:
flex-auth load-registry --file registry/flex-auth/production_registry_snapshot.json
4. Copy or promote the snapshot to the flex-auth runtime. For repo-level drift
coverage, update examples/ops-warden/production_registry_snapshot.json when
the intended production fixture changes.
5. Restart or reload the flex-auth runtime with the new snapshot.
6. From the workstation that runs warden sign, verify:
curl -fsS <policy.flex_auth_url>/healthz
7. Run one allow smoke and one deny smoke. Record only non-secret evidence:
actor name, decision id, effect, reason, backend, and whether a certificate
was issued.
## Current Production Fixture
The initial fixture mirrors ops-warden production inventory as of 2026-06-23.
It registers:
| Actor | Type | Principal | Max TTL hours | Allowed subjects |
| --- | --- | --- | --- | --- |
| adm-example | adm | adm-full | 48 | adm-example, iam:adm-example |
| agt-codex-interhub-bootstrap | agt | agt-interhub-bootstrap | 2 | agt-codex-interhub-bootstrap, iam:agt-codex-interhub-bootstrap |
| agt-state-hub-bridge | agt | agt-task-bridge | 24 | agt-state-hub-bridge, iam:agt-state-hub-bridge |
| atm-backup-daily | atm | atm-backup-daily | 8 | atm-backup-daily, iam:atm-backup-daily |
The IAM subject form is intended for WARDEN_POLICY_SUBJECT. If that environment
variable is unset, ops-warden sends the actor name and the same policy path
continues to work.
## Smoke Expectations
Allow path:
warden sign agt-state-hub-bridge
Expected non-secret evidence: decision effect allow, reason
signing_policy_matched, signatures.log includes policy_decision_id.
Deny path:
warden sign agt-state-hub-bridge --ttl 999
Expected non-secret evidence: effect deny, reason ttl_out_of_bounds, no
certificate issued. With fail_closed true, unreachable flex-auth must also block
signing.
OpenBao-backed signing remains an operator smoke because it requires a scoped
VAULT_TOKEN. The previous session returned HTTP 403 on 2026-06-23; retry with:
SMOKE_VAULT=1 ~/ops-warden/scripts/policy_gate_production_smoke.sh
## References
- docs/ops-warden-policy-gate-handoff.md
- examples/ops-warden/production_registry_snapshot.json
- ~/ops-warden/wiki/PolicyGatedSigning.md
- ~/ops-warden/history/2026-06-23-flex-auth-policy-gate-production-smoke.md

View File

@@ -1,6 +1,6 @@
# Flex-Auth Workplan Planning Map
Date: 2026-05-17
Date: 2026-06-30
## Purpose
@@ -21,9 +21,11 @@ This document captures the current sequencing view for flex-auth workplans.
| --- | --- | --- | --- | --- |
| `FLEX-WP-0001` | complete | done | none | Repo intent, boundaries, and authorization landscape research are complete. |
| `FLEX-WP-0005` | complete | done | `FLEX-WP-0001` | Foundations and Topaz alignment are complete: ADR-001/002/003, Go skeleton, `FlexAuthResourceManifest` schema pin, Topaz mapping spike, IAM Profile citation, ops-warden boundary clarification. |
| `FLEX-WP-0002` | P0 | ready | `FLEX-WP-0001`, `FLEX-WP-0005` | Standalone policy-as-code core: schemas, local registry, CARING profile/descriptors, Rego-in-Markdown policy packages, check APIs, explanations, decision log, CLI/service skeleton, tests. |
| `FLEX-WP-0003` | P1 | blocked | `FLEX-WP-0002` | Markitect consumer integration and first CARING benchmark: resource namespace, manifest import, action vocabulary, descriptor fixtures, decision fixtures, integration docs. |
| `FLEX-WP-0004` | P2 | blocked | `FLEX-WP-0002`, `FLEX-WP-0005` | Delegated PDP and directory adapters: Topaz adapter implementation (evaluation already done in `0005`), OpenFGA/SpiceDB, OPA/Cedar, Keycloak Authorization Services, Entra/Graph/SCIM, CARING envelope preservation. |
| `FLEX-WP-0002` | complete | completed | `FLEX-WP-0001`, `FLEX-WP-0005` | Standalone policy-as-code core is complete: schemas, local registry, CARING profile/descriptors, Rego-in-Markdown policy packages, check APIs, explanations, decision log, CLI/service skeleton, tests. |
| `FLEX-WP-0003` | complete | completed | `FLEX-WP-0002` | Markitect consumer integration and first CARING benchmark are complete: resource namespace, manifest import, action vocabulary, descriptor fixtures, decision fixtures, integration docs. |
| `FLEX-WP-0004` | complete | completed | `FLEX-WP-0002`, `FLEX-WP-0005` | Delegated PDP and directory adapter boundary work is complete: Topaz adapter shape, OpenFGA/SpiceDB, OPA/Cedar, Keycloak Authorization Services, Entra/Graph/SCIM, CARING envelope preservation. |
| `FLEX-WP-0006` | complete | finished | `FLEX-WP-0002`, `FLEX-WP-0005` | Ops-warden unblocker is complete: flex-auth publishes `ssh-certificate` / `sign` policies, fixtures, and `/v1/check` smoke evidence for the opt-in pre-sign gate shipped in ops-warden `WARDEN-WP-0007` and tracked for production in `WARDEN-WP-0009`. |
| `FLEX-WP-0007` | complete | finished | `FLEX-WP-0006` | Production registry fixture, sync contract, runtime command, healthz coverage, real actor/IAM tests, operator tunnel reachability, and vault-backed joint smoke are complete. `policy.enabled` remains off by maturity decision until testing/production posture calls for live enforcement. |
## Dependency Notes
@@ -58,6 +60,14 @@ now implements the Topaz adapter against the spike's output.
Delegated adapters must preserve flex-auth's CARING descriptor and
conformance fields even when backend-native role semantics differ.
`FLEX-WP-0006` was the cross-repo integration unblocker for
ops-warden. ops-warden already implements the opt-in policy call
(`policy.enabled: true`) and production OpenBao signing works without the
gate. flex-auth now publishes the protected-system manifest,
`ssh-certificate` / `sign` policy package, allow/deny fixtures, and
`POST /v1/check` evidence that ops-warden can use before enabling
`policy.enabled` in production.
## State Hub Mirror
Native State Hub dependency edges:
@@ -68,3 +78,9 @@ Native State Hub dependency edges:
- `FLEX-WP-0003 -> FLEX-WP-0002`
- `FLEX-WP-0004 -> FLEX-WP-0002`
- `FLEX-WP-0004 -> FLEX-WP-0005` (Topaz adapter consumes the spike)
- `FLEX-WP-0006 -> FLEX-WP-0002`
- `FLEX-WP-0006 -> FLEX-WP-0005`
- ops-warden: `WARDEN-WP-0009` finished (caller + registry smoke). FLEX-WP-0007
is also finished; production `policy.enabled: true` waits for a later
maturity/posture decision, not for repo-side flex-auth artifacts.
- `FLEX-WP-0007 -> FLEX-WP-0006`

View File

@@ -13,6 +13,8 @@ examples/
# decision, registry, and audit fixtures (P2.1)
markitect/ # FlexAuthResourceManifest fixtures, decision
# fixtures, and Rego-in-Markdown policy packages
ops-warden/ # SSH certificate signing policy-gate fixtures
# for ops-warden policy.enabled smoke checks
topaz/ # docker-compose + sample directory and policy
# for the Topaz alignment spike (P5.4)
policies/ # generic Rego-in-Markdown packages used by

View File

@@ -0,0 +1,49 @@
# Ops-Warden SSH Signing Policy Gate
This example is the flex-auth side of ops-warden's opt-in pre-sign gate.
When `policy.enabled: true`, ops-warden calls `POST /v1/check` before signing
or issuing an SSH certificate.
Files:
- `protected_system_manifest.yaml` declares the `ops-warden` protected system,
`ssh-certificate` resource type, and `sign` action.
- `resource_manifest.yaml` declares fixture SSH certificate actor resources and
non-secret policy attributes such as allowed principals and TTL maxima.
- `subject_manifest.yaml` declares non-secret fixture actors for `adm`, `agt`,
and `atm` signing paths.
- `registry_snapshot.json` is the combined local registry used by the CLI and
service examples.
- `policy_package.md` is the Rego-in-Markdown policy package.
- `policy_fixtures.yaml` contains allow and deny expectations for package
validation.
- `check_request_*.json` files are ops-warden-shaped `/v1/check` requests.
Run locally:
```bash
flex-auth validate --kind protected-system --file examples/ops-warden/protected_system_manifest.yaml
flex-auth validate --kind resource-manifest --file examples/ops-warden/resource_manifest.yaml
flex-auth validate --kind subject-manifest --file examples/ops-warden/subject_manifest.yaml
flex-auth load-registry --file examples/ops-warden/registry_snapshot.json
flex-auth test-policy --file examples/ops-warden/policy_package.md
flex-auth check --registry examples/ops-warden/registry_snapshot.json --policy examples/ops-warden/policy_package.md --request examples/ops-warden/check_request_allow_adm.json
```
The fixture public-key fingerprints are examples only. Do not put real keys,
OpenBao tokens, or private signing material in these files.
## Production Registry Fixture
production_registry_snapshot.json is a non-secret fixture generated by
ops-warden for FLEX-WP-0007 coverage. It mirrors the current production actor
names used by ops-warden inventory and should be refreshed when that inventory
changes.
Validate both registries locally:
flex-auth load-registry --file examples/ops-warden/registry_snapshot.json
flex-auth load-registry --file examples/ops-warden/production_registry_snapshot.json
The production sync contract is documented in docs/ops-warden-registry-sync.md.

View File

@@ -0,0 +1,23 @@
{
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"platform",
"root"
],
"actor_type": "adm",
"ttl_hours": 4,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "agt"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"deploy"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-backup-automation-atm",
"tenant": "tenant:platform",
"subject": {
"id": "backup-automation",
"type": "atm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/backup-automation",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"backup"
],
"actor_type": "atm",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-atm-fingerprint"
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"deploy"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "agt"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"root"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
}

View File

@@ -0,0 +1,21 @@
{
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 4
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 12,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
}

View File

@@ -0,0 +1,22 @@
{
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "unknown-actor",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden"
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 4,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
}

View File

@@ -0,0 +1,337 @@
[
{
"id": "fixture:ops-warden-adm-sign-allow",
"request": {
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": [
"platform-steward",
"iam:platform-steward"
],
"allowed_principals": [
"platform",
"root"
],
"max_ttl_hours": 8
}
},
"context": {
"principals": [
"platform",
"root"
],
"actor_type": "adm",
"ttl_hours": 4,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
},
"expect": {
"effect": "allow",
"reason": "signing_policy_matched"
}
},
{
"id": "fixture:ops-warden-agt-sign-allow",
"request": {
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "agt"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "ci-deploy-agent",
"actor_type": "agt",
"allowed_subjects": [
"ci-deploy-agent",
"iam:ci-deploy-agent"
],
"allowed_principals": [
"deploy",
"git"
],
"max_ttl_hours": 2
}
},
"context": {
"principals": [
"deploy"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
},
"expect": {
"effect": "allow",
"reason": "signing_policy_matched"
}
},
{
"id": "fixture:ops-warden-atm-sign-allow",
"request": {
"id": "check:ops-warden-backup-automation-atm",
"tenant": "tenant:platform",
"subject": {
"id": "backup-automation",
"type": "atm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/backup-automation",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "backup-automation",
"actor_type": "atm",
"allowed_subjects": [
"backup-automation",
"iam:backup-automation"
],
"allowed_principals": [
"backup"
],
"max_ttl_hours": 1
}
},
"context": {
"principals": [
"backup"
],
"actor_type": "atm",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-atm-fingerprint"
}
},
"expect": {
"effect": "allow",
"reason": "signing_policy_matched"
}
},
{
"id": "fixture:ops-warden-unknown-subject-deny",
"request": {
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "unknown-actor",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": [
"platform-steward",
"iam:platform-steward"
],
"allowed_principals": [
"platform",
"root"
],
"max_ttl_hours": 8
}
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 4,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
},
"expect": {
"effect": "deny",
"reason": "unknown_subject"
}
},
{
"id": "fixture:ops-warden-actor-type-mismatch-deny",
"request": {
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "ci-deploy-agent",
"actor_type": "agt",
"allowed_subjects": [
"ci-deploy-agent",
"iam:ci-deploy-agent"
],
"allowed_principals": [
"deploy",
"git"
],
"max_ttl_hours": 2
}
},
"context": {
"principals": [
"deploy"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
},
"expect": {
"effect": "deny",
"reason": "actor_type_mismatch"
}
},
{
"id": "fixture:ops-warden-ttl-above-max-deny",
"request": {
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": [
"platform-steward",
"iam:platform-steward"
],
"allowed_principals": [
"platform",
"root"
],
"max_ttl_hours": 8
}
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 12,
"pubkey_fingerprint": "SHA256:example-adm-fingerprint"
}
},
"expect": {
"effect": "deny",
"reason": "ttl_out_of_bounds"
}
},
{
"id": "fixture:ops-warden-disallowed-principal-deny",
"request": {
"id": "check:ops-warden-ci-deploy-agent-agt",
"tenant": "tenant:platform",
"subject": {
"id": "ci-deploy-agent",
"type": "agt"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "ci-deploy-agent",
"actor_type": "agt",
"allowed_subjects": [
"ci-deploy-agent",
"iam:ci-deploy-agent"
],
"allowed_principals": [
"deploy",
"git"
],
"max_ttl_hours": 2
}
},
"context": {
"principals": [
"root"
],
"actor_type": "agt",
"ttl_hours": 1,
"pubkey_fingerprint": "SHA256:example-agt-fingerprint"
}
},
"expect": {
"effect": "deny",
"reason": "disallowed_principal"
}
},
{
"id": "fixture:ops-warden-missing-fingerprint-deny",
"request": {
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {
"id": "platform-steward",
"type": "adm"
},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": [
"platform-steward",
"iam:platform-steward"
],
"allowed_principals": [
"platform",
"root"
],
"max_ttl_hours": 8
}
},
"context": {
"principals": [
"platform"
],
"actor_type": "adm",
"ttl_hours": 4
}
},
"expect": {
"effect": "deny",
"reason": "missing_pubkey_fingerprint"
}
}
]

View File

@@ -0,0 +1,257 @@
---
id: ops-warden.ssh-certificate.sign
name: Ops-Warden SSH certificate signing
namespace: ops-warden:ssh-certificate
version: v1
status: ready
package: flexauth.ops_warden.ssh_signing
actions:
- sign
owner: team:platform-security
fixtures:
- policy_fixtures.yaml
caring:
profile: caring-0.4.0-rc2
enforce: false
canonical_roles:
- Operator
organization_relations:
- ServiceProvider
scopes:
- level: Platform
id: platform:ssh-signing
tenant: tenant:platform
planes:
- Identity
- Secret
- Audit
capabilities:
- Use
- Operate
- Audit
exposure_modes:
- Metadata
conditions:
- TimeLimited
- Logged
restrictions:
- PrivilegeEscalationBlocked
- SecretAccessBlocked
activation:
mode: local
metadata:
source: examples/ops-warden/policy_package.md
ops_warden_policy_gate: v2
---
# Ops-Warden SSH Certificate Signing
This package authorizes ops-warden's opt-in pre-sign policy gate. The caller
keeps SSH CA custody, actor inventory, and OpenBao signing; flex-auth decides
whether a specific `sign` request is allowed now.
## Rules
```rego
import future.keywords.contains
import future.keywords.if
import future.keywords.in
actor_types := {"adm", "agt", "atm"}
decision := {"effect": "allow", "reason": "signing_policy_matched"} if {
allowed
} else := {"effect": "deny", "reason": first_denial} if {
true
}
allowed if {
input.action == "sign"
input.resource.system == "ops-warden"
input.resource.type == "ssh-certificate"
effective_tenant == "tenant:platform"
valid_actor_type
subject_type_matches_context
actor_type_matches_resource
resource_id_matches_actor
subject_id_allowed
valid_ttl
has_pubkey_fingerprint
principals_allowed
}
default effective_tenant := ""
effective_tenant := input.tenant if {
is_string(input.tenant)
input.tenant != ""
} else := input.resource.tenant if {
is_string(input.resource.tenant)
input.resource.tenant != ""
} else := input.subject.tenant if {
is_string(input.subject.tenant)
input.subject.tenant != ""
}
default first_denial := "no_matching_rule"
first_denial := "wrong_action" if {
input.action != "sign"
} else := "wrong_system" if {
input.resource.system != "ops-warden"
} else := "wrong_resource_type" if {
input.resource.type != "ssh-certificate"
} else := "wrong_tenant" if {
effective_tenant != "tenant:platform"
} else := "unknown_actor_resource" if {
not has_actor_resource
} else := "unknown_subject" if {
not subject_id_allowed
} else := "actor_type_mismatch" if {
not valid_actor_type
} else := "actor_type_mismatch" if {
not subject_type_matches_context
} else := "actor_type_mismatch" if {
not actor_type_matches_resource
} else := "actor_resource_mismatch" if {
not resource_id_matches_actor
} else := "ttl_out_of_bounds" if {
not valid_ttl
} else := "missing_pubkey_fingerprint" if {
not has_pubkey_fingerprint
} else := "missing_principal" if {
not has_principals
} else := "disallowed_principal" if {
count(disallowed_principals) > 0
}
has_actor_resource if {
is_string(input.resource.attributes.actor_id)
input.resource.attributes.actor_id != ""
}
valid_actor_type if {
is_string(input.context.actor_type)
input.context.actor_type in actor_types
}
subject_type_matches_context if {
input.subject.type == input.context.actor_type
}
subject_type_matches_context if {
input.subject.attributes.actor_type == input.context.actor_type
}
actor_type_matches_resource if {
input.context.actor_type == input.resource.attributes.actor_type
}
resource_id_matches_actor if {
input.resource.id == sprintf("ssh-cert:actor/%s", [input.resource.attributes.actor_id])
}
subject_id_allowed if {
input.subject.id in input.resource.attributes.allowed_subjects
}
has_ttl if {
is_number(input.context.ttl_hours)
}
valid_ttl if {
has_ttl
input.context.ttl_hours > 0
input.context.ttl_hours <= input.resource.attributes.max_ttl_hours
}
has_pubkey_fingerprint if {
is_string(input.context.pubkey_fingerprint)
input.context.pubkey_fingerprint != ""
}
has_principals if {
count(input.context.principals) > 0
}
principals_allowed if {
has_principals
count(disallowed_principals) == 0
}
allowed_principal(principal) if {
principal in input.resource.attributes.allowed_principals
}
disallowed_principals contains principal if {
principal := input.context.principals[_]
not allowed_principal(principal)
}
```
## Tests
```rego test
package flexauth.ops_warden.ssh_signing_test
import future.keywords.if
import data.flexauth.ops_warden.ssh_signing
adm_request := {
"id": "check:ops-warden-platform-steward-adm",
"tenant": "tenant:platform",
"subject": {"id": "platform-steward", "type": "adm"},
"action": "sign",
"resource": {
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"system": "ops-warden",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": ["platform-steward", "iam:platform-steward"],
"allowed_principals": ["platform", "root"],
"max_ttl_hours": 8
}
},
"context": {
"actor_type": "adm",
"principals": ["platform"],
"pubkey_fingerprint": "SHA256:example-adm-fingerprint",
"ttl_hours": 4
}
}
test_adm_sign_allowed if {
ssh_signing.decision.effect == "allow" with input as adm_request
}
test_high_ttl_denied if {
ssh_signing.decision.reason == "ttl_out_of_bounds" with input as {
"tenant": "tenant:platform",
"subject": {"id": "platform-steward", "type": "adm"},
"action": "sign",
"resource": adm_request.resource,
"context": {
"actor_type": "adm",
"principals": ["platform"],
"pubkey_fingerprint": "SHA256:example-adm-fingerprint",
"ttl_hours": 12
}
}
}
test_missing_fingerprint_denied if {
ssh_signing.decision.reason == "missing_pubkey_fingerprint" with input as {
"tenant": "tenant:platform",
"subject": {"id": "platform-steward", "type": "adm"},
"action": "sign",
"resource": adm_request.resource,
"context": {
"actor_type": "adm",
"principals": ["platform"],
"ttl_hours": 4
}
}
}
```

View File

@@ -0,0 +1,450 @@
{
"systems": [
{
"id": "ops-warden",
"name": "Ops Warden",
"resource_types": [
{
"name": "ssh-certificate",
"scope_level": "Resource",
"planes": [
"Identity",
"Secret",
"Audit"
],
"metadata": {
"description": "Short-lived SSH certificate signing request."
}
}
],
"actions": [
{
"name": "sign",
"capabilities": [
"Use",
"Operate",
"Audit"
],
"planes": [
"Identity",
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [
"principals",
"actor_type",
"pubkey_fingerprint",
"ttl_hours"
]
}
}
],
"caring_profiles": [
"caring-0.4.0-rc2"
],
"metadata": {
"flex_auth_contract": "protected-system-v0",
"ops_warden_policy_gate": "v2",
"policy_enabled_config": "policy.enabled",
"tenant": "tenant:platform"
}
}
],
"resource_manifests": [
{
"id": "ops-warden-ssh-certificates",
"system": "ops-warden",
"resources": [
{
"id": "ssh-cert:actor/adm-example",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"adm"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "adm-example",
"actor_type": "adm",
"allowed_subjects": [
"adm-example",
"iam:adm-example"
],
"allowed_principals": [
"adm-full"
],
"max_ttl_hours": 48
}
},
{
"id": "ssh-cert:actor/agt-codex-interhub-bootstrap",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"agt"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "agt-codex-interhub-bootstrap",
"actor_type": "agt",
"allowed_subjects": [
"agt-codex-interhub-bootstrap",
"iam:agt-codex-interhub-bootstrap"
],
"allowed_principals": [
"agt-interhub-bootstrap"
],
"max_ttl_hours": 2
}
},
{
"id": "ssh-cert:actor/agt-state-hub-bridge",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"agt"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "agt-state-hub-bridge",
"actor_type": "agt",
"allowed_subjects": [
"agt-state-hub-bridge",
"iam:agt-state-hub-bridge"
],
"allowed_principals": [
"agt-task-bridge"
],
"max_ttl_hours": 24
}
},
{
"id": "ssh-cert:actor/atm-backup-daily",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"atm"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "atm-backup-daily",
"actor_type": "atm",
"allowed_subjects": [
"atm-backup-daily",
"iam:atm-backup-daily"
],
"allowed_principals": [
"atm-backup-daily"
],
"max_ttl_hours": 8
}
}
],
"actions": [
"sign"
],
"caring_profile": "caring-0.4.0-rc2",
"metadata": {
"flex_auth_contract": "resource-registration-v0",
"tenant": "tenant:platform"
}
}
],
"tenants": [
{
"id": "tenant:platform",
"name": "Platform Tenant"
}
],
"subjects": [
{
"id": "adm-example",
"type": "Agent",
"display_name": "Example human operator \u2014 replace with per-person adm-* actors",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-admins"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "adm"
}
},
{
"id": "agt-codex-interhub-bootstrap",
"type": "Agent",
"display_name": "Short-lived agent access for attended Inter-Hub bootstrap",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-agents"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "agt"
}
},
{
"id": "agt-state-hub-bridge",
"type": "Agent",
"display_name": "ops-bridge tunnel agent for state-hub",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-agents"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "agt"
}
},
{
"id": "atm-backup-daily",
"type": "Automation",
"display_name": "Example nightly automation actor",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-automations"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "atm"
}
}
],
"groups": [
{
"id": "group:ops-warden-admins",
"display_name": "Ops Warden Admins",
"members": [
"adm-example"
],
"tenant": "tenant:platform"
},
{
"id": "group:ops-warden-agents",
"display_name": "Ops Warden Agents",
"members": [
"agt-codex-interhub-bootstrap",
"agt-state-hub-bridge"
],
"tenant": "tenant:platform"
},
{
"id": "group:ops-warden-automations",
"display_name": "Ops Warden Automations",
"members": [
"atm-backup-daily"
],
"tenant": "tenant:platform"
}
],
"relationships": [
{
"id": "rel:adm-example-sign-adm-example",
"system": "ops-warden",
"subject": "group:ops-warden-admins",
"relation": "signer",
"object": "ssh-cert:actor/adm-example",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-adm-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/adm-example",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/adm-example"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
},
{
"id": "rel:agt-codex-interhub-bootstrap-sign-agt-codex-interhub-bootstrap",
"system": "ops-warden",
"subject": "group:ops-warden-agents",
"relation": "signer",
"object": "ssh-cert:actor/agt-codex-interhub-bootstrap",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-agt-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/agt-codex-interhub-bootstrap",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/agt-codex-interhub-bootstrap"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
},
{
"id": "rel:agt-state-hub-bridge-sign-agt-state-hub-bridge",
"system": "ops-warden",
"subject": "group:ops-warden-agents",
"relation": "signer",
"object": "ssh-cert:actor/agt-state-hub-bridge",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-agt-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/agt-state-hub-bridge",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/agt-state-hub-bridge"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
},
{
"id": "rel:atm-backup-daily-sign-atm-backup-daily",
"system": "ops-warden",
"subject": "group:ops-warden-automations",
"relation": "signer",
"object": "ssh-cert:actor/atm-backup-daily",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-atm-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/atm-backup-daily",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/atm-backup-daily"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
}
]
}

View File

@@ -0,0 +1,36 @@
id: ops-warden
name: Ops Warden
resource_types:
- name: ssh-certificate
scope_level: Resource
planes:
- Identity
- Secret
- Audit
metadata:
description: Short-lived SSH certificate signing request.
actions:
- name: sign
capabilities:
- Use
- Operate
- Audit
planes:
- Identity
- Secret
- Audit
exposure_modes:
- Metadata
metadata:
required_context:
- principals
- actor_type
- pubkey_fingerprint
- ttl_hours
caring_profiles:
- caring-0.4.0-rc2
metadata:
flex_auth_contract: protected-system-v0
ops_warden_policy_gate: v2
policy_enabled_config: policy.enabled
tenant: tenant:platform

View File

@@ -0,0 +1,366 @@
{
"systems": [
{
"id": "ops-warden",
"name": "Ops Warden",
"resource_types": [
{
"name": "ssh-certificate",
"scope_level": "Resource",
"planes": [
"Identity",
"Secret",
"Audit"
],
"metadata": {
"description": "Short-lived SSH certificate signing request."
}
}
],
"actions": [
{
"name": "sign",
"capabilities": [
"Use",
"Operate",
"Audit"
],
"planes": [
"Identity",
"Secret",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"metadata": {
"required_context": [
"principals",
"actor_type",
"pubkey_fingerprint",
"ttl_hours"
]
}
}
],
"caring_profiles": [
"caring-0.4.0-rc2"
],
"metadata": {
"flex_auth_contract": "protected-system-v0",
"ops_warden_policy_gate": "v2",
"policy_enabled_config": "policy.enabled",
"tenant": "tenant:platform"
}
}
],
"resource_manifests": [
{
"id": "ops-warden-ssh-certificates",
"system": "ops-warden",
"resources": [
{
"id": "ssh-cert:actor/platform-steward",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"adm"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "platform-steward",
"actor_type": "adm",
"allowed_subjects": [
"platform-steward",
"iam:platform-steward"
],
"allowed_principals": [
"platform",
"root"
],
"max_ttl_hours": 8
}
},
{
"id": "ssh-cert:actor/ci-deploy-agent",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"agt"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "ci-deploy-agent",
"actor_type": "agt",
"allowed_subjects": [
"ci-deploy-agent",
"iam:ci-deploy-agent"
],
"allowed_principals": [
"deploy",
"git"
],
"max_ttl_hours": 2
}
},
{
"id": "ssh-cert:actor/backup-automation",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"atm"
],
"trust_zone": "platform",
"owner": "team:platform-security",
"attributes": {
"actor_id": "backup-automation",
"actor_type": "atm",
"allowed_subjects": [
"backup-automation",
"iam:backup-automation"
],
"allowed_principals": [
"backup"
],
"max_ttl_hours": 1
}
}
],
"actions": [
"sign"
],
"caring_profile": "caring-0.4.0-rc2",
"metadata": {
"flex_auth_contract": "resource-registration-v0",
"tenant": "tenant:platform"
}
}
],
"tenants": [
{
"id": "tenant:platform",
"name": "Platform Tenant"
}
],
"subjects": [
{
"id": "platform-steward",
"type": "Agent",
"display_name": "Platform Steward",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-admins"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "adm"
}
},
{
"id": "ci-deploy-agent",
"type": "Agent",
"display_name": "CI Deploy Agent",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-agents"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "agt"
}
},
{
"id": "backup-automation",
"type": "Automation",
"display_name": "Backup Automation",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-automations"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "atm"
}
}
],
"groups": [
{
"id": "group:ops-warden-admins",
"display_name": "Ops Warden Admin Actors",
"members": [
"platform-steward"
],
"tenant": "tenant:platform"
},
{
"id": "group:ops-warden-agents",
"display_name": "Ops Warden Agent Actors",
"members": [
"ci-deploy-agent"
],
"tenant": "tenant:platform"
},
{
"id": "group:ops-warden-automations",
"display_name": "Ops Warden Automation Actors",
"members": [
"backup-automation"
],
"tenant": "tenant:platform"
}
],
"relationships": [
{
"id": "rel:platform-steward-sign-platform-steward",
"system": "ops-warden",
"subject": "group:ops-warden-admins",
"relation": "signer",
"object": "ssh-cert:actor/platform-steward",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-adm-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/platform-steward",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/platform-steward"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
},
{
"id": "rel:ci-deploy-agent-sign-ci-deploy-agent",
"system": "ops-warden",
"subject": "group:ops-warden-agents",
"relation": "signer",
"object": "ssh-cert:actor/ci-deploy-agent",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-agt-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/ci-deploy-agent",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/ci-deploy-agent"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
},
{
"id": "rel:backup-automation-sign-backup-automation",
"system": "ops-warden",
"subject": "group:ops-warden-automations",
"relation": "signer",
"object": "ssh-cert:actor/backup-automation",
"tenant": "tenant:platform",
"conditions": [
"TimeLimited",
"Logged"
],
"caring": {
"id": "descriptor:ops-warden-atm-signer",
"profile": "caring-0.4.0-rc2",
"subject_type": "Group",
"organization_relation": "ServiceProvider",
"canonical_role": "Operator",
"scope": {
"level": "Resource",
"id": "ssh-cert:actor/backup-automation",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/backup-automation"
},
"planes": [
"Identity",
"Secret",
"Audit"
],
"capabilities": [
"Use",
"Operate",
"Audit"
],
"exposure_modes": [
"Metadata"
],
"conditions": [
"TimeLimited",
"Logged"
],
"restrictions": [
"PrivilegeEscalationBlocked",
"SecretAccessBlocked"
],
"access_path": "mediated"
}
}
]
}

View File

@@ -0,0 +1,59 @@
id: ops-warden-ssh-certificates
system: ops-warden
resources:
- id: ssh-cert:actor/platform-steward
type: ssh-certificate
labels:
- ssh-signing
- adm
trust_zone: platform
owner: team:platform-security
attributes:
actor_id: platform-steward
actor_type: adm
allowed_subjects:
- platform-steward
- iam:platform-steward
allowed_principals:
- platform
- root
max_ttl_hours: 8
- id: ssh-cert:actor/ci-deploy-agent
type: ssh-certificate
labels:
- ssh-signing
- agt
trust_zone: platform
owner: team:platform-security
attributes:
actor_id: ci-deploy-agent
actor_type: agt
allowed_subjects:
- ci-deploy-agent
- iam:ci-deploy-agent
allowed_principals:
- deploy
- git
max_ttl_hours: 2
- id: ssh-cert:actor/backup-automation
type: ssh-certificate
labels:
- ssh-signing
- atm
trust_zone: platform
owner: team:platform-security
attributes:
actor_id: backup-automation
actor_type: atm
allowed_subjects:
- backup-automation
- iam:backup-automation
allowed_principals:
- backup
max_ttl_hours: 1
actions:
- sign
caring_profile: caring-0.4.0-rc2
metadata:
flex_auth_contract: resource-registration-v0
tenant: tenant:platform

View File

@@ -0,0 +1,54 @@
id: subjects:ops-warden-platform
tenants:
- id: tenant:platform
name: Platform Tenant
subjects:
- id: platform-steward
type: Agent
display_name: Platform Steward
organization_relation: ServiceProvider
roles:
- Operator
groups:
- group:ops-warden-admins
tenant: tenant:platform
metadata:
actor_type: adm
- id: ci-deploy-agent
type: Agent
display_name: CI Deploy Agent
organization_relation: ServiceProvider
roles:
- Operator
groups:
- group:ops-warden-agents
tenant: tenant:platform
metadata:
actor_type: agt
- id: backup-automation
type: Automation
display_name: Backup Automation
organization_relation: ServiceProvider
roles:
- Operator
groups:
- group:ops-warden-automations
tenant: tenant:platform
metadata:
actor_type: atm
groups:
- id: group:ops-warden-admins
display_name: Ops Warden Admin Actors
members:
- platform-steward
tenant: tenant:platform
- id: group:ops-warden-agents
display_name: Ops Warden Agent Actors
members:
- ci-deploy-agent
tenant: tenant:platform
- id: group:ops-warden-automations
display_name: Ops Warden Automation Actors
members:
- backup-automation
tenant: tenant:platform

View File

@@ -105,6 +105,7 @@ func (e *Engine) BatchCheck(ctx context.Context, request api.BatchCheckRequest)
for _, resource := range request.Resources {
decision, err := e.Check(ctx, api.CheckRequest{
ID: request.ID,
Tenant: request.Tenant,
Subject: request.Subject,
Action: request.Action,
Resource: resource,
@@ -188,6 +189,15 @@ func (e *Engine) normalizeRequest(request api.CheckRequest) (api.CheckRequest, r
normalized := request
facts := registryFacts{}
if normalized.Tenant != "" {
if normalized.Subject.Tenant == "" {
normalized.Subject.Tenant = normalized.Tenant
}
if normalized.Resource.Tenant == "" {
normalized.Resource.Tenant = normalized.Tenant
}
}
if subject, ok := e.store.Subject(request.Subject.ID); ok {
facts.subjectFound = true
facts.subject = subject

View File

@@ -74,6 +74,28 @@ func TestRedactPolicyPackageMarkdownValidates(t *testing.T) {
}
}
func TestOpsWardenPolicyPackageMarkdownValidates(t *testing.T) {
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "ops-warden", "policy_package.md"))
if err != nil {
t.Fatalf("LoadAndValidateFile: %v", err)
}
if !pkg.Valid {
t.Fatalf("pkg.Valid = false\n%s", formatValidation(pkg.Validation))
}
if pkg.Metadata.Namespace != "ops-warden:ssh-certificate" {
t.Fatalf("metadata.Namespace = %q; want ops-warden:ssh-certificate", pkg.Metadata.Namespace)
}
if len(pkg.Validation.Fixtures) != 8 {
t.Fatalf("Validation.Fixtures len = %d; want 8", len(pkg.Validation.Fixtures))
}
for _, fixture := range pkg.Validation.Fixtures {
if !fixture.Passed {
t.Fatalf("fixture %s failed: %s\nactual: %+v", fixture.ID, fixture.Error, fixture.Actual)
}
}
}
func TestCaringFindingsAreAdvisoryUntilEnforced(t *testing.T) {
doc := inlinePolicy(false, "allow")
pkg, err := policy.Load([]byte(doc), "inline-policy.md")

View File

@@ -148,6 +148,7 @@ type DecisionExpectation struct {
// CheckRequest is the stable protected-system-facing decision request.
type CheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resource ResourceRef `json:"resource" yaml:"resource"`
@@ -159,6 +160,7 @@ type CheckRequest struct {
// BatchCheckRequest evaluates one subject/action against multiple resources.
type BatchCheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resources []ResourceRef `json:"resources" yaml:"resources"`

View File

@@ -7,6 +7,7 @@
"required": ["subject", "action", "resource"],
"properties": {
"id": {"type": "string", "minLength": 1},
"tenant": {"type": "string", "minLength": 1},
"subject": {"$ref": "#/$defs/subject_ref"},
"action": {"type": "string", "minLength": 1},
"resource": {"$ref": "#/$defs/resource_ref"},

View File

@@ -2,7 +2,7 @@
id: FLEX-WP-0001
type: workplan
title: "Repo Intent and Authorization Architecture Baseline"
domain: netkingdom
domain: infotech
status: done
owner: flex-auth
topic_slug: flex-auth

View File

@@ -2,7 +2,7 @@
id: FLEX-WP-0002
type: workplan
title: "Standalone Policy-as-Code Core"
domain: netkingdom
domain: infotech
status: completed
owner: flex-auth
topic_slug: flex-auth

View File

@@ -2,7 +2,7 @@
id: FLEX-WP-0003
type: workplan
title: "Markitect Consumer Integration"
domain: netkingdom
domain: infotech
status: completed
owner: flex-auth
topic_slug: flex-auth

View File

@@ -2,7 +2,7 @@
id: FLEX-WP-0004
type: workplan
title: "Delegated PDP and Directory Adapters"
domain: netkingdom
domain: infotech
status: completed
owner: flex-auth
topic_slug: flex-auth

View File

@@ -2,7 +2,7 @@
id: FLEX-WP-0005
type: workplan
title: "Foundations and Topaz Alignment"
domain: netkingdom
domain: infotech
status: done
owner: flex-auth
topic_slug: flex-auth

View File

@@ -0,0 +1,230 @@
---
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/<actor-name>` |
| `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/<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`.

View File

@@ -0,0 +1,223 @@
---
id: FLEX-WP-0007
type: workplan
title: "Ops-Warden Policy Gate Production Deployment"
domain: infotech
repo: flex-auth
status: finished
owner: codex
topic_slug: flex-auth
planning_priority: P0
planning_order: 70
depends_on_workplans:
- FLEX-WP-0006
related_workplans:
- WARDEN-WP-0009
created: "2026-06-23"
updated: "2026-06-30"
state_hub_workstream_id: "358ce697-2611-4fe9-89ab-63e86ceb00fa"
---
# FLEX-WP-0007: Ops-Warden Policy Gate Production Deployment
## Purpose
Deploy flex-auth as a reachable production runtime for ops-warden's opt-in SSH
signing policy gate, load a production registry aligned with real inventory
actors, and complete joint smoke evidence so operators can set policy.enabled:
true in warden.yaml when the ecosystem maturity stage calls for live enforcement.
Review update: repo-side production readiness is now separated from
operator-only work. flex-auth can publish the production fixture, tests,
runtime command, and sync contract in this repo. The actual stable URL
deployment and OpenBao smoke were completed through the operator tunnel and a
scoped warden-sign OpenBao lane. The final `policy.enabled` production flip is
explicitly deferred until the ecosystem reaches testing/production maturity.
## Background
ops-warden finished WARDEN-WP-0009 on the caller side: local and
production-registry smoke passed, and the production registry generator exists.
The remaining risk is operational, not policy shape: warden workstations need a
reachable flex-auth URL and a vault-backed joint smoke before the gate can be
banked for later enforcement.
Production registry artifacts:
- flex-auth fixture: examples/ops-warden/production_registry_snapshot.json
- ops-warden source artifact: ~/ops-warden/registry/flex-auth/production_registry_snapshot.json
- ops-warden generator: ~/ops-warden/scripts/build_flex_auth_registry.py
## Ownership Boundary
| Concern | Owner |
| --- | --- |
| Policy package and PDP decision | flex-auth |
| Actor inventory and TTL/principal defaults | ops-warden |
| SSH CA and OpenBao signing | ops-warden |
| Production registry content for SSH actors | Joint: ops-warden generates, flex-auth hosts |
| policy.enabled flip | ops-warden operator after flex-auth is reachable |
No SSH private keys, OpenBao tokens, or other secrets belong in fixtures, docs,
State Hub messages, or smoke evidence.
## T1 - Deploy production flex-auth runtime
```task
id: FLEX-WP-0007-T01
status: done
priority: high
state_hub_task_id: "727573fc-86a3-4f5a-abd7-40b0ccb01e68"
```
Deploy flex-auth serve, or equivalent, to a stable URL reachable from
workstations that run warden sign.
- [x] Choose preferred target: in-cluster Service at http://flex-auth.flex-auth.svc.cluster.local:8080 when reachable; otherwise approved operator tunnel or ingress with the same base path
- [x] Document canonical policy.flex_auth_url selection in docs/ops-warden-registry-sync.md
- [x] Document healthz pre-flight: GET /healthz returns HTTP 200
- [x] Add service test coverage for /healthz
- [x] Operator tunnel deployed as flex-auth-coulombcore and confirmed POST /v1/check is reachable from CoulombCore
Acceptance: operator runs curl <flex_auth_url>/healthz from the warden
workstation and receives HTTP 200. Verified from CoulombCore on 2026-06-24 with
flex_auth_url http://127.0.0.1:18090.
## T2 - Load production registry and verify real actors
```task
id: FLEX-WP-0007-T02
status: done
priority: high
state_hub_task_id: "6ec1e00c-4a3a-475b-aefb-af3961de7070"
```
Load the production registry snapshot derived from ops-warden inventory, not
only the template actors in examples/ops-warden/registry_snapshot.json.
- [x] Add examples/ops-warden/production_registry_snapshot.json from the ops-warden generated artifact
- [x] Document regenerate and load procedure in docs/ops-warden-registry-sync.md
- [x] Verify allow for agt-state-hub-bridge / sign
- [x] Verify deny for ttl_out_of_bounds
- [x] Verify deny for unregistered actors with unknown_actor_resource
- [x] Add CI tests using production actor names: agt-state-hub-bridge, agt-codex-interhub-bootstrap, adm-example, atm-backup-daily
Acceptance: local flex-auth coverage allows agt-state-hub-bridge without
ops-warden-local registry patching. Deployed runtime verification remains part
of T1.
## T3 - Publish registry sync contract with ops-warden
```task
id: FLEX-WP-0007-T03
status: done
priority: medium
state_hub_task_id: "afa09ec3-516c-433d-87a7-330cb79845a8"
```
Document the two-repo workflow when inventory or policy boundaries change.
- [x] Publish docs/ops-warden-registry-sync.md
- [x] Cover ops-warden ownership of actor names, actor types, principals, and TTL defaults
- [x] Cover flex-auth ownership of hosted registry, relationships, and policy package evaluation
- [x] Document trigger: inventory add/change -> regenerate snapshot -> flex-auth reload
- [x] Cross-link from docs/ops-warden-policy-gate-handoff.md
- [x] Confirm ops-warden wiki/PolicyGatedSigning.md already points to the flex-auth handoff; flex-auth now points back from the sync runbook
Acceptance: a new agt-* actor addition has an unambiguous procedure across both
repos.
## T4 - Joint OpenBao + policy gate production smoke
```task
id: FLEX-WP-0007-T04
status: done
priority: medium
state_hub_task_id: "32a96f1c-e0e8-4e27-baa6-7b8c445cf7a1"
```
Coordinate with ops-warden for vault-backed signing through the deployed
flex-auth runtime.
- [x] flex-auth deployed with production registry via operator tunnel, completing T1
- [x] policy.flex_auth_url validated against deployed URL http://127.0.0.1:18090 on CoulombCore; `policy.enabled` intentionally remains off until testing/production maturity
- [x] Scoped warden-sign OpenBao lane available for the smoke; no token value recorded here
- [x] Allow smoke: `warden sign agt-state-hub-bridge` recorded backend `vault` and policy_decision_id `decision:032b096c433ad80c`
- [x] Deny smoke: TTL above registry max was denied by flex-auth before OpenBao with reason `ttl_out_of_bounds`
- [x] Record non-secret evidence: decision ids, reasons, actor names only
Closed on 2026-06-30 from ops-warden non-secret smoke evidence received
2026-06-29. The operator deliberately keeps `policy.enabled` off for now because
the ecosystem is still build-stage/pre-testing; the gate is verified and banked
for later live enforcement rather than forced into premature production rigor.
Smoke runner when token is valid:
SMOKE_VAULT=1 ~/ops-warden/scripts/policy_gate_production_smoke.sh
## T5 - IAM subject binding for production
```task
id: FLEX-WP-0007-T05
status: done
priority: low
state_hub_task_id: "65dc3c59-1e4b-4335-b6a0-db492ea9b2b5"
```
Clarify how WARDEN_POLICY_SUBJECT maps to flex-auth allowed_subjects in
production.
- [x] Document production default: actor name as subject.id unless WARDEN_POLICY_SUBJECT supplies the IAM subject
- [x] Confirm production registry allowed_subjects includes iam:<actor> entries
- [x] Add test coverage for iam:agt-state-hub-bridge allow path
Acceptance: documented subject-id strategy; no ops-warden special-casing is
required beyond existing policy behavior.
## Exit Criteria
- flex-auth production runtime reachable from CoulombCore warden path: done via flex-auth-coulombcore operator tunnel
- Production registry loaded and real inventory actors covered locally: done
- Registry sync contract published and cross-linked: done
- Joint vault-backed smoke evidence recorded: done, decision:032b096c433ad80c
- ops-warden operator has the repo-side artifacts needed to set policy.enabled: true later, when maturity posture calls for live enforcement
## Implementation Notes
2026-06-23 repo-side implementation:
- Added examples/ops-warden/production_registry_snapshot.json from the ops-warden generated production registry artifact.
- Added Go coverage for production actor allows, IAM subject allow, ttl_out_of_bounds, unknown_actor_resource, production registry counts, and /healthz.
- Published docs/ops-warden-registry-sync.md and cross-linked it from the handoff and examples docs.
Closeout note:
- The OpenBao-backed smoke passed through ops-warden with the scoped warden-sign lane.
- The `policy.enabled` flip is intentionally deferred by operator/maturity decision, not treated as an open repo-side blocker.
- After workplan file changes, run make fix-consistency REPO=flex-auth from ~/state-hub to mirror these statuses into State Hub.
## See Also
- docs/ops-warden-policy-gate-handoff.md
- docs/ops-warden-registry-sync.md
- workplans/FLEX-WP-0006-ops-warden-ssh-signing-policy-gate.md
- ~/ops-warden/wiki/PolicyGatedSigning.md
- ~/ops-warden/workplans/WARDEN-WP-0009-flex-auth-policy-gate-production.md
- ~/ops-warden/history/2026-06-23-flex-auth-production-pickup-suggestion.md
2026-06-24 operator tunnel update:
- Built /tmp/flex-auth and started the production registry runtime on local 127.0.0.1:18090.
- Added local ops-bridge tunnel flex-auth-coulombcore, forwarding CoulombCore 127.0.0.1:18090 to the local runtime.
- Verified remote health from CoulombCore: GET /healthz returned HTTP 200.
- Verified remote POST /v1/check from CoulombCore allowed agt-state-hub-bridge with decision:873c6c682a52bebc.
- VAULT_TOKEN is absent, so OpenBao-backed smoke remains blocked on operator credential refresh.
2026-06-30 closeout from ops-warden smoke handoff:
- Mode: `FLEX_AUTH_EXTERNAL` against deployed runtime `127.0.0.1:18090` via the CoulombCore operator path.
- Allow: `warden sign agt-state-hub-bridge` returned policy_decision_id `decision:032b096c433ad80c`.
- Deny: `--ttl 999` was rejected with `ttl_out_of_bounds` before OpenBao signing.
- Vault-backed allow: backend `vault` produced the same policy_decision_id through the scoped warden-sign OpenBao lane.
- Operator decision: keep `policy.enabled` off during build-stage/pre-testing and flip it later when the ecosystem reaches the appropriate maturity posture.