Skip to main content
Applies to:
DuoKey Cockpit v2Percona Distribution for PostgreSQL 17.5+ (pg_tde 2.0)Binary KMIP (TTLV over mTLS)

Overview​

Percona Distribution for PostgreSQL ships pg_tde, an extension providing Transparent Data Encryption for tables and, optionally, the write-ahead log (WAL). pg_tde's KMIP key provider fetches a principal (master) key from an external KMIP server and uses it to wrap the internal table and WAL keys.

The DuoKey Cockpit is that KMIP server. pg_tde speaks binary KMIP — OASIS TTLV over mutual TLS — directly to the Cockpit's KMIP listener. The principal key is generated, sealed at rest, and audited centrally; the PostgreSQL host itself never stores the master key on disk.

Percona PostgreSQL to the DuoKey KMIP server
Percona PostgreSQLpg_tde extensiontde_heap tables + optional WAL encryption
KMIP (TTLV) over mutual TLS — port 5696
DuoKey KMIP serverCockpit's KMIP listener — Register / Get / Activate / Locate / Destroy
seal / unseal
Tenant vault / HSMPrincipal key sealed at rest — never exported to the database host

pg_tde's KMIP key provider talks directly to the Cockpit's KMIP listener; the principal key never leaves DuoKey custody.

Key hierarchy: Principal Key and Internal Keys​

pg_tde uses a two-tier key hierarchy, in Percona's own terms:

  • The Principal Key is the one key managed externally — by the DuoKey KMIP server, in this integration. pg_tde uses one principal key per database.
  • Every Internal Key is generated locally, one per encrypted database object (a table and each of its indexes each get their own internal key, keyed by object identifier), and is itself wrapped under the principal key. Internal keys — not the principal key — do the actual page-level encryption of table data.

Retrieval of the principal key from the KMIP provider is cached in PostgreSQL backend memory, so pg_tde calls out to the Cockpit only when the key is not already held locally — not on every table read or write. WAL encryption uses the same principal key but is switched on independently of table encryption, via its own server-wide setting; a WAL segment can contain both encrypted and unencrypted records depending on when that setting was toggled.

The most-proven integration in this catalog

Unlike the other engines in the Database Encryption catalog, this integration has a genuine, automated end-to-end proof: a real KMIP server, a real pg_tde client performing the actual Register/Get sequence over TTLV/mTLS, and an integration test that starts a live Percona 17.5 container, runs the full setup SQL, and greps the on-disk table file to confirm plaintext is absent from the encrypted table and present in a plaintext control table.

PropertyValue
Encryption modelEngine-native TDE (table + optional WAL)
Key transportKMIP (TTLV) over mutual TLS, port 5696
Key custodyPrincipal key sealed at rest in the DuoKey vault; never exported to the database host
OnboardingApp detail page (richer config surface: pooling, failover, replicas)
Proof statusEnd-to-end proven against a live Percona 17.5 / pg_tde 2.0 container

How pg_tde talks to the Cockpit​

pg_tde's KMIP client drives this exact sequence, validated against pg_tde 2.0:

1

Register

pg_tde_create_key_using_global_key_provider generates the principal key locally and Registers its bytes on the Cockpit's KMIP server. The Cockpit seals the material at rest and returns a KMIP unique identifier.

2

Get

pg_tde_set_key_using_global_key_provider Gets the key back from the Cockpit to activate it as the database's principal key.

3

Wrap and encrypt

pg_tde wraps its internal table/WAL keys under that principal key. Table data is then encrypted locally by the tde_heap storage access method — the Cockpit is never on the row-level data path.

Interop detail worth knowing

pg_tde's KMIP client segfaults if a Get for a previously registered key omits the key material. The Cockpit therefore persists the client-supplied bytes on Register, so the subsequent Get always returns the real key — closing an interop gap that would otherwise crash pg_tde at startup.

What happens when PostgreSQL starts​

The five steps below are what actually runs end to end once pg_tde is configured and PostgreSQL (re)starts, or when a backend first needs the principal key:

Startup key retrieval and page encryption
1. PostgreSQL startspg_tde extension loads via shared_preload_libraries
first operation needing the principal key
2. Request the principal keypg_tde's KMIP client issues a Get — only on a cache miss
KMIP (TTLV) over mutual TLS — port 5696
3. DuoKey KMIP serverAuthenticates the client certificate, returns the sealed principal key
cached in backend memory
4. Unwrap the Internal KeysPer-table / per-index internal keys, decrypted under the principal key
5. Transparent encrypt / decrypttde_heap table pages, and WAL records if WAL encryption is enabled

The principal key crosses the network once per cache miss; every subsequent table or WAL read/write is served from the cached, unwrapped internal keys.

