Files
Rendezvous/docs/security/provisioning.md
T
KyuubiYoru 08729ae25c
quality-gate / quality (push) Failing after 1m9s
quality-gate / container (push) Has been skipped
feat(deployment): add secure Linux runtime (#17)
2026-07-16 15:03:04 +02:00

4.3 KiB

Game provisioning and signing-key lifecycle

Tracking: #5

Rendezvous treats game and environment scope as provisioned policy, not caller input. Production starts only when it can build an enabled policy registry and load at least one currently active signing key from an external secret provider. Unknown and disabled scopes fail closed.

Policy boundary

Each GamePolicy fixes the allowed:

  • game/environment pair and regions;
  • exact gameplay protocol versions;
  • publisher trust and listing visibility modes;
  • metadata keys, required keys, per-value limits, total bytes, and key count;
  • listing, anonymous-host, and active-attempt quotas; and
  • dedicated fallback feature policy.

Publisher authorization first authenticates a typed principal, then derives the authoritative game/environment from that principal. Request fields are compared for mismatch detection but never replace the authenticated scope. Dedicated workloads, short-lived player-host grants, anonymous unlisted publishers, and operators are distinct principal types. Operator credentials cannot be used as publisher credentials, and anonymous publishers cannot escalate to public visibility.

Signed credentials

Signed principal credentials use the compact form rv1.<key-id>.<base64url-payload>.<base64url-HMAC-SHA256>. The signed payload contains version, issuer, audience, subject, principal kind, bounded scope, issued/not-before/expiry times, and a random nonce. It contains no signing key, reusable publisher secret, player identity, or gameplay state.

Validation is deliberately ordered and bounded:

  1. enforce the v1 opaque-credential length and four-segment grammar;
  2. resolve a known, non-revoked key in its verification window;
  3. compare the HMAC in fixed time;
  4. parse canonical bounded JSON;
  5. require exact version, issuer, and audience;
  6. enforce clock skew, expiry, key lifetime, principal kind, and scope shape.

Failures return typed internal reasons without echoing the credential. Logs and metrics must record only allowlisted tenant/principal/result dimensions; token, key, secret-reference value, and raw key material are excluded.

Rotation and revocation

A key is bound either to operator credentials only or to allowed publisher credential kinds for exactly one game/environment. The verifier checks this authority after the signature, so even a compromised game grant issuer cannot mint a valid cross-game or operator credential.

A key also has three times: NotBefore, SignUntil, and VerifyUntil. Issuance picks the newest authorized non-revoked key inside its signing window. Older credentials continue to verify until the old key's verification window ends, providing an explicit overlap. After VerifyUntil they fail as retired. Configuration revocation and runtime revocation both reject immediately. A configured revoked key retains only its public key ID/lifecycle metadata and does not require retired secret material to remain available.

Key IDs are non-secret base64url identifiers. Secret references are resolved through ISecretProvider; production supports base64 env:<VARIABLE> and raw file:/absolute/path references to bounded non-symlink files. The interface is replaceable by a deployment-specific vault/KMS adapter. The committed development profile uses an in-memory random key identified by a development:ephemeral/... reference. It never writes key material to disk and all credentials become invalid when the process exits.

Production configuration

Rendezvous:Provisioning supplies issuer, audience, clock skew, signing-key descriptors, and game policies. A production key reference such as env:RENDEZVOUS_SIGNING_KEY_2026_01 expects that environment variable to hold at least 32 random bytes encoded as base64. file:/run/secrets/rendezvous-signing expects the raw bytes in a read-only, absolute, non-symlink file. Missing, malformed, short, inactive, or duplicate keys stop startup with a key-ID-only diagnostic. No game-wide secret belongs in appsettings, source control, examples, the Client package, URLs, responses, logs, metrics, exceptions, or diagnostic dumps.

Readiness becomes true only after provisioning and UDP startup both succeed. OpenAPI generation uses a pinned build-only host and does not start listeners or bypass provisioning in a deployed server process.