FLEX-WP-0037: register ops-warden actor agt-custodian in the production registry
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 57s

Add agt-custodian (WARDEN-WP-0045) to examples/ops-warden/production_registry_snapshot.json
so flex-auth-ops-warden stops refusing it with unknown_actor_resource (CUST-WP-0073-T02).
Additions only; regenerated-inventory drift (agt-rehearsal, adm/codex zone fields) not taken.
Tests cover own-principal proceed and foreign-principal disallowed_principal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 605711@bnt-lap001
Assistant-Session: 1f5e2256-181f-49ed-b7b1-a1c34bf37a61
This commit is contained in:
tegwick 2026-10-20 22:58:36 +02:00
parent a310f3fe35
commit 9132f9b677
5 changed files with 233 additions and 1 deletions

View file

@ -46,6 +46,7 @@
| workplan | FLEX-WP-0034 | finished | — | workplans/FLEX-WP-0034-informed-decision-review-discovery.md |
| workplan | FLEX-WP-0035 | finished | — | workplans/FLEX-WP-0035-per-identity-application-readback.md |
| workplan | FLEX-WP-0036 | active | — | workplans/FLEX-WP-0036-runtime-rename-access-engine.md |
| workplan | FLEX-WP-0037 | active | — | workplans/FLEX-WP-0037-ops-warden-agt-custodian-actor.md |
| task | FLEX-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
| task | FLEX-WP-ADHOC-2026-10-03-T02 | wait | — | workplans/ADHOC-2026-10-03.md |
| task | FLEX-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md |
@ -224,6 +225,9 @@
| task | FLEX-WP-0036-T06 | wait | — | workplans/FLEX-WP-0036-runtime-rename-access-engine.md |
| task | FLEX-WP-0036-T07 | todo | — | workplans/FLEX-WP-0036-runtime-rename-access-engine.md |
| task | FLEX-WP-0036-T08 | todo | — | workplans/FLEX-WP-0036-runtime-rename-access-engine.md |
| task | FLEX-WP-0037-T01 | done | — | workplans/FLEX-WP-0037-ops-warden-agt-custodian-actor.md |
| task | FLEX-WP-0037-T02 | progress | — | workplans/FLEX-WP-0037-ops-warden-agt-custodian-actor.md |
| task | FLEX-WP-0037-T03 | wait | — | workplans/FLEX-WP-0037-ops-warden-agt-custodian-actor.md |
| intake | FLEX-IN-0001 | closed | — | intakes/intakes.md |
| intake | FLEX-IN-0002 | closed | — | intakes/intakes.md |
| intake | FLEX-IN-0003 | open | — | intakes/intakes.md |

View file

@ -285,7 +285,7 @@ func TestRunLoadRegistryOpsWardenProduction(t *testing.T) {
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) {
if result["subjects"] != float64(5) || result["relationships"] != float64(5) || result["resource_manifests"] != float64(1) {
t.Fatalf("load-registry result = %+v; want production actor registry counts", result)
}
}
@ -336,6 +336,36 @@ func TestOpsWardenProductionRegistryActors(t *testing.T) {
wantEffect: api.DecisionEffectAuditOnly,
wantReason: "advisory_would_signing_policy_matched",
},
{
name: "custodian agent proceeds with its own principal while zone is unknown",
subjectID: "agt-custodian",
actor: "agt-custodian",
actorType: "agt",
principal: "agt-custodian",
ttlHours: 12,
wantEffect: api.DecisionEffectAuditOnly,
wantReason: "advisory_would_signing_policy_matched",
},
{
name: "custodian agent IAM subject proceeds while zone is unknown",
subjectID: "iam:agt-custodian",
actor: "agt-custodian",
actorType: "agt",
principal: "agt-custodian",
ttlHours: 1,
wantEffect: api.DecisionEffectAuditOnly,
wantReason: "advisory_would_signing_policy_matched",
},
{
name: "custodian agent unknown principal is not matched",
subjectID: "agt-custodian",
actor: "agt-custodian",
actorType: "agt",
principal: "adm-full",
ttlHours: 1,
wantEffect: api.DecisionEffectAuditOnly,
wantReason: "advisory_would_disallowed_principal",
},
{
name: "admin actor allow",
subjectID: "adm-example",

View file

@ -91,6 +91,7 @@ It registers:
| --- | --- | --- | --- | --- |
| 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-custodian | agt | agt-custodian | 12 | agt-custodian, iam:agt-custodian (added 2026-10-20, FLEX-WP-0037) |
| 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 |

View file

@ -108,6 +108,31 @@
"security_zone_reason": "workload_resolution_absent"
}
},
{
"id": "ssh-cert:actor/agt-custodian",
"type": "ssh-certificate",
"labels": [
"ssh-signing",
"agt"
],
"owner": "team:platform-security",
"attributes": {
"actor_id": "agt-custodian",
"actor_type": "agt",
"allowed_subjects": [
"agt-custodian",
"iam:agt-custodian"
],
"allowed_principals": [
"agt-custodian"
],
"max_ttl_hours": 12,
"workload_id": "custodian-agents",
"security_zone": "unknown",
"security_zone_admission": "unknown",
"security_zone_reason": "workload_resolution_absent"
}
},
{
"id": "ssh-cert:actor/agt-state-hub-bridge",
"type": "ssh-certificate",
@ -208,6 +233,22 @@
"actor_type": "agt"
}
},
{
"id": "agt-custodian",
"type": "Agent",
"display_name": "Supervised interactive custodian agent (custodian-agt on Railiance01)",
"organization_relation": "ServiceProvider",
"roles": [
"Operator"
],
"groups": [
"group:ops-warden-agents"
],
"tenant": "tenant:platform",
"metadata": {
"actor_type": "agt"
}
},
{
"id": "agt-state-hub-bridge",
"type": "Agent",
@ -255,6 +296,7 @@
"display_name": "Ops Warden Agents",
"members": [
"agt-codex-interhub-bootstrap",
"agt-custodian",
"agt-state-hub-bridge"
],
"tenant": "tenant:platform"
@ -363,6 +405,53 @@
"access_path": "mediated"
}
},
{
"id": "rel:agt-custodian-sign-agt-custodian",
"system": "ops-warden",
"subject": "group:ops-warden-agents",
"relation": "signer",
"object": "ssh-cert:actor/agt-custodian",
"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-custodian",
"tenant": "tenant:platform",
"resource": "ssh-cert:actor/agt-custodian"
},
"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",

