Przeglądaj źródła

Updated documentation

Efren Yevale Varela 3 tygodni temu
rodzic
commit
cf6c249fb8
5 zmienionych plików z 43 dodań i 24 usunięć
  1. 10 8
      README.md
  2. 18 7
      RUNBOOK.md
  3. 10 6
      docs/CHANGELOG.md
  4. 1 0
      docs/DEPLOYMENT.md
  5. 4 3
      docs/OAUTH.md

+ 10 - 8
README.md

@@ -47,7 +47,9 @@ cargo build --locked --release
 
 The server reads configuration from `D_OAUTH_*` environment variables. Export a
 development configuration (values here are placeholders; see
-[docs/CONFIGURATION.md](docs/CONFIGURATION.md) for every key).
+[docs/CONFIGURATION.md](docs/CONFIGURATION.md) for every key). The ports below
+are the local-dev defaults (Postgres `5432`, Redis `6379`); the shared test
+services use `5433`/`6380` (see Tests).
 
 ```bash
 export D_OAUTH_ISSUER=https://localhost:8080
@@ -146,8 +148,9 @@ full grant behavior.
 ## Tests
 
 The authoritative suite. The integration and end-to-end tests connect to the
-shared Postgres and Redis from `compose.test.yaml` (Postgres on
-`127.0.0.1:5433`, Redis on `127.0.0.1:6380`), so start the services first and
+shared test Postgres and Redis from `compose.test.yaml` (test defaults:
+Postgres on `127.0.0.1:5433`, Redis on `127.0.0.1:6380` — not the dev
+`5432`/`6379`), so start the services first and
 tear them down with `down -v`:
 
 ```bash
@@ -208,15 +211,14 @@ The container `HEALTHCHECK` uses the `healthcheck` subcommand, which probes the
 plaintext health listener without a shell or curl. See
 [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
 
-For a local multi-replica proof, the deployment assets provide a compose file
-that runs three app replicas against one Postgres and one Redis. It is the local
+For a local multi-replica proof, `compose.yaml` runs three app replicas
+against one Postgres and one Redis. It is the local
 analogue of the cluster manifests, and proves that a token issued by one replica
-is introspectable via another. If your checkout does not include the compose
-file yet, it ships with the cluster manifests; see
+is introspectable via another; see
 [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md):
 
 ```bash
-docker compose up --scale app=3
+docker compose -f compose.yaml up --scale app=3
 ```
 
 Cluster deployment uses a separate migration Job, a PodDisruptionBudget, and

+ 18 - 7
RUNBOOK.md

@@ -115,10 +115,11 @@ correlation. No step below writes a secret to a log.
 
 ### Rotation order and commands
 
-- [ ] Confirm whether the key store leaked with the dump. The token pepper and
-      the TOTP encryption key live outside the database
+- [ ] Confirm whether the key store leaked with the dump. The token pepper, the
+      TOTP encryption key, and the PII field-encryption key live outside the database
       (`D_OAUTH_TOKEN_PEPPER` / `D_OAUTH_TOKEN_PEPPER_FILE`,
-      `D_OAUTH_TOTP_ENCRYPTION_KEY` / `D_OAUTH_TOTP_ENCRYPTION_KEY_FILE`); if it
+      `D_OAUTH_TOTP_ENCRYPTION_KEY` / `D_OAUTH_TOTP_ENCRYPTION_KEY_FILE`,
+      `D_OAUTH_PII_ENCRYPTION_KEY` / `D_OAUTH_PII_ENCRYPTION_KEY_FILE`); if it
       is not provably untouched, assume it leaked and continue.
 - [ ] **1. Rotate the token pepper.** Point `D_OAUTH_TOKEN_PEPPER` (or
       `D_OAUTH_TOKEN_PEPPER_FILE`) at a new value, bump
@@ -134,7 +135,15 @@ correlation. No step below writes a secret to a log.
 - [ ] Re-enroll every TOTP user before dropping the old key:
       `d-oauth-server user enroll-totp --email <email>`
       (prints the secret and `otpauth://` URI exactly once).
