Percona PostgreSQL TDE
pg_tde は Cockpit が実行する本物の KMIP サーバーからプリンシパルキーを取得します — ライブの Percona コンテナに対して、ディスク上の暗号文に至るまでエンドツーエンドで実証済みです。
概要
Percona Distribution for PostgreSQL には pg_tde が同梱されています。これは、テーブルと、オプションで先行書き込みログ(WAL)に対する Transparent Data Encryption を提供する拡張機能です。pg_tde の KMIP キープロバイダーは、外部の KMIP サーバーからプリンシパル(マスター)キーを取得し、それを使って内部のテーブルキーと WAL キーをラップします。
DuoKey Cockpit が、その KMIP サーバーそのものです。pg_tde はバイナリ KMIP — 相互 TLS 上の OASIS TTLV — で Cockpit の KMIP リスナーと直接やり取りします。プリンシパルキーは中央で生成され、保存時にシールされ、監査されます。PostgreSQL ホスト自身がマスターキーをディスクに保存することはありません。
pg_tde の KMIP キープロバイダーは Cockpit の KMIP リスナーと直接通信します。プリンシパルキーが DuoKey のカストディを離れることはありません。
キー階層: プリンシパルキーと内部キー
pg_tde は、Percona 自身の用語で言えば 2 階層のキー階層を使用します。
- プリンシパルキーは、外部で管理される唯一のキーです。この統合では DuoKey KMIP サーバーが管理します。
pg_tdeはデータベースごとに 1 つのプリンシパルキーを使用します。 - 内部キーはすべてローカルで生成され、暗号化されたデータベースオブジェクトごとに 1 つ作られ(テーブルとその各インデックスは、オブジェクト識別子をキーとしてそれぞれ独自の内部キーを持ちます)、それ自体がプリンシパルキーの下でラップされます。テーブルデータの実際のページレベル暗号化を行うのは、プリンシパルキーではなく内部キーです。
KMIP プロバイダーからのプリンシパルキーの取得は PostgreSQL バックエンドのメモリにキャッシュされるため、pg_tde が Cockpit を呼び出すのはキーがローカルに保持されていない場合だけであり、テーブルの読み書きのたびに呼び出すわけではありません。WAL 暗号化は同じプリンシパルキーを使用しますが、テーブル暗号化とは独立に、サーバー全体の専用設定で有効化されます。WAL セグメントには、その設定がいつ切り替えられたかによって、暗号化されたレコードと暗号化されていないレコードの両方が含まれることがあります。
データベース暗号化カタログの他のエンジンとは異なり、この統合には本物の自動化されたエンドツーエンドの実証があります。本物の KMIP サーバー、TTLV/mTLS 上で実際の Register/Get シーケンスを実行する本物の pg_tde クライアント、そしてライブの Percona 17.5 コンテナを起動し、セットアップ SQL 一式を実行し、ディスク上のテーブルファイルを grep して、暗号化されたテーブルには平文が存在しないこと、平文の対照テーブルには平文が存在することを確認する統合テストです。
| プロパティ | 値 |
|---|---|
| 暗号化モデル | エンジンネイティブな TDE(テーブル + オプションで WAL) |
| キーの転送 | KMIP (TTLV) over mutual TLS, port 5696 |
| キーのカストディ | プリンシパルキーは DuoKey vault 内で保存時にシールされ、データベースホストにエクスポートされることはありません |
| オンボーディング | アプリ詳細ページ(プーリング、フェイルオーバー、レプリカなど、より豊富な設定項目) |
| 実証ステータス | ライブの Percona 17.5 / pg_tde 2.0 コンテナに対してエンドツーエンドで実証済み |
pg_tde と Cockpit のやり取り
pg_tde の KMIP クライアントは、pg_tde 2.0 に対して検証済みの、以下のシーケンスを正確に実行します。
Register
pg_tde_create_key_using_global_key_provider はプリンシパルキーをローカルで生成し、そのバイト列を Cockpit の KMIP サーバーに Register します。Cockpit はそのマテリアルを保存時にシールし、KMIP の一意識別子を返します。
Get
pg_tde_set_key_using_global_key_provider は Cockpit からそのキーを Get し、データベースのプリンシパルキーとして有効化します。
ラップして暗号化
pg_tde は内部のテーブル/WAL キーをそのプリンシパルキーの下でラップします。その後、テーブルデータは tde_heap ストレージアクセスメソッドによってローカルで暗号化されます — Cockpit が行レベルのデータ経路に関与することはありません。
pg_tde の KMIP クライアントは、以前に登録されたキーに対する Get がキーマテリアルを省略するとセグメンテーション違反を起こします。そのため Cockpit は Register 時にクライアントから提供されたバイト列を永続化し、後続の Get が常に実際のキーを返すようにしています — これにより、そうしなければ起動時に pg_tde をクラッシュさせる相互運用上のギャップを解消しています。
PostgreSQL 起動時に何が起こるか
以下の 5 つのステップは、pg_tde が設定された状態で PostgreSQL が(再)起動したとき、またはバックエンドが初めてプリンシパルキーを必要としたときに、実際にエンドツーエンドで実行される内容です。
プリンシパルキーがネットワークを越えるのはキャッシュミスごとに 1 回だけです。以降のテーブルや WAL の読み書きは、キャッシュされてアンラップ済みの内部キーで処理されます。
設定
このアプリの設定項目は、本番の PostgreSQL クライアントに意図的に近づけてあります。コネクションプーリング、フェイルオーバー、レプリカの認識は後付けではなく、第一級のフィールドとして扱われます。
| フィールド | 目的 | |||
|---|---|---|---|---|
host | port | PostgreSQL プライマリの接続エンドポイント。 | ||
database | TDE の対象データベース。 | |||
user | ssl_mode | アプリケーション接続の資格情報と TLS モード。 | ||
tde_algorithm | テーブル暗号化アルゴリズム(pg_tde が受け付けるもの)。 | |||
tde_scope | tde_tablespaces | データベース全体のデフォルト、または <code>tde_heap</code> に変換するテーブルスペースの明示的なリスト。 | ||
wal_encryption | 先行書き込みログのセグメントも暗号化するかどうか(再起動が必要)。 | |||
key_rotation_days | プリンシパルキーのローテーションの目標間隔。 | |||
kmip_auth_mode | kmip_port | Cockpit KMIP リスナーの認証モードとポート(デフォルトは 5696)。 | ||
pool_max | pool_min | pool_idle_timeout_secs | pool_max_lifetime_secs | 対象データベースへのコネクションプールのサイジング。 |
health_check_interval_secs | アプリがデータベースのヘルスをポーリングする頻度。 | |||
failover_mode | replica_hosts | target_session_attrs | retry_policy | 接続層におけるリードレプリカの認識とフェイルオーバーの挙動。 |
vault_id | key_id | vault と、(任意で)プリンシパルキーとしてリンクする既存のキー。 |
デプロイ SQL
アプリをデプロイすると、そのまま実行できるセットアップスクリプトが生成されます。プロバイダー名は dke_cockpit で、<host> と <port>(デフォルトは 5696)は Cockpit の KMIP リスナーを指します。
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>');KMIP リスナーは、CA バンドルが設定されている場合、それに対してクライアント証明書を検証します — 本番では相互 TLS が必須であり、それがなければリスナーは起動を拒否します。
エンドツーエンドの実証が実際に検証している内容
この実証はモックのラウンドトリップではありません。専用の統合テストは次を行います。
- 本物の Cockpit KMIP リスナーを提供し、
- ネットワーク越しに Cockpit ホストへ到達する本物の
percona/percona-distribution-postgresql:17.5-3コンテナを起動し、 - 上に示したデプロイ SQL をそのまま実行し、
pg_tde_is_encrypted()が true を返すことをアサートし、- ディスク上の
tde_heapファイルを直接検査します。平文のマーカー文字列は、暗号化されたテーブルのファイルには存在せず、平文の対照テーブルのファイルには存在します — これは、KMIP 経由で提供されたプリンシパルキーによって保存時に暗号文が生成されていることの直接的な証拠です。
同じスイートは、キーローテーション(既存の行が新しいプリンシパルキーの下で再ラップされても維持されること)と再起動時の耐久性(PostgreSQL の再起動後にプリンシパルキーが KMIP サーバーから正しく再読み込みされること)も検証します。
ヘルスとセルフテスト
| チェック | 報告される内容 | 現時点でのライブプローブ |
|---|---|---|
| ヘルス | インスタンスのステータスエンベロープ | まだ未対応 — 有効化/無効化とヘルスは、ライブの顧客データベースをプローブせずにステータスを返します。オンホストのヘルスエージェントが導入され次第の対応となります |
| セルフテスト / e2e スイート | Register→Get、保存時の暗号文、ローテーション、再起動時の耐久性 | あり。ライブの Percona コンテナに対するオプトインの統合テストで実施 |
パフォーマンス
以下の数値は単一の開発ホストで取得したもので、相対的な比較として意味を持つものであり、絶対的なベンチマークではありません。キャパシティプランニングの前に、ご自身の環境でベンチマークを取得してください。
pg_tde が暗号化するのは保存時です。ページがいったん共有メモリに読み込まれるとそこでは復号された状態で保持されるため、ワーキングセットがキャッシュに収まる場合、平文アクセスと暗号化アクセスの差はごくわずかです。測定可能なコストはディスク書き込み経路(WAL とチェックポイントのページ)に生じ、同時書き込み負荷の下で顕在化します。
| 指標 | 平文 heap | 暗号化 tde_heap | オーバーヘッド |
|---|---|---|---|
| バルク書き込み(単発) | ~0.23–0.48 M rows/s | ~0.27–0.49 M rows/s | ノイズの範囲内 |
| シーケンシャル読み取り(フルスキャン集計) | ~0.3–0.6 s | ~0.3–0.5 s | ノイズの範囲内 |
| 同時挿入負荷(16 クライアント) | 2 444 tps | 2 344 tps | ≈ +4% |
| KMIP プリンシパルキーの設定(Create+Get のラウンドトリップ) | — | — | ~0.4–1.0 s、ホットパス外 |