Capability-Grant Authorization (Permission to Run, feature 078)
wbsp-api can authorize application hosting from issuer-signed capability grants
(PTA + RTH) instead of only an identity check, and enforce the resource limits those grants
carry. It ships disabled (observe mode) and must be explicitly switched on per cluster.
The two modes
| Mode | What wbsp-api does |
|---|---|
| observe (default) | Verifies grants and logs what it would reject / cap / tear down — but changes nothing. Every deploy behaves exactly as before. The existing identity check stays authoritative. |
| enforce | A missing/invalid/replayed grant refuses the deploy (403/409); the RTH's hard ceilings are applied as k8s container Resources + a namespace ResourceQuota/LimitRange; a revoked grant's app is torn down by the reconcile loop. |
wbsp-api cannot make a deploy fail that would succeed today until enforce is enabled.
Environment variables (on the wbsp-api pod)
| Variable | Meaning |
|---|---|
WBSP_GRANT_VERIFY_KEYS | Comma-separated kid:base64(ed25519 public key) entries — the public keys wbsp-api trusts to sign grants. Public keys only; never a private/secret key. Now the FALLBACK for any role without its own anchor. |
WBSP_PTA_VERIFY_KEYS | Keys trusted to sign permission-to-access (image access). Typically the catalogue website's. |
WBSP_RTH_VERIFY_KEYS | Keys trusted to sign hosting grants. Typically this platform's subscriber portal. |
WBSP_CONTROL_VERIFY_KEYS | Keys trusted to sign control tokens (the control plane below). |
WBSP_GRANT_AUDIENCE | The audience every grant must name (aud). One value per cluster, by convention cluster:<operator-chosen id>. Defaults to WBSP_API_AUDIENCE. |
WBSP_GRANT_MODE | observe (default) or enforce. |
Notes:
- With no verify keys at all, grant checks are disabled (pre-078 behavior).
- Each per-role variable falls back to
WBSP_GRANT_VERIFY_KEYSwhile unset, so a cluster that sets none of them behaves exactly as it did before the split. A role variable that IS set replaces the legacy set for that role — it never merges, because merging would leave the legacy signer able to mint the role you just separated. WBSP_GRANT_MODE=enforcewith any role unable to verify anything is refused at boot, naming the role — never a silent open gate.- Verification is offline (no JWKS fetch); if keys are configured but unloadable, verification fails closed.
- Boot logs name each role's key source and the effective audience, so a rollout is checkable
without redeploying:
pta=WBSP_PTA_VERIFY_KEYS rth=WBSP_GRANT_VERIFY_KEYS (fallback) ….
Cutover procedure (per cluster)
- Enable observe: set
WBSP_GRANT_VERIFY_KEYS(+ optionallyWBSP_GRANT_AUDIENCE), leaveWBSP_GRANT_MODE=observe, redeploywbsp-api. - Soak: run real deploys and watch the
grantlogger. Each deploy emitsgrant.observe would_alloworgrant.observe would_reject reason=<x>. Confirm the would-decisions are correct against live traffic. Nothing is enforced yet. - Separate the anchors and the audience — both are preconditions of enforce, not optional
polish. Give each role its own keys, and give this cluster its own
cluster:<id>audience, still under observe: a mismatch is a would-reject log line, not an outage. Prove each key withPOST /api/v1/grants/verify(control-token authenticated; writes nothing). - Flip to enforce: set
WBSP_GRANT_MODE=enforce, redeploywbsp-api. From now on invalid grants are refused and resource ceilings are applied. - Reversible: set
WBSP_GRANT_MODE=observeand redeploy to fall back at any time — no app redeploy required.
Do not skip step 3. Enforcing on a shared anchor and a shared audience is a gate that looks closed and is not: whoever can sign one role can sign them all, and a token minted for another cluster still names the same audience.
Control plane (control-token auth)
The issuing authority (website/HAP) calls these with a control token signed by a trusted key:
GET /api/v1/tenants/{tenant}/usage·GET /api/v1/tenants/{tenant}/apps·GET /api/v1/tenants/{tenant}/apps/{name}/usage— inventory, limits, threshold state. (Usage / estimated cost are"unavailable"until Phase 2 metering lands.)POST /api/v1/grants/verify— verify-only: does this cluster accept a token, and as which role? Writes nothing, deploys nothing. The way an issuing authority proves its keys.POST /api/v1/tenants/{tenant}/apps/{name}/enforce{action: shutdown}— stop an app (observe returnsapplied:false);{action: resume}— redeploy it from the platform's retained record, in either mode, taking deploy time and reporting no resulting state;throttleis 501.DELETE /api/v1/grants/{jti}— revoke a grant. Enforce mode marks it revoked and the reconcile loop tears the app down; observe returnsapplied:false.
Platforms without a deployer
A cluster may run wbsp-api with WBSP_API_DEPLOYER=disabled (the appliance case) and grants
configured. It boots: the verifier and control plane are live, and only the reconcile loop is
skipped, with a WARN naming it. There, revocation is recorded but not executed — the response
carries executed:false — until that platform supplies a remover; shutdown and resume answer
503 deployer_unavailable. This combination used to be fatal at boot.
What is NOT in this feature
- Cost enforcement / metering — Phase 2 (rate card + the dormant
wbsp.usage.*instruments). - Federation — anchors are per ROLE, not per issuer; issuer→tenant scoping and per-issuer revocation are deferred.
- The token-minting side — issued by the WBSP website (or a HAP) per
specs/078-permission-to-run/contracts/token-claims.md; not part ofwbsp-platform. Running your own platform and want your own user/app management instead of wbsp.ai's? Build your own issuer — see Building an Issuing Authority.