Skip to content

Latest commit

 

History

History
179 lines (169 loc) · 10.4 KB

PRODUCTION_CHECKLIST.md

File metadata and controls

179 lines (169 loc) · 10.4 KB

Production checklist

What a deployment must do before real clients rely on it. Each item links to the part of GETTING_STARTED.md that explains it. New refuses many of these mistakes at startup under production assurance, but not all of them. The items marked (yours) are things no constructor can check.

Authorization server (server)

  • Config.Assurance = server.AssuranceProduction, with Config.Deployment set. AssuranceDevelopment is not FAPI 2.0 conformant, though its metadata looks the same. Step 2
  • Durable stores that declare storage.StoreAssurance and pass the contract tests in your own test suite, since the declaration isn't verified: storage.TestClientRepositoryContract, TestTransactionStoreContract, TestGrantStoreContract, TestReplayStoreContract, TestRevocationStoreContract, plus TestAccessTokenStoreContract (opaque access tokens), TestNonceStoreContract (DPoP nonces) and TestBackchannelAuthenticationStoreContract (CIBA). With DeploymentHorizontallyScaled, every instance must share them. The sqlstore module provides the client repository, transaction, grant, replay, nonce, revocation, opaque access token, CIBA and pairwise subject stores on PostgreSQL (any deployment) or SQLite (DeploymentSingleInstance only), already passing those contracts: see Storage with sqlstore. Step 1
  • Durable signing keys that declare keys.KeyCustodyAssurance (keys.NewKeyManagerFromSigners with keys.DeclareCustody), and a plan for rotating them.
  • Client keys from keys.NewStaticClientKeys or keys.NewClientJWKSSource. A keys.ClientKeySource of your own must pass keys.TestClientKeySourceContract. Step 1
  • An audit sink (Dependencies.Audit), server.SystemClock{} and crypto/rand.Reader. Step 4
  • A revocation choice: a revocation store the resource servers share, or server.NoRevocation{} with short access token lifetimes, knowing that code reuse and RevokeGrant then revoke nothing. Step 2
  • A DPoP nonce choice: a nonce store every instance shares (Dependencies.Nonces, with Limits.DPoPNonceLifetime of at most 15 minutes), so a DPoP proof generated ahead of time or captured is refused, at the cost of one extra round trip per client on first use; or server.NoDPoPNonces{} to decline, as an mTLS-only deployment can. Step 4
  • A client certificate trust choice: NoPKIClientAuthentication{} unless a client uses tls_client_auth; otherwise TrustedClientCAs with revocation checking, or ClientCertificateChainVerifiedUpstream{} only when your TLS termination verifies every chain. Step 4
  • (yours) Authenticate before redirecting. Before a user has authenticated, end with DenyLocally, never Deny or AuthenticationFailed. Step 5
  • (yours) Pass the real authentication time to server.NewAuthenticationContext: when the user actually authenticated, not time.Now() when you reuse a session, so max_age and prompt=login hold. Step 5
  • (yours) Bind the interaction to the browser with server/interactioncookie (its keys shared by every instance) or the equivalent, and protect the consent form against CSRF. Step 5
  • (yours) Wrap /authorize and every login, consent and error page in server.AuthorizationPageHeaders (anti-framing, Referrer-Policy: no-referrer, no-store). Load no third-party scripts or images there, and don't log full /authorize URLs. Redirect with 303 after a form POST, and render errors with WriteText/WriteJSON, never Error(). Step 5
  • (yours) The HTTP layer: TLS 1.2 or later with BCP 195 cipher suites (at the proxy, if one terminates TLS), HSTS on browser-facing endpoints, and no CORS on the authorization endpoint. Step 6
  • (yours) Client certificates behind a proxy: read a forwarded certificate with fapihttp.ForwardedClientCertificate (RFC 9440 Client-Cert), passing a trusted check such as fapihttp.RemoteAddrIn that accepts only your own proxy, and have the proxy strip any copy of the header a client sent. Step 7
  • Subjects: decide between public and pairwise subjects before clients store any sub. Pairwise subjects

