-- pg_vault_tde--1.7.sql -- Extension install script for pg_vault_tde v1.7 (consolidated) -- -- Copyright (c) 2026 Miriade S.r.l. — PostgreSQL License -- -- Fresh installation at v1.7. For upgrades from older versions, use the -- ALTER EXTENSION UPDATE path (scripts pg_vault_tde--1.0--1.4.sql, etc.). -- ============================================================================ -- Superuser guard -- ============================================================================ DO $$ BEGIN IF NOT (SELECT rolsuper FROM pg_catalog.pg_roles WHERE rolname = current_user) THEN RAISE EXCEPTION 'pg_vault_tde requires superuser privileges to install'; END IF; END; $$; -- ============================================================================ -- Access method handler functions (must precede CREATE ACCESS METHOD) -- ============================================================================ CREATE FUNCTION pg_vault_tde_tableam_handler(internal) RETURNS table_am_handler LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_tableam_handler'; CREATE FUNCTION pg_vault_tde_iam_handler(internal) RETURNS index_am_handler LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_iam_handler'; -- ============================================================================ -- Access methods -- ============================================================================ CREATE ACCESS METHOD encrypted_heap TYPE TABLE HANDLER pg_vault_tde_tableam_handler; CREATE ACCESS METHOD tde_btree TYPE INDEX HANDLER pg_vault_tde_iam_handler; COMMENT ON ACCESS METHOD encrypted_heap IS 'pg_vault_tde Table Access Method: AES-256-GCM transparent encryption via Vault/OpenBao'; COMMENT ON ACCESS METHOD tde_btree IS 'pg_vault_tde Index Access Method: AES-256-SIV deterministic encryption for B-Tree indexes'; -- ============================================================================ -- v1.5 plaintext operator classes for tde_btree -- -- bytea, text, numeric remain DEFAULT. -- int4, int8, uuid, date, timestamptz are kept for backwards compatibility -- but are NOT DEFAULT — the v1.7 enc_ops classes are the defaults for those. -- ============================================================================ CREATE OPERATOR CLASS tde_bytea_ops DEFAULT FOR TYPE bytea USING tde_btree AS OPERATOR 1 < (bytea, bytea), OPERATOR 2 <= (bytea, bytea), OPERATOR 3 = (bytea, bytea), OPERATOR 4 >= (bytea, bytea), OPERATOR 5 > (bytea, bytea), FUNCTION 1 byteacmp(bytea, bytea); CREATE OPERATOR CLASS tde_text_ops DEFAULT FOR TYPE text USING tde_btree AS OPERATOR 1 < (text, text), OPERATOR 2 <= (text, text), OPERATOR 3 = (text, text), OPERATOR 4 >= (text, text), OPERATOR 5 > (text, text), FUNCTION 1 bttextcmp(text, text); -- NOT DEFAULT: superseded by tde_int4_enc_ops in v1.7 CREATE OPERATOR CLASS tde_int4_ops FOR TYPE int4 USING tde_btree AS OPERATOR 1 < (int4, int4), OPERATOR 2 <= (int4, int4), OPERATOR 3 = (int4, int4), OPERATOR 4 >= (int4, int4), OPERATOR 5 > (int4, int4), FUNCTION 1 btint4cmp(int4, int4); -- NOT DEFAULT: superseded by tde_int8_enc_ops in v1.7 CREATE OPERATOR CLASS tde_int8_ops FOR TYPE int8 USING tde_btree AS OPERATOR 1 < (int8, int8), OPERATOR 2 <= (int8, int8), OPERATOR 3 = (int8, int8), OPERATOR 4 >= (int8, int8), OPERATOR 5 > (int8, int8), FUNCTION 1 btint8cmp(int8, int8); -- NOT DEFAULT: superseded by tde_uuid_enc_ops in v1.7 CREATE OPERATOR CLASS tde_uuid_ops FOR TYPE uuid USING tde_btree AS OPERATOR 1 < (uuid, uuid), OPERATOR 2 <= (uuid, uuid), OPERATOR 3 = (uuid, uuid), OPERATOR 4 >= (uuid, uuid), OPERATOR 5 > (uuid, uuid), FUNCTION 1 uuid_cmp(uuid, uuid); CREATE OPERATOR CLASS tde_numeric_ops DEFAULT FOR TYPE numeric USING tde_btree AS OPERATOR 1 < (numeric, numeric), OPERATOR 2 <= (numeric, numeric), OPERATOR 3 = (numeric, numeric), OPERATOR 4 >= (numeric, numeric), OPERATOR 5 > (numeric, numeric), FUNCTION 1 numeric_cmp(numeric, numeric); -- NOT DEFAULT: superseded by tde_date_enc_ops in v1.7 CREATE OPERATOR CLASS tde_date_ops FOR TYPE date USING tde_btree AS OPERATOR 1 < (date, date), OPERATOR 2 <= (date, date), OPERATOR 3 = (date, date), OPERATOR 4 >= (date, date), OPERATOR 5 > (date, date), FUNCTION 1 date_cmp(date, date); -- NOT DEFAULT: superseded by tde_timestamptz_enc_ops in v1.7 CREATE OPERATOR CLASS tde_timestamptz_ops FOR TYPE timestamptz USING tde_btree AS OPERATOR 1 < (timestamptz, timestamptz), OPERATOR 2 <= (timestamptz, timestamptz), OPERATOR 3 = (timestamptz, timestamptz), OPERATOR 4 >= (timestamptz, timestamptz), OPERATOR 5 > (timestamptz, timestamptz), FUNCTION 1 timestamptz_cmp(timestamptz, timestamptz); -- ============================================================================ -- v1.7 enc_ops: AES-256-SIV encrypted-key operator classes (STORAGE bytea) -- ============================================================================ -- tde_enc_bytea_cmp: three-way memcmp comparator for SIV ciphertexts stored -- as bytea. Used as btree support function 1 for all enc_ops classes. CREATE FUNCTION tde_enc_bytea_cmp(bytea, bytea) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; COMMENT ON FUNCTION tde_enc_bytea_cmp(bytea, bytea) IS 'Three-way comparator for AES-256-SIV encrypted index keys stored as bytea. ' 'Used as btree support function 1 for tde_*_enc_ops operator classes. ' 'Ordering is memcmp-based and has no semantic meaning; equality is preserved.'; -- Per-type SQL wrappers: index_getprocinfo looks up support function 1 using -- opcintype (e.g. int4) as both lefttype/righttype — NOT the STORAGE type. CREATE FUNCTION tde_int4_enc_cmp(int4, int4) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; CREATE FUNCTION tde_int8_enc_cmp(int8, int8) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; CREATE FUNCTION tde_uuid_enc_cmp(uuid, uuid) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; CREATE FUNCTION tde_date_enc_cmp(date, date) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; CREATE FUNCTION tde_timestamptz_enc_cmp(timestamptz, timestamptz) RETURNS integer LANGUAGE C STRICT IMMUTABLE AS 'MODULE_PATHNAME', 'tde_enc_bytea_cmp'; CREATE OPERATOR FAMILY tde_enc_ops_family USING tde_btree; ALTER OPERATOR FAMILY tde_enc_ops_family USING tde_btree ADD FUNCTION 1 (int4, int4) tde_int4_enc_cmp(int4, int4); ALTER OPERATOR FAMILY tde_enc_ops_family USING tde_btree ADD FUNCTION 1 (int8, int8) tde_int8_enc_cmp(int8, int8); ALTER OPERATOR FAMILY tde_enc_ops_family USING tde_btree ADD FUNCTION 1 (uuid, uuid) tde_uuid_enc_cmp(uuid, uuid); ALTER OPERATOR FAMILY tde_enc_ops_family USING tde_btree ADD FUNCTION 1 (date, date) tde_date_enc_cmp(date, date); ALTER OPERATOR FAMILY tde_enc_ops_family USING tde_btree ADD FUNCTION 1 (timestamptz, timestamptz) tde_timestamptz_enc_cmp(timestamptz, timestamptz); CREATE OPERATOR CLASS tde_int4_enc_ops DEFAULT FOR TYPE int4 USING tde_btree FAMILY tde_enc_ops_family AS OPERATOR 3 = (int4, int4), STORAGE bytea; CREATE OPERATOR CLASS tde_int8_enc_ops DEFAULT FOR TYPE int8 USING tde_btree FAMILY tde_enc_ops_family AS OPERATOR 3 = (int8, int8), STORAGE bytea; CREATE OPERATOR CLASS tde_uuid_enc_ops DEFAULT FOR TYPE uuid USING tde_btree FAMILY tde_enc_ops_family AS OPERATOR 3 = (uuid, uuid), STORAGE bytea; CREATE OPERATOR CLASS tde_date_enc_ops DEFAULT FOR TYPE date USING tde_btree FAMILY tde_enc_ops_family AS OPERATOR 3 = (date, date), STORAGE bytea; CREATE OPERATOR CLASS tde_timestamptz_enc_ops DEFAULT FOR TYPE timestamptz USING tde_btree FAMILY tde_enc_ops_family AS OPERATOR 3 = (timestamptz, timestamptz), STORAGE bytea; -- ============================================================================ -- C functions: KMS status and token management -- ============================================================================ -- KMS diagnostic status: returns a text summary of DEK cache state CREATE FUNCTION pg_vault_tde_vault_status() RETURNS TABLE ( configured boolean, auth_method text, reachable boolean ) LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_vault_status'; -- Manually renew the current Vault token lease CREATE FUNCTION pg_vault_tde_refresh_token() RETURNS boolean LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_refresh_token'; -- ============================================================================ -- C functions: table re-encryption -- ============================================================================ -- Re-encrypt all rows in an encrypted_heap table with the current DEK. CREATE FUNCTION pg_vault_tde_reencrypt_table( rel regclass, batch_size integer DEFAULT 1000 ) RETURNS void LANGUAGE C AS 'MODULE_PATHNAME', 'pg_vault_tde_reencrypt_table_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_reencrypt_table(regclass, int) FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_vault_tde_reencrypt_table(regclass, int) TO pg_monitor; -- Text-typed overload for callers that pass a table name as text CREATE FUNCTION pg_vault_tde_reencrypt_table( rel text, batch_size int DEFAULT 1000 ) RETURNS void LANGUAGE SQL SECURITY DEFINER AS $$ SELECT pg_vault_tde_reencrypt_table(rel::regclass, batch_size); $$; REVOKE ALL ON FUNCTION pg_vault_tde_reencrypt_table(text, int) FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_vault_tde_reencrypt_table(text, int) TO pg_monitor; -- ============================================================================ -- C functions: verification and diagnostics -- ============================================================================ -- Verify GCM authentication tags; returns (total_tuples, failed_tuples) CREATE FUNCTION pg_vault_tde_verify_integrity( rel regclass, OUT total_tuples bigint, OUT failed_tuples bigint ) RETURNS record LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_verify_integrity'; -- Report encryption storage overhead; returns (total_tuples, overhead_bytes) CREATE FUNCTION pg_vault_tde_encrypted_size( rel regclass, OUT total_tuples bigint, OUT encryption_overhead_bytes bigint ) RETURNS record LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_encrypted_size'; -- Hardware acceleration diagnostics CREATE FUNCTION pg_vault_tde_hw_accel_info( OUT openssl_version text, OUT configured_provider text, OUT provider_loaded boolean, OUT gcm_cipher text, OUT siv_cipher text, OUT aes_ni_available boolean ) RETURNS record LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_hw_accel_info'; -- ============================================================================ -- Per-table DEK catalog -- ============================================================================ -- Stores the wrapped DEK for each encrypted_heap relation. The in-memory -- shmem cache (TdeRelDekCache) is authoritative at runtime; this catalog is -- the source of truth for DEK restoration after server restart. CREATE TABLE IF NOT EXISTS pg_vault_tde_catalog ( relid oid NOT NULL, -- pg_class.oid generation bigint NOT NULL DEFAULT 1, -- DEK rotation epoch wrapped_dek bytea, -- provider-opaque wrapped DEK bytes kms_provider text NOT NULL DEFAULT 'vault', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT pg_vault_tde_catalog_pkey PRIMARY KEY (relid) ); REVOKE ALL ON TABLE pg_vault_tde_catalog FROM PUBLIC; -- ============================================================================ -- Online key rotation progress catalog -- ============================================================================ CREATE TABLE IF NOT EXISTS pg_vault_tde_rotation_progress ( relid oid NOT NULL, status text NOT NULL DEFAULT 'running', tuples_done bigint NOT NULL DEFAULT 0, tuples_total bigint, started_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT pg_vault_tde_rotation_progress_pkey PRIMARY KEY (relid) ); REVOKE ALL ON TABLE pg_vault_tde_rotation_progress FROM PUBLIC; -- Monitoring view (readable by pg_monitor role) CREATE OR REPLACE VIEW pg_vault_tde_rotation_status AS SELECT c.relname AS table_name, r.relid, r.status, r.tuples_done, r.tuples_total, CASE WHEN r.tuples_total IS NULL OR r.tuples_total = 0 THEN NULL ELSE ROUND(100.0 * r.tuples_done / r.tuples_total, 1) END AS pct_complete, r.started_at, r.updated_at FROM pg_vault_tde_rotation_progress r JOIN pg_catalog.pg_class c ON c.oid = r.relid; GRANT SELECT ON pg_vault_tde_rotation_status TO pg_monitor; -- ============================================================================ -- Local Wallet SQL functions -- ============================================================================ CREATE FUNCTION pg_vault_tde_wallet_init(passphrase text) RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_wallet_init_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_wallet_init(text) FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_vault_tde_wallet_init(text) TO pg_monitor; COMMENT ON FUNCTION pg_vault_tde_wallet_init(text) IS 'Initialize a new local PKCS#12 TDE wallet. The passphrase MUST match the ' 'environment variable named by pg_vault_tde.wallet_passphrase_env on ' 'subsequent server restarts.'; -- v1.6 version: 6-column wallet status CREATE FUNCTION pg_vault_tde_wallet_status() RETURNS TABLE ( wallet_exists bool, wallet_open bool, kek_algorithm text, last_opened timestamptz, file_perms text ) LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_wallet_status_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_wallet_status() FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_vault_tde_wallet_status() TO pg_monitor; COMMENT ON FUNCTION pg_vault_tde_wallet_status() IS 'Return wallet diagnostics: existence, open/locked state, key algorithm, ' 'number of cached per-table DEKs, last-unlock timestamp, and file permissions.'; -- Re-wraps all DEKs under a new passphrase without touching encrypted data CREATE FUNCTION pg_vault_tde_wallet_change_passphrase(old_passphrase text, new_passphrase text) RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_wallet_change_passphrase_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_wallet_change_passphrase(text, text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_wallet_change_passphrase(text, text) IS 'Re-protect the wallet and all per-table DEKs under a new passphrase. ' 'Verifies the old passphrase first; then atomically re-wraps every catalog ' 'entry and writes a new PKCS#12 wallet file.'; -- Verify passphrase and flush DEK cache CREATE FUNCTION pg_vault_tde_wallet_unlock(passphrase text) RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_wallet_unlock_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_wallet_unlock(text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_wallet_unlock(text) IS 'Verify the supplied passphrase opens the PKCS#12 wallet, then flush the ' 'per-table DEK cache in shared memory so every backend reloads keys through ' 'the full unwrap path. Marks the wallet as open.'; -- Flush DEK cache, mark wallet locked CREATE FUNCTION pg_vault_tde_wallet_lock() RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_wallet_lock_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_wallet_lock() FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_wallet_lock() IS 'Flush all plaintext DEK material from shared memory without restarting the ' 'server. After this call, any access to an encrypted table will attempt an ' 'unwrap — which will fail if no passphrase source is configured.'; -- Migrate all Vault-wrapped DEKs to a new local PKCS#12 wallet CREATE FUNCTION pg_vault_tde_migrate_vault_to_wallet(new_passphrase text) RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_migrate_vault_to_wallet_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_migrate_vault_to_wallet(text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_migrate_vault_to_wallet(text) IS 'Re-wrap every per-table DEK that is currently protected by the Vault KMS ' 'provider under a new local PKCS#12 wallet with new_passphrase. Updates ' 'kms_provider = ''local'' for each migrated row. The local wallet must ' 'already exist (call pg_vault_tde_wallet_init() first).'; -- ============================================================================ -- Physical backup key sealing -- ============================================================================ -- Seal all wrapped DEKs (any provider) into an HMAC-signed bundle to accompany -- a pg_basebackup. The KEK is NOT included: it stays in the KMS/wallet and is -- provisioned on the standby separately. CREATE FUNCTION pg_vault_tde_seal_keys(dest_path text, seal_passphrase text, label text DEFAULT 'basebackup') RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_seal_keys_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_seal_keys(text, text, text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_seal_keys(text, text, text) IS 'Write an HMAC-SHA256-signed bundle of all wrapped DEKs (every kms_provider) ' 'to dest_path, to accompany a physical backup (pg_basebackup). The HMAC key ' 'is derived from seal_passphrase via PBKDF2-SHA256. The KEK is never included. ' 'dest_path is written with 0600 permissions.'; -- Same bundle as pg_vault_tde_seal_keys, returned as bytea instead of being -- written server-side. Lets a remote client (pg_basebackup_tde) store the -- bundle next to the backup on the client host. CREATE FUNCTION pg_vault_tde_seal_keys_bytea(seal_passphrase text, label text DEFAULT 'basebackup') RETURNS bytea LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_seal_keys_bytea_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_seal_keys_bytea(text, text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_seal_keys_bytea(text, text) IS 'Return an HMAC-SHA256-signed bundle of all wrapped DEKs (every kms_provider) ' 'as bytea, to accompany a physical backup (pg_basebackup). Same format as ' 'pg_vault_tde_seal_keys(); the client is responsible for storing the bytes. ' 'The KEK is never included.'; -- unseal the wrapped dek bundle CREATE FUNCTION pg_vault_tde_unseal_keys(src_path text, seal_passphrase text) RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_unseal_keys_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_unseal_keys(text, text) FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_unseal_keys(text, text) IS 'Verify and re-import a wrapped-DEK bundle created by pg_vault_tde_seal_keys(). ' 'Checks the HMAC trailer with seal_passphrase before writing anything; on ' 'success UPSERTs the catalog rows and evicts the shmem cache.'; -- ============================================================================ -- PKCS#11 / HSM SQL functions (v1.7) -- ============================================================================ -- One-time KEK provisioning on the PKCS#11 token CREATE FUNCTION pg_vault_tde_pkcs11_keygen() RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_pkcs11_keygen_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_pkcs11_keygen() FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_pkcs11_keygen() IS 'Generate the AES-256 KEK on the PKCS#11 token (CKA_SENSITIVE, ' 'CKA_EXTRACTABLE=FALSE, CKA_WRAP/CKA_UNWRAP) under the label ' 'pg_vault_tde.pkcs11_key_label. Fails if a key with that label already ' 'exists. Requires pg_vault_tde.kms_provider = ''pkcs11''.'; -- ============================================================================ -- Online key rotation -- ============================================================================ -- Spawn a BGW that re-encrypts rel in batches without AccessExclusiveLock CREATE FUNCTION pg_vault_tde_rotate_online( rel regclass, batch_size int DEFAULT 1000 ) RETURNS void LANGUAGE C CALLED ON NULL INPUT AS 'MODULE_PATHNAME', 'pg_vault_tde_rotate_online_sql'; -- Query rotation progress for a given relation CREATE FUNCTION pg_vault_tde_get_rotation_status(rel regclass) RETURNS TABLE ( status text, tuples_done bigint, tuples_total bigint, pct_complete numeric, started_at timestamptz, updated_at timestamptz ) LANGUAGE SQL STABLE AS $$ SELECT status, tuples_done, tuples_total, CASE WHEN tuples_total IS NULL OR tuples_total = 0 THEN NULL ELSE ROUND(100.0 * tuples_done / tuples_total, 1) END, started_at, updated_at FROM pg_vault_tde_rotation_progress WHERE relid = $1::oid; $$; -- ============================================================================ -- Health check (v1.7) -- ============================================================================ CREATE FUNCTION pg_vault_tde_build_version() RETURNS text LANGUAGE C STRICT AS 'MODULE_PATHNAME', 'pg_vault_tde_build_version'; COMMENT ON FUNCTION pg_vault_tde_build_version() IS 'Compiled-in build version (from the VERSION file at build time), ' 'independent of pg_extension.extversion. Distinguishes binary builds ' 'that share the same extversion (e.g. a C-only bugfix with no SQL ' 'script change).'; CREATE FUNCTION pg_vault_tde_health_check() RETURNS TABLE ( version text, build_version text, enabled bool, kms_provider text, enc_ops_available bool, checked_at timestamptz ) LANGUAGE SQL STABLE AS $$ SELECT (SELECT extversion FROM pg_extension WHERE extname='pg_vault_tde') AS version, pg_vault_tde_build_version() AS build_version, current_setting('pg_vault_tde.enabled', true)::bool AS enabled, current_setting('pg_vault_tde.kms_provider', true) AS kms_provider, EXISTS ( SELECT 1 FROM pg_opclass WHERE opcname = 'tde_int4_enc_ops' AND opcmethod = (SELECT oid FROM pg_am WHERE amname = 'tde_btree') ) AS enc_ops_available, now() AS checked_at; $$; COMMENT ON FUNCTION pg_vault_tde_health_check() IS 'Return a single-row diagnostic snapshot of the pg_vault_tde extension. ' 'Columns: version, build_version, enabled, kms_provider, enc_ops_available ' '(true when tde_*_enc_ops operator classes are installed), checked_at.'; -- ============================================================================ -- KEK rotation -- ============================================================================ -- Re-wrap all per-table DEKs under a new KEK without touching encrypted data CREATE FUNCTION pg_vault_tde_rotate_kek() RETURNS void LANGUAGE C STRICT SECURITY DEFINER AS 'MODULE_PATHNAME', 'pg_vault_tde_rotate_kek_sql'; REVOKE ALL ON FUNCTION pg_vault_tde_rotate_kek() FROM PUBLIC; COMMENT ON FUNCTION pg_vault_tde_rotate_kek() IS 'Re-wrap all per-table DEKs under a new KEK without touching encrypted tuple data. ' 'For the local wallet provider: generates a fresh random KEK and rewrites the wallet file. ' 'For the Vault provider: rotates the Transit key and re-wraps all DEKs. ' 'For the PKCS#11 provider: regenerate KEK on token with a new version