Finish coding-agent high-risk boundary coverage
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
7a1dcb8a52
commit
429cc912ed
12 changed files with 720 additions and 54 deletions
117
docs/coding-agent-openbao-identity.md
Normal file
117
docs/coding-agent-openbao-identity.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# Coding-agent OpenBao identity
|
||||
|
||||
`RAILIANCE-WP-0022` requires an OpenBao identity that is distinct from the
|
||||
attended human `platform-admin` role. The identity is split across its proper
|
||||
owners:
|
||||
|
||||
- KeyCape owns a confidential `client_credentials` service principal and its
|
||||
signed short-lived JWT.
|
||||
- railiance-platform owns the exact-bound OpenBao JWT role and the policies on
|
||||
the resulting OpenBao token.
|
||||
- ops-warden remains an interested consumer and SSH-certificate issuer; it does
|
||||
not issue this identity or its credentials.
|
||||
|
||||
The operational identity is the platform-owned AppRole
|
||||
`coding-agent-railiance-platform`. The exact-bound KeyCape JWT role is the
|
||||
issuer-backed migration target; it does not block the deny-wins control while
|
||||
the KeyCape client is being registered.
|
||||
|
||||
## Operational AppRole
|
||||
|
||||
The reviewed role body is `openbao/auth/coding-agent-approle.json`. Apply it
|
||||
with attended platform authority:
|
||||
|
||||
```bash
|
||||
bao write auth/approle/role/coding-agent-railiance-platform \
|
||||
@openbao/auth/coding-agent-approle.json
|
||||
```
|
||||
|
||||
This is a standing machine role, not a manually minted token. Every SecretID
|
||||
expires after five minutes and is single-use; resulting tokens last at most 15
|
||||
minutes, have eight uses, omit the default policy, and always carry
|
||||
`agent-high-risk-boundary`. The role also carries the otherwise-readable
|
||||
issue-core policy solely to make the deny-wins invariant directly testable.
|
||||
The boundary permits token metadata lookup, capabilities inspection, and
|
||||
self-revocation; it does not grant token creation or management of any other
|
||||
token.
|
||||
|
||||
Run the non-disclosing verifier while holding attended authority to create one
|
||||
ephemeral SecretID:
|
||||
|
||||
```bash
|
||||
python3 scripts/verify_coding_agent_approle.py
|
||||
```
|
||||
|
||||
The verifier holds the SecretID and token only in memory, calls
|
||||
`sys/capabilities-self` rather than a KV read, and revokes the test token before
|
||||
exit. It prints only policy names, TTL, capabilities, and pass/fail state.
|
||||
|
||||
## KeyCape contract
|
||||
|
||||
The accepted service token must contain exactly the following identity
|
||||
coordinates:
|
||||
|
||||
| Claim | Required value |
|
||||
| --- | --- |
|
||||
| `aud` / client id | `codex-railiance-platform` |
|
||||
| `sub` | `service:codex:railiance-platform` |
|
||||
| `principal_type` | `service` |
|
||||
| `tenant` | `tenant:coulomb` |
|
||||
| `roles` | contains `coding-agent` |
|
||||
| requested scope | `openbao:login` |
|
||||
|
||||
The KeyCape access token lifetime is 15 minutes. Its confidential client secret
|
||||
must be generated and delivered through an approved out-of-repository custody
|
||||
path. It must never appear in Git, State Hub, command arguments, logs, or chat.
|
||||
|
||||
## KeyCape-backed OpenBao role
|
||||
|
||||
The migration-target role body is
|
||||
`openbao/auth/coding-agent-jwt-role.json`. Apply it only with attended platform
|
||||
authority:
|
||||
|
||||
```bash
|
||||
bao write auth/netkingdom/role/coding-agent \
|
||||
@openbao/auth/coding-agent-jwt-role.json
|
||||
```
|
||||
|
||||
The role binds the exact service audience, subject, principal type, tenant, and
|
||||
role. It produces a 15-minute, eight-use token without the default policy. The
|
||||
token deliberately combines `agent-high-risk-boundary` with
|
||||
`workload-kv-read-issue-core-runtime`: the latter would otherwise read the
|
||||
issue-core bundle, while the former must win with `deny`.
|
||||
|
||||
## Value-safe verification
|
||||
|
||||
Do not verify by attempting `bao kv get`; an unexpected policy error could
|
||||
print a protected value. After KeyCape exchanges the client credential and the
|
||||
JWT login produces an OpenBao token through a mode-0600 temporary file, use only
|
||||
capabilities and token metadata:
|
||||
|
||||
```bash
|
||||
BAO_TOKEN_FILE=/run/user/$(id -u)/coding-agent.openbao-token
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") \
|
||||
bao token capabilities \
|
||||
platform/data/workloads/issue-core/issue-core/issue-core-runtime
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") \
|
||||
bao token capabilities \
|
||||
platform/metadata/workloads/issue-core/issue-core/issue-core-runtime
|
||||
BAO_TOKEN=$(head -n 1 "$BAO_TOKEN_FILE") bao token lookup -format=json \
|
||||
| jq '{display_name:.data.display_name,policies:.data.policies,ttl:.data.ttl}'
|
||||
```
|
||||
|
||||
Expected results are `deny` for the data path and `read` for the metadata path.
|
||||
The lookup must list both policies, omit `default`, and report a TTL no greater
|
||||
than 15 minutes. Shred the temporary JWT and OpenBao token files after the
|
||||
check. Revoke the issued OpenBao token if it remains live.
|
||||
|
||||
## Abort and rollback
|
||||
|
||||
- Abort if any bound claim differs, the JWT issuer/audience is not the live
|
||||
KeyCape contract, or a token would be printed.
|
||||
- Delete `auth/netkingdom/role/coding-agent` if deny-wins does not appear in
|
||||
capabilities or the token includes an unexpected policy.
|
||||
- Delete `auth/approle/role/coding-agent-railiance-platform` if its metadata
|
||||
differs from the reviewed body or its verifier fails.
|
||||
- Revoke the test OpenBao token, disable the KeyCape client, and rotate its
|
||||
confidential secret after suspected disclosure.
|
||||
|
|
@ -22,6 +22,23 @@ modified.
|
|||
catalog entries share the Binky IMAP path and Core Hub is an additional
|
||||
reviewed deny without a catalog lane.
|
||||
|
||||
### 2026-08-22 generated-input reconciliation
|
||||
|
||||
- Ops-warden delivered generated artifact revision
|
||||
`55f0f47a021375b8b25c924953d1b49a24e002c5`: 19 high-risk lanes, 14
|
||||
concrete entries (13 unique paths), and five pattern/non-KV lanes.
|
||||
- The corrected grades add issue-core runtime (whose bundle also contains
|
||||
`GITEA_BACKEND_TOKEN`) and reuse-surface runtime (whose bundle also contains
|
||||
`REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET`). Both data paths now have explicit
|
||||
deny blocks and read-only metadata blocks.
|
||||
- `scripts/agent_high_risk_boundary.py` verified that the vendored input equals
|
||||
the clean upstream artifact aside from generation time and that every entry
|
||||
is covered. Forty-nine focused tests passed.
|
||||
- Under attended `platform-admin` OIDC, the policy was uploaded and read back.
|
||||
OpenBao added only its normal trailing newline. The server-backed ops-warden
|
||||
audit reported 19 high-risk lanes, 14 covered, zero uncovered, and five with
|
||||
no concrete address.
|
||||
|
||||
## Attachment audit and residual blocker
|
||||
|
||||
A metadata-only scan listed and read role configuration under netkingdom OIDC,
|
||||
|
|
@ -30,14 +47,33 @@ Kubernetes auth, AppRole, and token roles. It found:
|
|||
- roles attaching `agent-high-risk-boundary`: **0**;
|
||||
- roles combining it with any `workload-kv-read-*` policy: **0**.
|
||||
|
||||
The live policy is therefore complete but is not automatically attached to a
|
||||
coding-agent identity. The documented manual short-lived token example is not
|
||||
a standing identity and carries no workload-read policy. Attaching the boundary
|
||||
to `platform-admin` would incorrectly constrain the attended operator role and
|
||||
erase the human/agent distinction, so that change was not made.
|
||||
The 2026-08-21 scan accurately found no attachment at that time. On 2026-08-22,
|
||||
State Hub decision `f0955252-7b20-4c80-86e9-f8080ec60793` selected the
|
||||
platform-owned `coding-agent-railiance-platform` AppRole as the operational
|
||||
machine identity while retaining the KeyCape JWT role as an issuer-backed
|
||||
migration target.
|
||||
|
||||
The remaining work is an identity-owner decision: define a distinct coding-
|
||||
agent issuance path, attach the boundary, and prove that deny wins when a
|
||||
workload read policy is also present. A versioned generated list of concrete
|
||||
high-risk deny paths is also requested from ops-warden so policy coverage does
|
||||
not depend on manual catalog transcription.
|
||||
Live AppRole metadata matched the reviewed source:
|
||||
|
||||
- `bind_secret_id=true`, `secret_id_ttl=300`, and
|
||||
`secret_id_num_uses=1`;
|
||||
- `token_ttl=900`, `token_max_ttl=900`, `token_num_uses=8`, and no default
|
||||
policy;
|
||||
- policies exactly `agent-high-risk-boundary` and
|
||||
`workload-kv-read-issue-core-runtime`.
|
||||
|
||||
The non-disclosing verifier minted one ephemeral SecretID, logged in, and used
|
||||
only token metadata plus `sys/capabilities-self`. The data path resolved to
|
||||
`deny`; its metadata path resolved to `read`; the token TTL was 900 seconds.
|
||||
The first verification exposed that a no-default-policy token could not revoke
|
||||
itself. That bounded test token was located and revoked by accessor under
|
||||
attended authority, `auth/token/revoke-self:update` was added to the boundary,
|
||||
and the repeat verification passed and self-revoked without warning. No KV read
|
||||
was attempted and no SecretID, token, or protected value was printed or logged.
|
||||
|
||||
The exact-bound JWT role `auth/netkingdom/role/coding-agent` was also applied
|
||||
with audience `codex-railiance-platform`, subject
|
||||
`service:codex:railiance-platform`, `principal_type=service`,
|
||||
`tenant=tenant:coulomb`, and role `coding-agent`. KeyCape client registration
|
||||
is a future identity-hardening handoff; it is not required for the live AppRole
|
||||
attachment proven here.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue