asobi_ops_token (asobi v0.72.6)
View SourceThe minted, env-scoped ops token: how a managed tenant's browser reaches this environment's ops plane.
console.asobi.dev authenticates the tenant, checks it owns this environment,
maps its role onto capability classes and mints a token. The browser then
talks to this environment directly, because the control plane must not
read a tenant game database and a browser can already reach an env pod while
the control plane cannot.
The shape
v1.<base64url(payload)>.<base64url(mac)>payload is JSON: env, sub, caps, iat, exp. The MAC covers
v1.<payload> - the version prefix is signed, so a future version cannot be
stripped down to this one.
The key
ops_token_secret, from ASOBI_OPS_TOKEN_SECRET - a per-environment secret
that signs ops tokens and does nothing else.
It was briefly derived from ENGINE_API_KEY, because both sides already held
that value. Deriving through a label stops two keys being confused; it does
not stop them being compromised together, and a value that both
authenticates this engine to the control plane and signs the operator
credentials it accepts is one leak away from doing both for an attacker. So
it is its own secret, generated per environment by the provisioner and held
nowhere else - not even in the control plane's database.
Being per environment, a token minted for one env cannot validate at another
even before env is checked. env is checked anyway.
Rotating it revokes every token outstanding for this environment at once, which is the only revocation there is.
What a signature does not buy
A valid MAC is necessary and not sufficient. exp must be in the future,
and the whole lifetime must be short - a token signed with a year-long
exp is refused, so a mint bug on the other side of the wire cannot issue
one this side will honour. Capability classes outside ADR 0007's vocabulary
are refused rather than ignored, and an unknown env is refused.
Nothing here is stateful: there is no revocation list, which is exactly why the lifetime is capped rather than merely checked.
Summary
Functions
Whether this node can verify a minted token at all.
The longest lifetime this node will honour, in seconds.
Mint a token for Claims.
Verify Token against the configured environment, returning its claims.
Types
-type claims() :: #{env := binary(), sub := binary(), caps := [asobi_ops_caps:class()], iat := integer(), exp := integer()}.
Functions
-spec configured() -> boolean().
Whether this node can verify a minted token at all.
Exported so asobi_console_env's enable decision reads the same pair
verify/1 does. A managed environment configures no ops_secret, so without
this the console would be switched off underneath the one credential it
accepts.
-spec max_ttl() -> pos_integer().
The longest lifetime this node will honour, in seconds.
Mint a token for Claims.
Here rather than only in asobi_saas so the two sides cannot drift: the
minting side is a consumer of this function's format, and the round trip is
tested against it.
Verify Token against the configured environment, returning its claims.
Every rejection is one atom and the caller answers 403 for all of them, so a holder of a bad token cannot tell which check failed.