2026-10-10 13:18:24 +02:00
|
|
|
# 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.
|
2026-10-11 15:33:14 +02:00
|
|
|
# Verify that client_credentials for each probe returns HTTP 401 invalid_profile_usage with feature client_id.
|
2026-10-10 13:18:24 +02:00
|
|
|
# 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
|