-- [ ] **3. Rotate every confidential client secret.** Read the current metadata,
+- [ ] **3. Rotate the PII field-encryption key.** Point
+      `D_OAUTH_PII_ENCRYPTION_KEY` (or `D_OAUTH_PII_ENCRYPTION_KEY_FILE`) at a
+      new value, bump `D_OAUTH_PII_KEY_ID`, and roll the replicas with the old
+      key retained. Then, under a write freeze (app tier scaled to zero), run
+      `d-oauth-server pii rotate` to re-encrypt every `_enc` value and recompute
+      every blind index, verify with `d-oauth-server pii verify`, and only then
+      retire the old key. See
+      [docs/OPERATIONS.md](docs/OPERATIONS.md#pii-and-totp-key-rotation).
+- [ ] **4. Rotate every confidential client secret.** Read the current metadata,
       then re-submit it through RFC 7592 `PUT /register/{client_id}` to mint a
       new secret and a new registration token:
 
@@ -151,15 +160,17 @@ correlation. No step below writes a secret to a log.
       The `PUT` response carries `client_secret` and
       `registration_access_token` exactly once. Store both before the process
       exits; the previous registration token immediately answers `403`.
-- [ ] **4. Rotate every DCR registration token.** Every successful `PUT` above
+- [ ] **5. Rotate every DCR registration token.** Every successful `PUT` above
       already rotated the registration token. If you also need new
       registrations after the breach, issue a fresh single-use initial-access
       token:
       `d-oauth-server initial-access-token issue --uses <n> --ttl <seconds>`
       (the raw token is printed once).
-- [ ] **5. Verify fail-closed behavior.** Readiness must report ready only with
+- [ ] **6. Verify fail-closed behavior.** Readiness must report ready only with
       both dependencies up:
-      `d-oauth-server healthcheck --url http://127.0.0.1:8081/health/ready`.
+      `d-oauth-server healthcheck --url http://127.0.0.1:8081/health/ready`
+      (readiness; liveness is `/health/live` as in
+      [docs/OPERATIONS.md](docs/OPERATIONS.md)).
       A dependency outage during recovery must yield `503`, never a fail-open
       success.
 

+ 10 - 6
docs/CHANGELOG.md

@@ -57,7 +57,7 @@ While the version stays below `1.0.0`, a minor bump may carry a breaking change.
   codecs); the text secret, URI, and one-time recovery codes are unchanged.
 - A full-database-disclosure response procedure: `docs/SECURITY.md` gains the
   dump blast-radius table and the ordered rotation (token pepper, TOTP key,
-  client secrets, DCR registration tokens), and `RUNBOOK.md` gains the
+  PII field-encryption key, client secrets, DCR registration tokens), and `RUNBOOK.md` gains the
   matching operator checklist with concrete commands.
 - Cluster hardening in `deploy/k8s`: the token pepper and TOTP key mount as
   `0400` tmpfs files under `/run/secrets` (read through
@@ -68,10 +68,12 @@ While the version stays below `1.0.0`, a minor bump may carry a breaking change.
   rejects one), and `automountServiceAccountToken` is `false`. Remote JAR
   `request_uri` is documented as unsupported under the default-deny, with the
   Cilium FQDN opt-in path in `docs/DEPLOYMENT.md`.
-- `user disable` now purges all Redis state for the subject (sessions and their
+- `user disable` now purges all subject-scoped Redis state for the subject (sessions and their
   meta, codes, devices with their sidecars and mappings, grants via cascade
   revoke, standalone tokens, and the lockout counter) before flipping
-  `users.disabled`; a Redis failure aborts before the flag changes, so the
+  `users.disabled`; records without a subject (PAR, pending, DPoP and
+  `private_key_jwt` replay markers, rotation map, TOTP replay, rate-limit
+  counters) are untouched. A Redis failure aborts before the flag changes, so the
   command fails closed.
 - `POST /revoke` authenticates mTLS-registered clients (RFC 8705 §2) with the
   verified peer chain injected by the TLS accept loop, exactly as the token and
@@ -86,9 +88,11 @@ While the version stays below `1.0.0`, a minor bump may carry a breaking change.
   previous key. `pii_dual_write` (default `false`, encrypt-only) turns on
   dual-write for the rolling-upgrade window; readiness fails closed while the
   switch is off and any blanked plaintext PII column still holds a value.
-- TOTP secrets now encrypt under HKDF-SHA256 (info `totp-gcm-v2`) instead of
-  bare `SHA256(totp_key)`. New blobs carry format version 2; version-1 blobs
-  keep decrypting through a legacy fallback, so no re-enrollment is needed.
+- TOTP secrets now use a per-row envelope (blob version 3): a random DEK
+  wrapped by AES-256-GCM under HKDF-SHA256 (info `totp-kek-v1`), so
+  `totp rewrap` re-wraps the DEK without re-encrypting the secret. Versions 1
+  (bare `SHA256(totp_key)`) and 2 (HKDF info `totp-gcm-v2`) keep decrypting
+  through a legacy fallback, so no re-enrollment is needed.
 
 - `GET /.well-known/oauth-authorization-server` now carries an explicit
   `Cache-Control: public, max-age=3600` instead of relying on browser

+ 1 - 0
docs/DEPLOYMENT.md

@@ -371,6 +371,7 @@ manifest; verify the cluster CNI applies it.
 
 - Egress admits DNS to the `kube-system` namespace on port 53 (UDP and TCP) and
   Postgres (5432) and Redis (6379) scoped to the namespace that hosts them —
+  cluster-service defaults, not the shared-test `5433`/`6380` —
   `d-oauth` in the reference deployment, matching the ConfigMap DSNs
   (`*.d-oauth.svc.cluster.local`). It carries no `ipBlock: 0.0.0.0/0` peer, so
   neither port is an exfiltration channel to an arbitrary internet host;

+ 4 - 3
docs/OAUTH.md

@@ -281,9 +281,10 @@ on every use.
 `private_key_jwt` is client authentication only. It produces no confirmation and
 never satisfies sender constraint. For token exchange, the proof must match the
 subject token's confirmation. See [SECURITY.md](SECURITY.md) for the full matrix
-and the confirmation propagation rule. It accepts `RS256`, `PS256`, `ES256`, and
-`EdDSA` only (`none` and HMAC are rejected); `exp` may lie at most 300 s past
-`now` plus 30 s clock skew, and the single-use `jti` marker lives for the
+and the confirmation propagation rule. `private_key_jwt` accepts `RS256`, `PS256`, `ES256`, and
+`EdDSA` only (`none` and HMAC are rejected); DPoP proofs accept `ES256`, `EdDSA`, and `RS256` only (no `PS256`).
+`exp` may lie at most 300 s past `now` with 30 s clock-skew leeway on each side
+(360 s bound), and the single-use `jti` marker lives for the
 assertion's remaining validity, clamped to `[1 s, 360 s]`.
 
 ## Metadata