Resource server (resource)

  • resource.AssuranceProduction with Deployment set, and stores that pass the same contract tests as above. Build a Config
  • Config.ResourceURLs set to this API's own external URLs; behind a proxy that rewrites paths, set VerifyRequest.URL. Verify a request
  • The access token format matches the authorization server's. JWT: MaxTokenLifetime at least the server's AccessTokenLifetime, and Audience set. Opaque: the same storage.AccessTokenStore the authorization server writes to, since there is no introspection endpoint. Wire Dependencies
  • The same revocation store as the authorization server, or resource.NoRevocationUntilExpiry{MaxTokenLifetime: ...} (at most 15 minutes) to decline checking it; production refuses NoRevocation{}. Wire Dependencies
  • A DPoP nonce choice: a nonce store of the resource server's own (never the authorization server's; with sqlstore, Nonces() from a DB with a TablePrefix of its own), shared by its instances, with Random and Limits.DPoPNonceLifetime; or resource.NoDPoPNonces{} to decline. serverresource.NewVerifier takes it as Options.Nonces. Wire Dependencies
  • (yours) Authorize anything a user owns by AuthorizationContext.EndUser(), never by InternalSubject alone (a client credentials token's subject is a client), and use ClientSubject for anything returned to a client. Pairwise subjects at a resource server
  • (yours) PeerCertificate behind a proxy: the same rule as the authorization server's, with fapihttp.ForwardedClientCertificate. Verify a request

Client (client)

  • client.AssuranceProduction, with Dependencies.HTTP from fapihttp.NewClient and any client certificate or private roots given when it's built. Step 10
  • A durable session store that passes storage.TestSessionStoreContract (sqlstore's Sessions() does; for a native app with CallbackBindingDeviceLocalStore, on SQLite), and durable keys declaring keys.KeyCustodyAssurance.
  • (yours) Bind each authorization to the browser that began it (client/sessioncookie), against login CSRF. Step 10
  • (yours) Serve the redirect URI with Referrer-Policy: no-referrer, and no third-party content, so the authorization code doesn't leak (RFC 9700 §4.2.4).
  • RequireAuthorizationResponseIss, and MaxIDTokenLifetime set to your provider's ID token lifetime if you know it.
  • (yours) Seal stored tokens with client.TokenSetSealer if you keep them past the request. Step 10

Storage with sqlstore

For a deployment keeping its stores in the sqlstore module. Durable storage

  • The right database for the deployment: PostgreSQL for DeploymentHorizontallyScaled; SQLite (WAL mode, a busy timeout) only for DeploymentSingleInstance, on the one host that serves.
  • The schema migrated before serving: Migrate at startup as a role that may create tables, or from a deployment step, with CheckSchema at startup to fail fast when it's behind.
  • Sweep on a schedule (every few minutes) with the server's time, or the tables grow without bound. A CIBA request is kept BackchannelExpiredRetention (an hour) past its expiry, so a late poll is told expired_token.
  • One TablePrefix per role and tenant: the authorization server and the resource servers reading its opaque access tokens or revocations share the server's prefix (and Sealer); a resource server's DPoP nonces get a prefix of their own; servers with different issuers never share one.
  • (yours) A least-privilege database role for each resource server: SELECT on the server's access_tokens, revocations and schema_migrations, SELECT/INSERT on replay, and its own prefix's tables; only the authorization server sweeps the tables they share.
  • (yours) A sealing key kept outside the database and its backups (Options.Sealer, AESGCMSealer), with a rotation plan: RotatingSealer, which opens with the old and new keys until the old one's records expire, well before a key's 2^32 seals (weeks, on a busy server).
  • ReadUnsealed only while migrating an existing database to sealing: deploy with it, run SealRecords, then deploy without it.
  • (yours) Write access to the database as tight as read access: sealing keeps records confidential and bound to their row, but anyone who can write can still delete rows or change lookup columns.