88 lines
4.3 KiB
Markdown
88 lines
4.3 KiB
Markdown
|
|
# Native caller authentication
|
||
|
|
|
||
|
|
KeyCape owns the `keycape service-token` and `keycape login` commands. They
|
||
|
|
produce issuer-verified JWTs in a new mode-0600 JSON file outside Git worktrees.
|
||
|
|
Existing files and symlinks are refused. Token responses and provider error
|
||
|
|
bodies are never printed. Commands exit nonzero on verification or delivery
|
||
|
|
failure and remove incomplete output files.
|
||
|
|
|
||
|
|
## Service exchange
|
||
|
|
|
||
|
|
Have the existing custody mechanism inject the registered client secret into a
|
||
|
|
named environment variable, then run:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
keycape service-token --issuer https://kc.coulomb.social \
|
||
|
|
--client-id rapp-qonto --scope qonto:read \
|
||
|
|
--secret-env KEYCAPE_RAPP_QONTO_CLIENT_SECRET \
|
||
|
|
--out /private/runtime/qonto-token.json
|
||
|
|
```
|
||
|
|
|
||
|
|
The output directory must already exist. No secret belongs on argv. The command
|
||
|
|
uses client_secret_basic with OAuth form encoding. The server must include the
|
||
|
|
matching decoding fix for secrets containing reserved characters. The Qonto
|
||
|
|
client ID and scope above follow the deployed KEY-WP-0006 registration.
|
||
|
|
|
||
|
|
For approval-engine clients, set `--audience approval-engine` explicitly; the
|
||
|
|
expected audience otherwise defaults to the client ID. Both requested scopes
|
||
|
|
and audience must match the issuer's static registration. The output contains
|
||
|
|
`access_token`, `token_type`, and `expires_in`; a service exchange has no ID token.
|
||
|
|
Re-exchange before expiry for renewal. There is no refresh token or credential
|
||
|
|
cache. Client disablement stops new issuance; already-issued JWTs expire normally.
|
||
|
|
|
||
|
|
## Browser login
|
||
|
|
|
||
|
|
Use a registered **public PKCE** client with an exact literal-loopback callback:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
keycape login --issuer https://kc.coulomb.social \
|
||
|
|
--client-id APPROVER_CLIENT_ID --audience approval-engine \
|
||
|
|
--scope 'openid approval:approve' \
|
||
|
|
--redirect-uri http://127.0.0.1:REGISTERED_PORT/callback \
|
||
|
|
--out /private/runtime/approver-token.json
|
||
|
|
```
|
||
|
|
|
||
|
|
Replace the client ID and port with the approved registration. This command
|
||
|
|
prints a browser authorization URL and waits up to five minutes. The provider
|
||
|
|
handles password and MFA; KeyCape CLI never receives those credentials. Configure
|
||
|
|
`mfaRequired: true` on the approver registration. State, nonce and S256 PKCE are
|
||
|
|
fresh for each attempt. The listener binds before the URL is printed, validates
|
||
|
|
callback host/path/state, and closes when the attempt ends.
|
||
|
|
|
||
|
|
Both tokens are verified against same-origin HTTPS discovery/JWKS. Access tokens
|
||
|
|
bind the expected resource audience and requested scopes. ID tokens bind the
|
||
|
|
client ID, nonce and matching subject. There is no insecure TLS switch and no
|
||
|
|
HTTP redirect following during credential exchange. Cross-origin discovery
|
||
|
|
endpoints and confidential browser clients are intentionally unsupported.
|
||
|
|
|
||
|
|
## OpenBao login boundary
|
||
|
|
|
||
|
|
The existing ops-warden login route runs
|
||
|
|
`bao login -no-print -method=oidc -path=netkingdom role=<domain>`. That produces an
|
||
|
|
**OpenBao token**, unlike KeyCape's new issuer JWT output. These commands are not
|
||
|
|
interchangeable. KeyCape owns browser authentication and issuer JWTs; OpenBao's
|
||
|
|
role mapping, token store and enforcement remain platform-owned. Do not replace
|
||
|
|
that route with `keycape login` without adapting and verifying its consumer
|
||
|
|
contract. No ops-warden route is retired by this change.
|
||
|
|
|
||
|
|
## Qonto rotation boundary
|
||
|
|
|
||
|
|
Native exchange is implemented. Rotation still needs a coordinated custody and
|
||
|
|
provider update, not a wrapper around a raw KV read. The reviewed sequence is:
|
||
|
|
|
||
|
|
1. Resolve exact existing authority for
|
||
|
|
`platform/workloads/rapp-qonto/keycape-client`, field `client_secret`, and
|
||
|
|
`sso/keycape-rapp-qonto-client`, key `client-secret`.
|
||
|
|
2. Generate the successor inside the approved custody execution transport;
|
||
|
|
retain the predecessor only there for bounded rollback and denial checks.
|
||
|
|
3. CAS-update that KV field while preserving siblings, update the matching
|
||
|
|
Kubernetes delivery reference, and restart KeyCape in the agreed window.
|
||
|
|
4. Verify the new `qonto:read` exchange, predecessor rejection and excess-scope
|
||
|
|
denial without exposing tokens; verify downstream readiness.
|
||
|
|
5. On failure, reconcile both custodians to the same version and reload KeyCape
|
||
|
|
before declaring rollback complete. Record versions and outcomes, never values.
|
||
|
|
|
||
|
|
No general rotation command is shipped until that cross-owner transaction has
|
||
|
|
an admitted execution and rollback contract. Calling secrets-engine's KV
|
||
|
|
rotation alone would leave the provider and consumers inconsistent.
|