View file

@ -0,0 +1,108 @@
---
id: FLEX-WP-0037
type: workplan
title: "Register ops-warden actor agt-custodian in the signing-policy registry and roll the flex-auth-ops-warden pin"
domain: infotech
repo: access-engine
status: active
owner: claude
topic_slug: netkingdom
created: "2026-10-20"
updated: "2026-10-20"
origin: demand
origin_ref: CUST-WP-0073-T02 (warden sign agt-custodian refused with unknown_actor_resource)
related: [CUST-WP-0073, WARDEN-WP-0045, FLEX-WP-0016, FLEX-WP-0007]
---
ops-warden `WARDEN-WP-0045` (commits `e62763c`, `a07c6c7`) added the actor
`agt-custodian` (type `agt`, principal `agt-custodian`, default and max TTL
12h, extension `permit-pty`, zone subject workload `custodian-agents`) for the
supervised interactive custodian agent of `CUST-WP-0073-T02`. The in-cluster
deployment `flex-auth/flex-auth-ops-warden` serves the registry snapshot baked
into its image (`/opt/flex-auth/examples/ops-warden/production_registry_snapshot.json`),
which does not know the actor, so `warden sign agt-custodian` is refused with
`unknown_actor_resource`.
## Add agt-custodian to the production fixture
```task
id: FLEX-WP-0037-T01
status: done
priority: high
```
Regenerated the snapshot with ops-warden `scripts/build_flex_auth_registry.py`
from the live inventory and took **only** the `agt-custodian` elements
(resource `ssh-cert:actor/agt-custodian`, subject `agt-custodian`, membership
in `group:ops-warden-agents`, relationship
`rel:agt-custodian-sign-agt-custodian`). The diff to
`examples/ops-warden/production_registry_snapshot.json` is additions only.
Drift deliberately not taken (needs its own owner decision):
- live inventory also carries `agt-rehearsal` (principal
`rehearsal-inert-nohost`, 2h), which is not in ops-warden's committed
snapshot either;
- the generator now emits `security_zone_admission: unknown` /
`catalog_applicability_absent` for `adm-example` (fixture: `not-applicable`,
human operator) and drops `workload_id: codex-interhub-bootstrap` from
`agt-codex-interhub-bootstrap`.
Validation: `access-engine load-registry` loads 5 subjects / 5 relationships;
`go test ./...` passes. `TestOpsWardenProductionRegistryActors` gains
agt-custodian cases: own principal (actor and `iam:` subject) proceeds with
`advisory_would_signing_policy_matched`; a foreign principal yields
`advisory_would_disallowed_principal`. Both are `audit_only` because the
actor's security zone resolves `unknown` (same advisory phase as every other
agt actor in this fixture).
## Build the image and pin flex-auth-ops-warden
```task
id: FLEX-WP-0037-T02
status: progress
priority: high
```
Pushing `examples/**` to main triggers `.forgejo/workflows/image.yaml`, which
publishes `forgejo.coulomb.social/coulomb/flex-auth:main-<short-sha>`. Take the
digest from the "Report immutable digest" step and set it in
`values/ops-warden.yaml` only (the fetch pin, `values/ops-warden-fetch.yaml`,
serves a different registry and stays where it is). Rollback target is the
current pin `sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b`.
flex-auth has no Argo Application; the pin is applied by an attended Helm
upgrade, so a pushed pin does not deploy by itself.
## Attended rollout and smoke
```task
id: FLEX-WP-0037-T03
status: wait
priority: high
needs_human: true
blocking_reason: "Attended helm upgrade of flex-auth-ops-warden on railiance01 plus an allow smoke; agents may not change the live cluster."
```
```bash
helm upgrade --install flex-auth-ops-warden charts/flex-auth \
--namespace flex-auth -f values/ops-warden.yaml --wait --timeout 2m
kubectl -n flex-auth get deploy flex-auth-ops-warden \
-o jsonpath='{.spec.template.spec.containers[0].image}'
```
Smoke through the `127.0.0.1:19090` tunnel (caller auth is `enforce`, so the
request needs the ops-warden ServiceAccount bearer; the simplest real smoke is
`warden sign agt-custodian` itself):
```bash
curl -s -X POST http://127.0.0.1:19090/v1/check \
-H 'Content-Type: application/json' -H "Authorization: Bearer $OPS_WARDEN_SA_TOKEN" \
-d '{"id":"check:smoke-agt-custodian","tenant":"tenant:platform",
"subject":{"id":"agt-custodian","type":"agt"},"action":"sign",
"resource":{"id":"ssh-cert:actor/agt-custodian","type":"ssh-certificate","system":"ops-warden"},
"context":{"principals":["agt-custodian"],"actor_type":"agt","ttl_hours":1,
"pubkey_fingerprint":"SHA256:smoke"}}'
```
Expected: `effect` `audit_only` (or `allow` once the zone resolves), reason
`advisory_would_signing_policy_matched` (not `unknown_actor_resource`).