Configuration​

The app's configuration surface is deliberately close to a production PostgreSQL client's — connection pooling, failover and replica awareness are first-class fields, not an afterthought.

FieldPurpose
hostportPostgreSQL primary connection endpoint.
databaseTarget database for TDE.
userssl_modeApplication connection credentials and TLS mode.
tde_algorithmTable encryption algorithm (as accepted by pg_tde).
tde_scopetde_tablespacesDatabase-wide default, or an explicit list of tablespaces to convert to <code>tde_heap</code>.
wal_encryptionWhether write-ahead log segments are also encrypted (requires a restart).
key_rotation_daysTarget cadence for principal-key rotation.
kmip_auth_modekmip_portAuthentication mode and port of the Cockpit KMIP listener (default 5696).
pool_maxpool_minpool_idle_timeout_secspool_max_lifetime_secsConnection-pool sizing toward the target database.
health_check_interval_secsHow often the app polls database health.
failover_modereplica_hoststarget_session_attrsretry_policyRead-replica awareness and failover behavior for the connection layer.
vault_idkey_idVault and (optionally) an existing key to link as the principal key.

Deployment SQL​

Deploying the app generates a ready-to-run setup script. Provider name is dke_cockpit; <host> and <port> (default 5696) point at the Cockpit's KMIP listener.

Setup SQL generated by the appSQL
CREATE EXTENSION IF NOT EXISTS pg_tde;

SELECT pg_tde_add_global_key_provider_kmip(
'dke_cockpit', '<host>', <port>,
'/etc/percona/certs/client.pem',    -- client cert (mTLS)
'/etc/percona/certs/client-key.pem',-- client key
'/etc/percona/certs/ca.pem');       -- CA validating the cockpit KMIP cert

SELECT pg_tde_create_key_using_global_key_provider('<label>', 'dke_cockpit');
SELECT pg_tde_set_key_using_global_key_provider('<label>', 'dke_cockpit');

-- Encrypt data (database-wide default, or per-table conversion):
ALTER DATABASE "<db>" SET default_table_access_method = 'tde_heap';
-- ALTER TABLE "<t>" SET ACCESS METHOD tde_heap;

-- Optional WAL encryption (requires a restart):
SELECT pg_tde_create_key_using_global_key_provider('<label>-wal', 'dke_cockpit');
SELECT pg_tde_set_server_key_using_global_key_provider('<label>-wal', 'dke_cockpit');
ALTER SYSTEM SET pg_tde.wal_encrypt = on;

-- Verify (returns 't'):
SELECT pg_tde_is_encrypted('<your_table>');
mTLS is enforced in production

The KMIP listener validates client certificates against its configured CA bundle when one is set — mutual TLS is required in production, and the listener refuses to start without it.

What the end-to-end proof actually verifies​

The proof is not a mocked round-trip. A dedicated integration test:

  • serves the real Cockpit KMIP listener,
  • brings up a genuine percona/percona-distribution-postgresql:17.5-3 container reaching the Cockpit host over the network,
  • runs the exact deployment SQL shown above,
  • asserts pg_tde_is_encrypted() returns true, and
  • inspects the on-disk tde_heap file directly: a plaintext marker string is absent from the encrypted table's file but present in a plaintext control table's file — direct evidence of ciphertext at rest, keyed by a principal key served over KMIP.

The same suite also exercises key rotation (existing rows survive re-wrap under a new principal key) and restart durability (the principal key is correctly reloaded from the KMIP server after a PostgreSQL restart).

Health and self-test​

CheckWhat it reportsLive probe today
HealthInstance status envelopeNot yet — enable/disable and health return status without probing the live customer database; a follow-up once an on-host health agent lands
Self-test / e2e suiteRegister→Get, ciphertext-at-rest, rotation, restart durabilityYes, in the opt-in integration test against a live Percona container

Performance​

Indicative only

The figures below come from a single development host and are meaningful as a relative comparison, not an absolute benchmark. Benchmark against your own environment before capacity planning.

pg_tde encrypts at rest: once a page is read into shared memory it stays decrypted there, so a working set that fits in cache shows negligible difference between plaintext and encrypted access. The measurable cost sits on the disk-write path (WAL and checkpoint pages), which shows up under concurrent write load.

MetricPlaintext heapEncrypted tde_heapOverhead
Bulk write (single-shot)~0.23–0.48 M rows/s~0.27–0.49 M rows/swithin noise
Sequential read (full-scan aggregate)~0.3–0.6 s~0.3–0.5 swithin noise
Concurrent insert load (16 clients)2 444 tps2 344 tps≈ +4%
KMIP principal-key set (Create+Get round-trip)——~0.4–1.0 s, off the hot path