Add opt-in issuer-owned access-token status and scoped revocation
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
This commit is contained in:
parent
d803e9282a
commit
67a0869c1e
18 changed files with 1230 additions and 28 deletions
119
docs/online-token-status.md
Normal file
119
docs/online-token-status.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Optional online access-token status
|
||||
|
||||
Source proposal under KEY-WP-0042, driven by CUST-WP-0080 and existing
|
||||
NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09. Independent issuer/consumer
|
||||
contract review and native admission remain required. Existing deployments are
|
||||
unchanged. This extends the bounded issuer; it does not introduce a broker,
|
||||
approval store, PDP, or a second application-local token registry.
|
||||
|
||||
The legacy profile authenticates self-contained JWTs until expiry. That does
|
||||
not establish that an operation's initiating token is still unrevoked. The
|
||||
optional profile adds issuer-owned current access-token status using
|
||||
[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html) introspection and
|
||||
[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html) revocation. It supports
|
||||
access tokens only, without refresh tokens or token exchange.
|
||||
|
||||
## Enablement and lifecycle
|
||||
|
||||
Owner-admitted configuration opts in explicitly:
|
||||
|
||||
```yaml
|
||||
tokenStatus:
|
||||
enabled: true
|
||||
maxEntries: 10000
|
||||
```
|
||||
|
||||
The issuer must use HTTPS at its existing trusted ingress. The server's internal
|
||||
HTTP listener and reverse-proxy topology are unchanged; direct HTTP exposure is
|
||||
not admitted by an HTTPS issuer string. Omitted configuration keeps the existing
|
||||
expiry-only token/UserInfo contract and advertises no new endpoints. Enabling
|
||||
status registers `/introspect` and `/revoke`, advertises their implemented client
|
||||
authentication methods, and connects the same store to issuance and UserInfo.
|
||||
|
||||
Human PKCE and service grants add a cryptographically random `jti` to each access
|
||||
token. ID-token claims and existing assurance/tenant provenance remain unchanged.
|
||||
Distinct grants in the same second have distinct bearer tokens: a new grant can
|
||||
never remint a revoked token. A successful response is emitted only after the
|
||||
token hash, expiry and current registration commitment have been recorded. No
|
||||
bearer, client secret or identity/group snapshot is retained in the status store.
|
||||
|
||||
The store is bounded to the configured count (default 10000, maximum 1000000).
|
||||
Live records and revocation tombstones are never evicted to admit another grant;
|
||||
capacity exhaustion refuses issuance with a fixed 503. Expired entries are
|
||||
collected when admitting a new token. Capacity and per-client ingress rate limits
|
||||
need workload-representative admission: this is not a throughput/availability
|
||||
claim or protection against an authorized client filling the bound.
|
||||
|
||||
This profile uses KeyCape's existing **single-instance topology**. Status is
|
||||
process-local. Restart, deployment, rollback or store loss makes every earlier
|
||||
token inactive at online endpoints, even if its JWT signature/expiry still
|
||||
validates offline. Consumers must reauthenticate; there is no recovery replay
|
||||
or token-state import that can resurrect revoked authority. Multiple replicas
|
||||
are unsupported. Native admission must include this interruption and the
|
||||
measured reauthentication/recovery procedure. A persisted/shared store is not
|
||||
introduced here.
|
||||
|
||||
## Introspection and revocation
|
||||
|
||||
Both endpoints accept POST form bodies only, bound to 64 KiB; token values are
|
||||
bound to 32 KiB. Query parameters, duplicate form or authentication fields,
|
||||
unsupported content types and malformed authentication refuse without echoing
|
||||
input. Responses use `Cache-Control: no-store` and `Pragma: no-cache`.
|
||||
|
||||
Introspection requires the existing registered confidential client's
|
||||
`client_secret_basic` authentication. Only that client's own issued tokens can
|
||||
be inspected. A separate resource audience is returned as `aud`, not confused
|
||||
with the issuing `client_id`. Cross-client resource-server delegation needs a
|
||||
separately reviewed authorization contract and is not implicitly granted by a
|
||||
matching audience string. Public clients cannot enumerate token status.
|
||||
|
||||
An active response means the exact access token was issued in this process,
|
||||
has not expired or been revoked, still matches its current complete registration,
|
||||
and passes current issuer/signature/purpose validation. For humans, directory
|
||||
lookup must succeed and the current account must be enabled and not suspended.
|
||||
Status is checked again after directory lookup. Service subjects remain bound
|
||||
to their current registration. Active output contains only standard access-token
|
||||
metadata; inactive output contains only `active: false`. Unknown tokens, ID
|
||||
tokens, altered tokens, another client's tokens and expired/revoked tokens all
|
||||
return that same inactive response to an authenticated introspector.
|
||||
|
||||
Revocation authenticates confidential clients using the same method. A registered
|
||||
public client may revoke its own token using `client_id` plus possession of that
|
||||
bearer; public-client introspection remains unavailable. Revocation only affects
|
||||
the exact token issued to that client. Unknown, other-client, expired and already
|
||||
revoked tokens all produce the same empty 200. No token existence is disclosed.
|
||||
Token-type hints do not bypass the actual access-token lookup. Removal or changes
|
||||
to a registration invalidate online status; the commitment explicitly includes
|
||||
the secret digest and internal token lifetime omitted from public Client JSON.
|
||||
|
||||
When enabled, actual UserInfo consults the same store before lookup and again
|
||||
after it. Revocation therefore affects that existing endpoint immediately on its
|
||||
next check. Ordinary login/logout and upstream Authelia sessions are distinct:
|
||||
neither browser logout nor deleting a local cookie is represented as global
|
||||
JWT revocation.
|
||||
|
||||
## Consumer contract and remaining acceptance
|
||||
|
||||
Online status establishes current access-token status, not permission
|
||||
for an application resource or privileged effect. Consumers still independently
|
||||
validate the actual signed token, admitted issuer/key/audience/type, subject,
|
||||
tenant, principal type, assurance and freshness. The authenticated introspection
|
||||
response must refer to that exact token/client. Obtain a new current response
|
||||
before each protected operation; unavailable, malformed, inactive or ambiguous
|
||||
responses refuse. A cached positive is not a current-session check.
|
||||
|
||||
Access Engine retains resource/action decisions, Approval Engine exact one-shot
|
||||
approval, Warden admission and owner adapters execution. Bind their existing
|
||||
descriptor, case, transaction and exact effect before dispatch. A separate new
|
||||
operation/inverse token must be acquired and revoked on every terminal path;
|
||||
renewed authentication cannot revive a consumed approval or replay a mutation.
|
||||
This issuer capability alone implements none of those consumer bindings.
|
||||
|
||||
Native completion still requires independent issuer/profile review, exact
|
||||
image/config/trust admission, bounded retained-token custody, real human/service
|
||||
issuance, private transport, current-token/revocation integration in each owner,
|
||||
all exit-path cleanup, restart/expiry/unavailable/refusal checks, ongoing
|
||||
HTTP/API/Git/SSH authorization and audit evidence. Offline consumers which never
|
||||
consult the endpoint retain their existing JWT validity; no estate-wide immediate
|
||||
revocation claim follows from source implementation. Disabling the feature is
|
||||
not an acceptable fallback for a consumer admitted under the online profile.
|
||||
|
|
@ -20,7 +20,8 @@ is not required today.
|
|||
Consequences to plan for:
|
||||
|
||||
- **A restart drops in-flight logins.** Anyone mid-login gets an error and must
|
||||
start again. Issued tokens are unaffected — they are self-contained JWTs and
|
||||
start again. In the default profile issued tokens are unaffected — they are
|
||||
self-contained JWTs and
|
||||
stay valid until they expire.
|
||||
- **Rolling deployments are single-instance rollovers**, so expect a brief window
|
||||
where new logins fail. Draining (below) protects requests already in flight, not
|
||||
|
|
@ -89,7 +90,8 @@ and reject freshly issued tokens until its cache expires. Plan rotation as
|
|||
a same-`kid` swap as a breaking change for anyone caching aggressively.
|
||||
|
||||
**Removing a client registration does not revoke tokens already issued to it.**
|
||||
There is no introspection or revocation endpoint, so an issued token stays valid
|
||||
In the default expiry-only profile there is no introspection or revocation
|
||||
endpoint, so an issued token stays valid
|
||||
until it expires — 15 minutes by default, or the client's `tokenLifetime`. To cut
|
||||
off a compromised client, remove the registration *and* wait out the lifetime, or
|
||||
rotate the signing key if you cannot.
|
||||
|
|
@ -281,3 +283,12 @@ set with net-kingdom `sso-mfa/k8s/keycape/openbao-signin-diagnosis-rollout.py`;
|
|||
`--operation remove` plus a restart is the rollback. A redirect URI that is not http(s)
|
||||
skips the page. Every `auth_success` log line now carries `client_id`, `uid` and
|
||||
`assurance`, and no token, code, email or one-time password.
|
||||
|
||||
## Proposed online token-status profile
|
||||
|
||||
KEY-WP-0042 adds an explicitly enabled singleton profile. It makes prior tokens
|
||||
inactive at online endpoints after restart and keeps revoked-token tombstones
|
||||
until expiry. It does not revoke offline consumers or upstream/browser sessions.
|
||||
Read [the complete lifecycle and admission contract](online-token-status.md)
|
||||
before proposing activation; no image/config rollout or consumer admission is
|
||||
established by source tests. Existing deployed configuration remains unchanged.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue