T01 done: railiance-platform agreed the case list. The packet adds an otherwise-valid expiry client and pins audience on the claim probes so each token fails only the claim under test. The script prints one refused boolean and refusal class per case, revokes any accepted token, and never counts a transport failure as a refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 779409@bnt-lap001 Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3
97 lines
4.1 KiB
YAML
97 lines
4.1 KiB
YAML
# Non-secret request packet for KEY-WP-0039: temporary probe registrations whose
|
|
# tokens the OpenBao role secrets-engine-openbao (auth/keycape-services) must
|
|
# refuse, for CCR-2026-0038 (railiance-platform RPF-WP-0035-T02).
|
|
#
|
|
# This is not executable authorization. Each probe is a copy of the live
|
|
# secrets-engine-openbao registration (config/service-clients.example.yaml) with
|
|
# exactly one property changed. `audience` is pinned to secrets-engine-openbao
|
|
# wherever aud is not the property under test: a service token's aud is the
|
|
# clientId unless audience is set (token.go, accessAudience), so without the pin
|
|
# every probe would also fail on aud and prove nothing about its own claim.
|
|
#
|
|
# The secrets-engine-openbao registration itself is not changed.
|
|
reference:
|
|
clientId: secrets-engine-openbao
|
|
aud: secrets-engine-openbao
|
|
sub: service:secrets-engine
|
|
tenant: tenant:platform
|
|
roles: [secrets-engine]
|
|
scope: openbao:login
|
|
principal_type: service
|
|
|
|
common:
|
|
grantTypes: ["client_credentials"]
|
|
clientType: confidential
|
|
tokenLifetime: "1m" # the minimum KeyCape accepts
|
|
# Each secretRef names an env var that exists only for the attended session.
|
|
# railiance-platform's condition (a), 2026-10-10: no OpenBao custody.
|
|
secretRefPattern: "env:KEYCAPE_<CLIENT_ID>_CLIENT_SECRET"
|
|
|
|
probes:
|
|
- case: aud
|
|
clientId: openbao-probe-aud
|
|
differs: {aud: openbao-probe-aud} # no audience pin
|
|
expect: refuse
|
|
expected_class: claim_mismatch
|
|
- case: sub
|
|
clientId: openbao-probe-sub
|
|
audience: secrets-engine-openbao
|
|
differs: {serviceSubject: "service:openbao-probe"}
|
|
expect: refuse
|
|
expected_class: claim_mismatch
|
|
- case: tenant
|
|
clientId: openbao-probe-tenant
|
|
audience: secrets-engine-openbao
|
|
differs: {tenant: "tenant:openbao-probe"}
|
|
expect: refuse
|
|
expected_class: claim_mismatch
|
|
- case: roles
|
|
clientId: openbao-probe-roles
|
|
audience: secrets-engine-openbao
|
|
differs: {roles: ["openbao-probe"]}
|
|
expect: refuse
|
|
expected_class: claim_mismatch
|
|
- case: scope
|
|
clientId: openbao-probe-scope
|
|
audience: secrets-engine-openbao
|
|
differs: {allowedScopes: ["openbao:probe"]}
|
|
expect: refuse
|
|
expected_class: claim_mismatch
|
|
- case: expired
|
|
clientId: openbao-probe-expiry
|
|
audience: secrets-engine-openbao
|
|
# Identical to the reference in every claim, so expiry is the only defect.
|
|
# This makes it a second credential that can log in as secrets-engine until
|
|
# it is removed. That exposure is bounded by the session and is the reason the
|
|
# probe clients go in the same session. A token from one of the other probes
|
|
# cannot stand in: it already fails a claim, so its refusal would not show
|
|
# that expiry was checked.
|
|
differs: {}
|
|
present_after: "exp + OpenBao expiration_leeway (default 150s) + 10s"
|
|
expect: refuse
|
|
expected_class: expired
|
|
|
|
existing_tokens:
|
|
# These differ from the reference in several claims, so they prove refusal but
|
|
# not which bound claim refused. The receipt records whatever class OpenBao reports.
|
|
- case: principal_type
|
|
source: a human access token from an attended `keycape login`
|
|
expect: refuse
|
|
- case: coding_agent
|
|
source: codex-railiance-platform (role coding-agent), if registered live
|
|
expect: refuse
|
|
|
|
session:
|
|
# One attended operator session, run by the owner of the live KeyCape client
|
|
# registry (net-kingdom). Order:
|
|
# 1. Generate the six probe secrets in the session. Write them to a transient
|
|
# Kubernetes Secret bound to Deployment sso/keycape, never to OpenBao.
|
|
# 2. Add the probe registrations; KeyCape restarts.
|
|
# 3. Run scripts/openbao_negative_probe.py with the secrets on stdin.
|
|
# 4. Remove the registrations and the transient Secret; KeyCape restarts.
|
|
# Verify that client_credentials for each probe returns invalid_client.
|
|
# Each restart drops in-flight logins, so pick a quiet window.
|
|
# The transient Secret is the only place the values exist outside process
|
|
# memory. KeyCape resolves secretRef from its environment at startup, so a
|
|
# copy that lives only in memory cannot reach it.
|
|
receipt: docs/evidence/<date>-openbao-negative-probe.json
|