generated from coulomb/repo-seed
Compare commits
13 Commits
2230163de1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 339c35e876 | |||
| 8124367e1d | |||
| b4520bd731 | |||
| 941501c590 | |||
| fae0f00a69 | |||
| 77bcd55ddb | |||
| f0d1afa237 | |||
| 0fde95a87c | |||
| 53e0d055c9 | |||
| e1c141234a | |||
| 8a913d6163 | |||
| 1be449dae8 | |||
| 1b899cd41c |
@@ -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}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
25
.repo-classification.yaml
Normal 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.
|
||||
23
AGENTS.md
23
AGENTS.md
@@ -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
|
||||
|
||||
29
INTENT.md
29
INTENT.md
@@ -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
|
||||
|
||||
|
||||
48
SCOPE.md
48
SCOPE.md
@@ -72,15 +72,22 @@ 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. 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, and audit internals.
|
||||
`FLEX-WP-0001`, `FLEX-WP-0005` (foundations and Topaz alignment), and
|
||||
`FLEX-WP-0006` (the ops-warden SSH signing policy gate) are complete.
|
||||
|
||||
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-0007` deploys flex-auth as a reachable
|
||||
production runtime for that gate; it is `blocked` only on T4 — the joint
|
||||
OpenBao-backed smoke awaiting a refreshed scoped `VAULT_TOKEN` — with all
|
||||
repo-side artifacts already published. Markitect consumer integration
|
||||
(`FLEX-WP-0003`) and delegated PDP/directory adapters (`FLEX-WP-0004`)
|
||||
remain planned on top of the stable core contracts.
|
||||
|
||||
State Hub integration is present through:
|
||||
|
||||
@@ -127,7 +134,15 @@ 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 planned **knowledge-pipeline** consumer and policy
|
||||
enforcement point (`FLEX-WP-0003`).
|
||||
- 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 +158,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.
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
114
docs/ops-warden-policy-gate-handoff.md
Normal file
114
docs/ops-warden-policy-gate-handoff.md
Normal 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.
|
||||
128
docs/ops-warden-registry-sync.md
Normal file
128
docs/ops-warden-registry-sync.md
Normal 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
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
49
examples/ops-warden/README.md
Normal file
49
examples/ops-warden/README.md
Normal 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.
|
||||
23
examples/ops-warden/check_request_allow_adm.json
Normal file
23
examples/ops-warden/check_request_allow_adm.json
Normal 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"
|
||||
}
|
||||
}
|
||||
22
examples/ops-warden/check_request_allow_agt.json
Normal file
22
examples/ops-warden/check_request_allow_agt.json
Normal 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"
|
||||
}
|
||||
}
|
||||
22
examples/ops-warden/check_request_allow_atm.json
Normal file
22
examples/ops-warden/check_request_allow_atm.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
22
examples/ops-warden/check_request_deny_ttl_above_max.json
Normal file
22
examples/ops-warden/check_request_deny_ttl_above_max.json
Normal 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"
|
||||
}
|
||||
}
|
||||
22
examples/ops-warden/check_request_deny_unknown_subject.json
Normal file
22
examples/ops-warden/check_request_deny_unknown_subject.json
Normal 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"
|
||||
}
|
||||
}
|
||||
337
examples/ops-warden/policy_fixtures.yaml
Normal file
337
examples/ops-warden/policy_fixtures.yaml
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
257
examples/ops-warden/policy_package.md
Normal file
257
examples/ops-warden/policy_package.md
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
450
examples/ops-warden/production_registry_snapshot.json
Normal file
450
examples/ops-warden/production_registry_snapshot.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
36
examples/ops-warden/protected_system_manifest.yaml
Normal file
36
examples/ops-warden/protected_system_manifest.yaml
Normal 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
|
||||
366
examples/ops-warden/registry_snapshot.json
Normal file
366
examples/ops-warden/registry_snapshot.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
59
examples/ops-warden/resource_manifest.yaml
Normal file
59
examples/ops-warden/resource_manifest.yaml
Normal 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
|
||||
54
examples/ops-warden/subject_manifest.yaml
Normal file
54
examples/ops-warden/subject_manifest.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
230
workplans/FLEX-WP-0006-ops-warden-ssh-signing-policy-gate.md
Normal file
230
workplans/FLEX-WP-0006-ops-warden-ssh-signing-policy-gate.md
Normal 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`.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user