Contents
- pg_vault_tde Roadmap
- Completed Releases (Summary)
- v1.1 — KMS / Vault + Key Rotation + TOAST + HW Accel — COMPLETED ✅
- v1.2 — Logical Decoding Compatibility — COMPLETED ✅
- v1.3 — Vault Transit KEK + multi_insert + BGW + health_check — COMPLETED ✅
- v1.4 — CI/CD + tde_btree + Wire Format v2 — COMPLETED ✅
- v1.5 — Per-Table DEK + Online Rotation + Wire Format v3 AAD — COMPLETED ✅
- v1.6 — Local Wallet KMS — Production-Ready Offline Encryption — COMPLETED ✅
- v1.7 — TOAST Chunks + KEK Hierarchy + HSM + Audit
- 1. TOAST Chunk-Level Storage Encryption (foundation — shipped in v1.6)
- 2. Proper KEK/DEK Wrapping Hierarchy (Critical)
- 3. tde_btree Fixed-Size Type Encryption — ✅ Completed in v1.7
- 4. Logical Replication of TOAST Columns (Medium) — COMPLETED ✅
- 5. PKCS#11 / HSM Integration (Critical) — COMPLETED ✅
- 6. Audit Trail / Event Log (Critical) — COMPLETED ✅
- 7. pg_dump Plaintext Leak Protection (Medium) — NOT completed, moved to v1.8
- 8. Physical Backup Key Sealing / pg_restore_tde (Medium) — COMPLETED ✅
- v1.8 — KMIP + Column-Level + GIN/Hash/GiST/BRIN + HA + Dual-Control (Q2 2027)
- 1. Column-Level Encryption (High)
- 2. GIN Index Encryption (Medium)
- 3. Hash Index Encryption (Low Effort)
- 4. pg_statistic Plaintext Mitigation (Low)
- 5. KMIP 1.2 Client (Enterprise)
- 6. GiST Equality-Only Encryption (Medium)
- 7. Streaming Replication Standby DEK Distribution (Medium)
- 8. Dual-Control / M-of-N Key Ceremony (High)
- 9. BRIN Bloom Equality Encryption (Medium — new candidate, needs a spike)
- 10. pg_dump Plaintext Leak Protection (Medium — carried over from v1.7, never implemented)
- Permanent Deferrals
- Version Summary
- Completed Releases (Summary)
pg_vault_tde Roadmap
Last updated: 2026-06-29 — v1.7 current. 109 regression tests (52 v1.4 + 20 v1.5 + 37 v1.6) carried forward, plus the
tap/12_logical_repl_toast.tend-to-end logical replication test. Key v1.7 changes: all KMS GUCs promoted to PGC_SUSET (per-database KMS viaALTER DATABASE SET);pg_restore_tdedecrypt-and-pipe loop completed; logical replication ofencrypted_heapTOAST columns via a custom WAL resource manager (pg_vault_tde.toast_custom_rmgr); documentation updated throughout.
Completed Releases (Summary)
v1.1 — KMS / Vault + Key Rotation + TOAST + HW Accel — COMPLETED ✅
41 regression tests — PG 17 + PG 18, zero compiler warnings.
Vault HTTP connector (libcurl async, Transit API), AppRole + K8s JWT auth, graceful key rotation
with prev_dek fallback, pg_vault_tde_reencrypt_table(), TOAST pre-TOAST fix, PG 17/18/19
build infrastructure, hardware acceleration (OpenSSL 3.x QAT/FIPS/default provider), tde_btree
IAM stubs, tests 1–43.
v1.2 — Logical Decoding Compatibility — COMPLETED ✅
Custom output plugin pg_vault_tde_pgoutput — intercepts change_cb, decrypts encrypted_heap
tuples in-place. Test 48. Known limitation: externally-TOAST’d columns not supported.
v1.3 — Vault Transit KEK + multi_insert + BGW + health_check — COMPLETED ✅
48 regression tests — PG 17 + PG 18, zero compiler warnings.
Vault Transit KEK wrapping (wrapped DEK persisted to $PGDATA), multi_insert batching
(3-phase pre-TOAST+encrypt), background worker for token renewal, pg_vault_tde_health_check()
14-column composite. Tests 44–48.
v1.4 — CI/CD + tde_btree + Wire Format v2 — COMPLETED ✅
52 regression tests — PG 17 + PG 18, zero compiler warnings.
CI benchmark pipeline (run-bench.sh), OpenBao 3-node Raft integration (12 tests),
wire format v2 with generation tag, tde_btree full wiring (ambuild/aminsert/amrescan),
security hardening (file permissions, secret_id rotation, token TTL logging). Tests 49–52.
v1.5 — Per-Table DEK + Online Rotation + Wire Format v3 AAD — COMPLETED ✅
72 regression tests (52 v1.4 + 20 new) — PG 17 + PG 18, zero compiler warnings.
Per-table DEK catalog (pg_vault_tde_catalog), KMS provider abstraction layer
(pg_vault_tde_kms_provider.h), TOAST heap-level round-trips, tde_btree native type
operator classes (text/int4/uuid/numeric/date/timestamptz), wire format v3 AEAD AAD binding
(cross-table paste attack prevention), online key rotation BGW (pg_vault_tde_rotate_online),
AppRole response-wrapping. Wallet SQL stubs registered (not functional). Tests 53–72.
v1.6 — Local Wallet KMS — Production-Ready Offline Encryption — COMPLETED ✅
Completed: 2026-06-03, patched 2026-05-08 — 109 regression tests (52 v1.4 + 20 v1.5 + 37 v1.6) — PG 17 + PG 18, zero compiler warnings.
Local Wallet KMS provider (src/kms/pg_vault_tde_kms_local.c) — full PKCS#12 / AES-256-WRAP
implementation, PBKDF2-SHA256 (600,000 iterations, NIST SP 800-132), 0600 wallet file
permissions. Flexible passphrase ingestion via GUCs (env var, file, shell command, dev-mode
convenience; priority command > env > file > dev_mode) plus SQL wallet_unlock/wallet_lock
for interactive control without a restart. pg_vault_tde_wallet_status() 6-column SRF.
KEK rotation and passphrase change re-wrap all DEKs atomically via SPI. Zero-downtime
pg_vault_tde_migrate_vault_to_wallet(). TOAST chunk-level storage encryption shipped here
as the foundation for v1.7’s logical-replication work. Patch fixed a write-path error-handling
gap (PG_TRY widened to cover the full write pipeline in all four write callbacks) and added
the RELKIND_TOASTVALUE read-path bypass so real TOAST chunks round-trip correctly. Tests 73–109.
The original wallet export/import bundle functions (
pg_vault_tde_wallet_export_bundle/_import_bundle) shipped in v1.6 were removed in v1.7, superseded bypg_vault_tde_seal_keys()/pg_vault_tde_unseal_keys().
v1.7 — TOAST Chunks + KEK Hierarchy + HSM + Audit
Status: ✅ Completed Target: ~100 regression tests — PG 17 + PG 18 + PG 19.
Theme: Close the TOAST data-leak gap, formalize the KEK/DEK wrap hierarchy across all providers, add PKCS#11/HSM support, audit trail for compliance (PCI-DSS, HIPAA).
1. TOAST Chunk-Level Storage Encryption (foundation — shipped in v1.6)
Per-chunk AES-256-GCM at the pg_toast_NNNNN storage layer using the parent
relation DEK (delivered in v1.6; listed here as the foundation the v1.7 logical
replication work in §4 builds on). Raw TOAST pages no longer contain plaintext.
2. Proper KEK/DEK Wrapping Hierarchy (Critical)
Provider-agnostic pg_vault_tde_kms_wrap_dek()/unwrap_dek() API. Vault Transit acts
as key protector (not key store) — raw DEK never sent to Vault, only wrapped ciphertext.
pg_vault_tde_catalog.wrapped_dek authoritative for all providers.
3. tde_btree Fixed-Size Type Encryption — ✅ Completed in v1.7
Custom btree key serialisation layer for int4/int8/uuid/date/timestamptz.
All operator classes now store encrypted index keys. Index-only scans are disabled
by design to prevent returning raw AES-256-SIV ciphertext to clients.
4. Logical Replication of TOAST Columns (Medium) — COMPLETED ✅
Custom WAL resource manager (pg_vault_tde.toast_custom_rmgr, PGC_POSTMASTER,
default off): tde_toast_wal_insert() logs encrypted TOAST chunks under
TDE_RMGR_ID so the logical decoder routes them away from the reorder buffer’s
toast_hash; rm_decode captures them per transaction and tde_toast_stitch()
reconstructs the plaintext value into the decrypted main tuple before pgoutput
serializes it. UPDATE/DELETE require REPLICA IDENTITY FULL + a primary key
(DEFAULT / PK-less unsupported — the replica identity would be read from
ciphertext). Covered end-to-end by tap/12_logical_repl_toast.t.
See doc/pg_vault_tde.md → “Logical Decoding and Replication”.
5. PKCS#11 / HSM Integration (Critical) — COMPLETED ✅
src/kms/pg_vault_tde_kms_pkcs11.c — direct Cryptoki: the vendor module is
dlopen()ed and DEKs are wrapped with C_WrapKey/C_UnwrapKey
(CKM_AES_KEY_WRAP, AES-256 KEK with CKA_EXTRACTABLE=FALSE). The
originally-planned OpenSSL 3.x pkcs11-provider route was evaluated and
discarded: symmetric key wrap with an opaque token key is not expressible
through EVP (would force an RSA KEK), and the pkcs11-provider package is
missing/outdated on the DEB targets. OASIS v3.2 headers vendored under
src/include/pkcs11/. KEK provisioning via pg_vault_tde_pkcs11_keygen();
rotation via the standard pg_vault_tde_rotate_kek(). Every KEK generation
is an immutable token object labelled <pkcs11_key_label>.v<N> (N never
reused, never renamed or destroyed); “current” is simply the highest N on
the token, and every wrapped_dek blob is prefixed with the version tag
of the KEK that produced it, so unwrap always finds the right key
regardless of what is “current” — including across a crash mid-rotation.
GUCs: pkcs11_library, pkcs11_token_label, pkcs11_slot_id,
pkcs11_pin_env, pkcs11_key_label. CI with SoftHSM2
(tap/16_pkcs11.t, 19 assertions, make ci-pkcs11). Follow-up: pg_dump_tde/
pg_restore_tde FRONTEND shim (they currently error out cleanly).
Cross-backend KEK-rotation propagation: a shared-memory beacon
(Pkcs11SharedState: one LWLock + a uint32 current_kek_version,
mapped via pg_vault_tde_kms_pkcs11_shmem_request/_shmem_init, same
dynamic-tranche pattern as the Vault token cache) lets an already-connected
backend pick up a KEK rotation committed by a different connection
without reconnecting. The raw CK_OBJECT_HANDLE is never shared across
processes (PKCS#11 handles are only meaningful within the session that
resolved them) — only the version number is; each backend re-resolves its
own handle locally via pkcs11_find_key_by_label(). Written only from
pkcs11_commit_kek_rotation() and the initial keygen (never from
prepare_kek_rotation, to avoid leaking an armed-but-uncommitted rotation
cluster-wide); read opportunistically on every wrap/unwrap/rewrap call via
pkcs11_refresh_kek_if_stale(), so staleness is bounded by “this backend’s
next operation”, not wall-clock time.
6. Audit Trail / Event Log (Critical) — COMPLETED ✅
src/audit/pg_vault_tde_audit.c — 10 event types (KEY_ROTATION, DEK_ACCESS,
INTEGRITY_VIOLATION, WALLET_OPEN, etc.). pg_vault_tde_audit_log encrypted table.
PCI-DSS Requirement 10 / HIPAA §164.312(b).
7. pg_dump Plaintext Leak Protection (Medium) — NOT completed, moved to v1.8
Designed (see src/backup/pg_vault_tde_backup.c header comment, “Layer 2 — SQL-LEVEL
GUARD”): ProcessUtility_hook would intercept COPY TO on encrypted tables and emit a
WARNING, gated by GUC pg_vault_tde.dump_plaintext_warning. Neither the hook nor the GUC
exist in code yet — tracked as v1.8 §10 below.
8. Physical Backup Key Sealing / pg_restore_tde (Medium) — COMPLETED ✅
pg_restore_tde standalone binary (src/backup/pg_restore_tde.c): reads the
tde_backup_header, unwraps the DEK via the active KMS provider
(tde_backup_header_validate()), decrypts the AES-256-GCM block stream
(tde_backup_decrypt_block() with block_seq as AAD), and pipes plaintext to
pg_restore -Fc.
pg_vault_tde_seal_keys()/pg_vault_tde_seal_keys_bytea()/pg_vault_tde_unseal_keys() (src/kms/pg_vault_tde_seal.c) — signed bundle of all wrapped_dek entries (KEK excluded), for pg_basebackup; TAP tap/14_seal_keys.t.
pg_basebackup_tde (src/backup/pg_basebackup_tde.c) — pg_basebackup wrapper: seals every database’s keys via seal_keys_bytea before the backup and writes one pg_vault_tde_keys.<datname>.sealed bundle per database after it succeeds; TAP tap/15_basebackup_tde.t.
A core-side BackupState/bbsink hook was evaluated and discarded: PostgreSQL exposes no extension hook to inject files into the pg_basebackup stream, and a custom bbsink runs in the walsender without SPI.
v1.8 — KMIP + Column-Level + GIN/Hash/GiST/BRIN + HA + Dual-Control (Q2 2027)
Status: 📋 Defined Target: ~130 regression tests.
Theme: Enterprise HA, KMIP standards compliance, column-level encryption, regulated-industry features.
1. Column-Level Encryption (High)
ALTER TABLE ... ENABLE/DISABLE COLUMN ENCRYPTION DDL. Per-column DEK support.
pg_vault_tde_columns catalog. src/tam/pg_vault_tde_column.c.
Feasibility (verified against the current TAM architecture, see
tam.instructions.md): encrypted_heap today encrypts the whole tuple as
one opaque AES-256-GCM blob (tde_encrypt_heap_tuple, wire format v4) —
there is no per-Datum boundary. Column-level encryption needs the write
path to operate around heap_deform_tuple/heap_form_tuple for specific
attributes instead of the raw tuple bytes:
- Varlena columns (text, bytea, jsonb, numeric, arrays):
straightforward — store [IV|ciphertext|GCM-tag] as the Datum’s own
varlena payload, the same shape already used at the tuple level, just
scoped to one attribute. No storage-layout change needed.
- Fixed-size columns (int4, int8, date, timestamptz, …):
AES-256-GCM’s IV+tag overhead does not fit the type’s fixed storage
width. Either (a) reuse tde_btree’s AES-256-SIV scheme — deterministic,
same output length as input, same security trade-off already accepted
for index keys (no protection against frequency analysis) — or (b)
widen physical storage (bigger lift: a pseudo-type or forced
bytea-backed column; likely out of scope for a first cut).
- Query pushdown: WHERE col = ... on an encrypted column needs the
same encrypt-then-compare trick tde_btree already implements for an
index to be usable; without a matching index it falls back to sequential
scan + per-Datum decrypt (same cost model as today’s whole-row decrypt,
just narrower).
- Two distinct feature shapes to choose between: (a) column encryption
as an additional layer inside encrypted_heap — a specific sensitive
column (SSN, card number) gets its own DEK/rotation/audit trail
independent of the table DEK, for defense-in-depth or per-column access
control; (b) column encryption on an ordinary heap table, without
switching the whole table to encrypted_heap — a lighter-weight opt-in
for one or two sensitive columns. (a) reuses most of the existing TAM
plumbing; (b) needs a new, narrower write/read hook that does not exist
anywhere in the codebase today.
2. GIN Index Encryption (Medium)
src/iam/pg_vault_tde_gin.c — per-entry AES-256-SIV. Equality operators only
(@>, ?, &&). Phrase search permanently rejected by amvalidate.
Feasibility: same delegation pattern already proven by tde_btree (see
iam.instructions.md — amgettuple/amendscan/ambulkdelete/
amvacuumcleanup delegate unchanged to the real AM; only the key
boundary is intercepted). GIN’s entry tree needs a consistent comparator
for its internal structure, not a semantically meaningful order — encrypting
each key extracted by extractValue/extractQuery with AES-256-SIV before
handing it to GIN’s own entry-tree code preserves exactly that: equal
plaintexts still compare equal, and a stable (if arbitrary) ciphertext
byte-order is all GIN’s internals require. Lower risk than GiST (below)
precisely because GIN, like btree, has no semantic-distance requirement.
3. Hash Index Encryption (Low Effort)
src/iam/pg_vault_tde_hash.c — same AES-256-SIV pattern as tde_btree;
hash index buckets only need bucket-hash + exact equality, both of which
survive deterministic encryption unchanged. Same low-risk delegation
pattern as GIN above.
4. pg_statistic Plaintext Mitigation (Low)
Post-ANALYZE hook: NULL out stavalues for encrypted columns.
GUC pg_vault_tde.encrypt_statistics.
5. KMIP 1.2 Client (Enterprise)
src/kms/pg_vault_tde_kms_kmip.c — KMIP 1.2 over mutual TLS. CI with PyKMIP.
6. GiST Equality-Only Encryption (Medium)
src/iam/pg_vault_tde_gist.c — equality-only operator classes. amvalidate
rejects range/geometric strategies.
Feasibility, and why this is harder than GIN/Hash above: unlike btree/
GIN/Hash, GiST cannot delegate its tree-shaping support functions
(penalty, picksplit, union, distance) to the real opclass on
ciphertext — those functions encode actual geometric/semantic distance in
the plaintext domain, which AES-SIV ciphertext has none of by design (that
is the point of encryption). A working equality-only GiST needs genuinely
custom, non-delegated support functions that make no attempt at
selectivity (e.g. constant penalty, arbitrary picksplit) and rely entirely
on consistent for an exact ciphertext match — functionally correct, but
with materially worse pruning than a real GiST tree, closer in practice to
a linear scan over each visited page. Worth it specifically for types that
have no other native access method in PostgreSQL (point, circle,
box, inet with non-equality operators unused) — for anything with a
usable tde_btree or the GIN path above, prefer those instead.
7. Streaming Replication Standby DEK Distribution (Medium)
pg_vault_tde_replica_setup() — read-only KMS credentials for standby. HA
documentation for all KMS providers.
8. Dual-Control / M-of-N Key Ceremony (High)
pg_vault_tde_key_custody_info(). Vault Shamir + PKCS#11 PIN-split documentation.
9. BRIN Bloom Equality Encryption (Medium — new candidate, needs a spike)
src/iam/pg_vault_tde_brin.c. The Permanent Deferrals table below correctly
rules out minmax BRIN opclasses (ciphertext has no meaningful min/max) —
but PostgreSQL’s bloom BRIN opclasses (core since PG 14,
src/backend/access/brin/brin_bloom.c) only need a per-block-range Bloom
filter of value hashes, never an ordering. Since AES-256-SIV is
deterministic (equal plaintext → equal ciphertext, the same property
tde_btree already relies on), hashing the raw ciphertext bytes directly
(hash_any()) produces exactly the membership test a bloom filter needs —
no type-specific logic required at all, unlike tde_btree/GIN/GiST which
need per-type SIV encode/decode. A single generic “encrypted equality”
bloom opclass could work uniformly across every type this project already
supports, giving cheap block-range pruning for equality predicates on
large encrypted tables at a fraction of tde_btree’s storage cost.
Needs a short technical spike before committing engineering time: confirm
the BRIN opclass support-function contract (opcinfo/add_value/
consistent/union) can be satisfied purely on ciphertext bytes without
ever needing the plaintext inside the index AM.
10. pg_dump Plaintext Leak Protection (Medium — carried over from v1.7, never implemented)
ProcessUtility_hook intercepts COPY TO on encrypted tables — emits WARNING.
GUC pg_vault_tde.dump_plaintext_warning = on. Designed in v1.7 (see
src/backup/pg_vault_tde_backup.c header comment) but the hook and GUC were
never written; carried forward here as the actual target release.
Permanent Deferrals
These gaps cannot be closed without modifying PostgreSQL core.
| Gap | Reason |
|---|---|
| WAL / redo encryption | Requires hook in XLogInsert() / XLogWrite() — no extension API |
| BRIN minmax on encrypted columns | min/max of AES-SIV ciphertexts is meaningless — no ordering preserved. (Bloom-based BRIN equality pruning is not in this category — tracked as a real candidate, see v1.8 §9.) |
| General GiST (range, geometric) | Penalty/picksplit requires ordering; AES-SIV destroys it. (Equality-only GiST is not in this category — tracked separately, see v1.8 §6.) |
| pg_upgrade transparent migration | pg_upgrade copies files without TAM; manual reencrypt_table() required |
| Full-text phrase search on encrypted tsvector | <-> proximity requires positional ordering |
WITH HOLD cursor temp file encryption |
The held-cursor tuplestore is written by the executor’s storage layer directly, bypassing the TAM — no hook exists anywhere in the WITH HOLD cursor lifecycle to intercept it. See README.md § Limitations item 6. |
Version Summary
| Version | Theme | Completed | Tests | Key Features |
|---|---|---|---|---|
| v1.1 | KMS/Vault + Key Rotation + HW Accel | ✅ 2026 | 41 | Vault Transit, AppRole, prev_dek fallback, OpenSSL 3.x HW dispatch |
| v1.2 | Logical Decoding | ✅ 2026 | — | pg_vault_tde_pgoutput output plugin |
| v1.3 | Vault KEK + multi_insert + BGW | ✅ 2026 | 48 | Transit KEK wrapping, batch COPY, token renewal BGW, health_check |
| v1.4 | CI/CD + tde_btree + Wire Format v2 | ✅ 2026-07-05 | 52 | OpenBao 3-node Raft, ambuild/aminsert/amrescan, generation tag |
| v1.5 | Per-Table DEK + Online Rotation + AAD | ✅ 2026 | 72 | Per-table catalog, native type ops, wire format v3, rotate_online BGW |
| v1.6 | Local Wallet KMS (production-ready) + write-path / catalog bugfix patch | ✅ 2026-07-20 (patched 2026-05-08) | 109 | Wallet unlock/lock, passphrase flexibility, KEK rotation, export/import, Vault→wallet migration; PG_TRY widening; TOAST relid auto-registration; STORAGE EXTERNAL TAM read bypass; all-read-paths TOAST coverage; forensic helpers; tests 73–109 |
| v1.7 | Per-database KMS + pg_restore_tde + PGC_SUSET + PKCS#11 + HSM + v1.4 removal | ✅ Completed | 109 | All KMS GUCs PGC_SUSET → per-database KMS via ALTER DATABASE SET; pg_restore_tde full decrypt-and-pipe restore loop; removed v1.4 global-DEK backward compat (TdeShmemData, rotate_key, key_generation, clear_prev_dek, encrypt_test, decrypt_test); PKCS#11/HSM provider with cross-backend KEK-rotation propagation; documentation overhaul |
| v1.8 | KMIP + Column-Level + GIN/Hash/GiST/BRIN + HA + Dual-Control | Q2 2027 | ~130 | KMIP 1.2 client, per-column encryption, GIN/Hash/GiST(equality)/BRIN(bloom) index AMs, streaming replication standby DEK distribution, M-of-N key ceremony, pg_dump/COPY TO plaintext-leak WARNING (carried over from v1.7) |