Phase 5 — API keys.
Today
No key mechanism exists that grants access. The legacy api-keys table is unrelated and is no longer honoured by the proxy.
Do
Per ADR-013, a key is a long-lived JWT signed by the data proxy: iss the proxy, sub the service account, a unique jti, type: api_key, and an optional exp. It is exchanged at /.sts like any other token; the only addition to the exchange is a jti revocation check.
- Issue a key: shown once, bound to exactly one service account.
- Store
expires_at (nullable) on the key record, not in the token. Default to a bounded expiry; allow "never expires" as an explicit choice with a visible warning.
- Allow expiry to be changed after issuance — extended for a workload that needs longer, shortened during an incident.
- Allow several active keys per service account. Rotation is issue-new → deploy → revoke-old, at the operator's pace.
- List, revoke, rotate, and record last-used.
Done when
A key can be issued, used at /.sts to obtain credentials, and revoked — with revocation effective for new exchanges within the jti cache TTL: 60s, aligned with the permissions lookup.
Watch
- Revoking a key does not recall credentials already issued from it. Those live to their session cap — up to 12 hours at
STS_MAX_SESSION_DURATION_SECS. Rotating SESSION_TOKEN_KEY is the emergency stop and invalidates every active session.
- Expired, revoked and unknown must return the same client-visible outcome plus a request id. Distinguishing them turns the endpoint into a key-validity oracle. That defence needs a matching latency floor, since rejecting locally is measurably faster than a lookup.
- Non-expiring keys depend on the proxy retaining every signing key it has ever used for verification (ADR-013, "Signing Key and Rotation"). If verification keys are ever pruned on a current-plus-previous schedule, keys older than two rotations break silently regardless of their record.
generateSecretAccessKey in src/lib/actions/crypto.ts has modulo bias over its alphabet. Do not copy it forward.
See ADR-013 — amended by source-cooperative/data.source.coop#230 so that sub is a service account and there is no per-key Role binding in the first release.
The proxy half — accepting these tokens at /.sts, the jti check, the latency floor, and minting — is source-cooperative/data.source.coop#231.
Depends on #546, delivered by #566 and #567 and closed by #570. No longer depends on #561: ADR-013 as revised in source-cooperative/data.source.coop#234 fixes the key marker. The work is #570, reworked by #580, with the proxy half in source-cooperative/data.source.coop#235.
Part of #491.
Phase 5 — API keys.
Today
No key mechanism exists that grants access. The legacy
api-keystable is unrelated and is no longer honoured by the proxy.Do
Per ADR-013, a key is a long-lived JWT signed by the data proxy:
issthe proxy,subthe service account, a uniquejti,type: api_key, and an optionalexp. It is exchanged at/.stslike any other token; the only addition to the exchange is ajtirevocation check.expires_at(nullable) on the key record, not in the token. Default to a bounded expiry; allow "never expires" as an explicit choice with a visible warning.Done when
A key can be issued, used at
/.ststo obtain credentials, and revoked — with revocation effective for new exchanges within thejticache TTL: 60s, aligned with the permissions lookup.Watch
STS_MAX_SESSION_DURATION_SECS. RotatingSESSION_TOKEN_KEYis the emergency stop and invalidates every active session.generateSecretAccessKeyinsrc/lib/actions/crypto.tshas modulo bias over its alphabet. Do not copy it forward.See ADR-013 — amended by source-cooperative/data.source.coop#230 so that
subis a service account and there is no per-key Role binding in the first release.The proxy half — accepting these tokens at
/.sts, thejticheck, the latency floor, and minting — is source-cooperative/data.source.coop#231.Depends on #546, delivered by #566 and #567 and closed by #570. No longer depends on #561: ADR-013 as revised in source-cooperative/data.source.coop#234 fixes the key marker. The work is #570, reworked by #580, with the proxy half in source-cooperative/data.source.coop#235.
Part of #491.