1791417600_pii_encryption.sql 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. -- PII envelope encryption, expansion step (plan todo 8).
  2. --
  3. -- Forward-only: deterministic, additive expansion only. This migration adds the
  4. -- encrypted (`_enc`) and keyed blind-index (`_index`) columns the PII backfill
  5. -- fills, and drops NOT NULL on the plaintext columns that backfill blanks to
  6. -- NULL. Nothing is dropped or renamed: the old plaintext columns, the `citext`
  7. -- extension, and the legacy unique constraints all remain, so version N-1
  8. -- replicas keep working through the rolling upgrade (docs/DATABASE.md,
  9. -- "Expand and contract").
  10. --
  11. -- Maintenance window: `ALTER TABLE ... DROP NOT NULL` takes ACCESS EXCLUSIVE on
  12. -- users / redirect_uris / oauth_clients, blocking both reads and writes for the
  13. -- duration; each non-concurrent `CREATE INDEX` takes SHARE, blocking writes.
  14. -- sqlx runs every migration inside ONE transaction, so a non-transactional
  15. -- `CREATE INDEX CONCURRENTLY` is NOT used (PostgreSQL rejects it in a
  16. -- transaction). Apply this migration from the dedicated migration Job during a
  17. -- maintenance window with the app tier quiesced; see RUNBOOK.md.
  18. --
  19. -- The legacy `ux_users_email UNIQUE (email)` and
  20. -- `ux_redirect_uris_uri_kind UNIQUE (uri, kind)` constraints are deliberately
  21. -- NOT dropped. PostgreSQL treats NULLs as distinct, so once the backfill blanks
  22. -- the plaintext each constraint goes inert (any number of NULL rows allowed)
  23. -- while still serving old replicas.
  24. -- (a) Plaintext columns the backfill blanks become nullable.
  25. ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
  26. ALTER TABLE redirect_uris ALTER COLUMN uri DROP NOT NULL;
  27. ALTER TABLE oauth_clients ALTER COLUMN client_name DROP NOT NULL;
  28. -- (b) users: encrypted email ciphertext + keyed blind index.
  29. ALTER TABLE users
  30. ADD COLUMN email_enc bytea NULL,
  31. ADD COLUMN email_index text NULL;
  32. CREATE UNIQUE INDEX ux_users_email_index
  33. ON users (email_index)
  34. WHERE email_index IS NOT NULL;
  35. -- (c) oauth_clients: encrypted PII metadata and blind-indexed mTLS identities.
  36. ALTER TABLE oauth_clients
  37. ADD COLUMN client_name_enc bytea NULL,
  38. ADD COLUMN contacts_enc bytea NULL,
  39. ADD COLUMN request_uris_enc bytea NULL,
  40. ADD COLUMN jwks_uri_enc bytea NULL,
  41. ADD COLUMN software_id_enc bytea NULL,
  42. ADD COLUMN tls_client_auth_subject_dn_enc bytea NULL,
  43. ADD COLUMN tls_client_auth_cert_thumbprint_enc bytea NULL,
  44. ADD COLUMN tls_client_auth_subject_dn_index text NULL,
  45. ADD COLUMN tls_client_auth_cert_thumbprint_index text NULL;
  46. -- Mirrors ux_oauth_clients_tls_subject_dn / ux_oauth_clients_tls_cert_thumbprint
  47. -- on the blind-index columns; the legacy plaintext indexes stay in place.
  48. CREATE UNIQUE INDEX ux_oauth_clients_tls_subject_dn_index
  49. ON oauth_clients (tls_client_auth_subject_dn_index)
  50. WHERE tls_client_auth_subject_dn_index IS NOT NULL;
  51. CREATE UNIQUE INDEX ux_oauth_clients_tls_cert_thumbprint_index
  52. ON oauth_clients (tls_client_auth_cert_thumbprint_index)
  53. WHERE tls_client_auth_cert_thumbprint_index IS NOT NULL;
  54. -- (d) redirect_uris: encrypted URI ciphertext + keyed blind index per kind.
  55. ALTER TABLE redirect_uris
  56. ADD COLUMN uri_enc bytea NULL,
  57. ADD COLUMN uri_index text NULL;
  58. CREATE UNIQUE INDEX ux_redirect_uris_uri_index_kind
  59. ON redirect_uris (uri_index, kind)
  60. WHERE uri_index IS NOT NULL;