現場で起きている問題:デフォルト設定という落とし穴
数年前、私は毎分約12,000リクエストを処理する高トラフィックなFintechサービス向けに、初めて本番環境のPostgreSQLクラスタをデプロイしました。クイックスタートガイドに従ってポート5432を開放し、バックエンドのマイクロサービスを接続したのです。
セキュリティチームが最初のペネトレーションテストを実施するまでは、すべて順調に稼働していました。しかし、その診断結果は深刻なものでした。プライベートサブネット内を平文のデータベース認証情報が飛び交い、認証テーブルは広範なIPレンジからの接続を許可しており、誰が重要なレコードを変更したのかを追跡する記録も一切残っていなかったのです。
多くの開発チームがこの落とし穴にはまっています。マネージドインスタンスや仮想マシンを立ち上げ、VPCの境界防御だけで攻撃を防げると過信してしまうのです。しかし、境界防御だけでは不十分です。クラスタ内でコンテナが1つでも侵害されれば、攻撃者に足がかりを与え、データベースパケットの盗聴やパスワードの傍受、さらには検知されることなくテーブル削除(DROP)を実行されてしまう危険性があります。
根本原因の分析:なぜPostgreSQLのデフォルト設定では不十分なのか
PostgreSQLのデフォルト設定は、ローカル開発環境でスムーズに動作することを最優先に設計されています。これにより数分で開発を始められますが、本番環境においては重大なセキュリティホールを生む原因となります。
- デフォルトでの非暗号化通信:近年のPostgreSQLバイナリはネイティブでSSLをサポートしています。しかし、初期設定のままでは
ssl = offになっていたり、警告なしに暗号化されていないTCP接続へと自動的にフォールバックしたりすることがよくあります。 - 緩すぎる認証ルール:
pg_hba.confの従来のデフォルト設定は、MD5のような脆弱なハッシュアルゴリズムや、ローカルインターフェースに対するtrust認証に依存していることが多々あります。このような構成では、パスワードハッシュがオフラインでの辞書攻撃に晒されるリスクがあります。 - 標準ログ機能の盲点:PostgreSQLは構文エラー、接続切断、および
log_min_duration_statementで設定された閾値を超えるスロークエリを記録します。しかし、きめ細かなDDLやDMLの追跡は一切行われません。そのため、HIPAA、SOC 2、PCI-DSSなどのコンプライアンス要件を満たしていることを証明できません。
防御戦略の比較
本番クラスタのセキュリティを確保するには、クエリのスループットを犠牲にしたりデプロイパイプラインを過度に複雑にしたりすることなく、データを保護できるアーキテクチャを選定する必要があります。
| セキュリティ戦略 | メリット | デメリット | 適合性 |
|---|---|---|---|
| ネットワーク境界のみ(VPC / セキュリティグループ) | 設定変更が不要で、初期セットアップが迅速。 | 同一ネットワーク内のインスタンス侵害に対する防御力がゼロ。パケットの暗号化や監査証跡なし。 | 開発環境およびサンドボックス環境のみ。 |
| 標準エンジンログ + パスワード認証 | 低オーバーヘッド(CPU負荷への影響約1%)。ログイン失敗やスロークエリを記録可能。 | テーブルレベルの詳細な監査ログがない。SSLなしでは転送中のパスワードが脆弱。 | 重要度の低いステージング環境。 |
| 多層防御による堅牢化(TLS 1.3 + SCRAM-SHA-256 + pgAudit) | 転送中データの完全な暗号化、強固な暗号化ハッシュ、改ざん耐性のあるコンプライアンスログ。 | 証明書の自動更新の仕組みが必要。ログストレージのオーバーヘッドが約3〜7%発生。 | すべての本番データベースで必須。 |
最適なアプローチ:ステップ・バイ・ステップ堅牢化ガイド
以下の手順に従って、デフォルト状態のPostgreSQL 16をミッションクリティカルな本番環境向けに堅牢化していきましょう。
ステップ1:TLS 1.3による転送時暗号化の強制
まずは、アプリケーションサーバーとデータベースエンジン間のすべての通信を暗号化するために、TLS証明書を生成またはプロビジョニングします。内部インフラストラクチャの場合は、組織内の内部認証局(HashiCorp Vaultやcert-managerなど)から発行された証明書を使用するか、専用の4096ビットのキーペアを作成します。
# PostgreSQLの設定ディレクトリに移動
cd /etc/postgresql/16/main
# 4096ビットのRSA秘密鍵を生成
openssl genrsa -out server.key 4096
# ファイルのパーミッションを厳格化(PostgreSQLは権限が緩い鍵を拒絶します)
chmod 600 server.key
chown postgres:postgres server.key
# 有効期間365日の自己署名証明書を生成
openssl req -new -x509 -days 365 -key server.key -out server.crt \
-subj "/CN=db-primary.internal.infra"
次に、postgresql.confを編集してTLSを有効化し、最新のプロトコルバージョンを指定して、証明書ファイルを関連付けます。
# /etc/postgresql/16/main/postgresql.conf 内で設定
ssl = on
ssl_cert_file = '/etc/postgresql/16/main/server.crt'
ssl_key_file = '/etc/postgresql/16/main/server.key'
ssl_min_protocol_version = 'TLSv1.3'
ssl_ciphers = 'HIGH:!aNULL:!MD5:!3DES'
ステップ2:pg_hba.confとSCRAMによるクライアントアクセスの厳格化
pg_hba.confファイルはデータベースにおけるファイアウォールの役割を果たします。従来のMD5認証を禁止し、scram-sha-256を必須化するとともに、リモートクライアントにはhostsslディレクティブを強制します。
アプリケーション用のユーザーをプロビジョニングする前に、暗号学的に安全で強固なパスワードを生成してください。ネットワーク経由でシークレットを漏洩させることなく、エントロピーの高い32文字のランダム文字列を生成するには、toolcraft.app/ja/tools/security/password-generator のようなブラウザ完結型のジェネレーターが便利です。パスワードを用意したら、アプリケーション用のロールを作成します。
-- ロール作成前にパスワード暗号化にSCRAMハッシュを使用するよう設定
SET password_encryption = 'scram-sha-256';
-- 最小権限の原則に基づきアプリケーション用ロールを作成
CREATE ROLE api_gateway WITH LOGIN PASSWORD 'xK9#mQ2$vL8*pZ4!wR7@yN1&jB6^tE5(';
GRANT CONNECT ON DATABASE orders_prod TO api_gateway;
続いて、pg_hba.confを開き、デフォルトのルールを限定的なCIDRブロックを指定した明示的なルールに置き換えます。
# TYPE DATABASE USER ADDRESS METHOD
# Unixドメインソケット経由のローカル管理者メンテナンス用
local all postgres peer
# 非暗号化TCP通信をすべて拒否し、APIサブネットからのみSCRAM認証を要求
hostssl orders_prod api_gateway 10.0.4.0/24 scram-sha-256
# それ以外のすべての接続を明示的に拒否
host all all 0.0.0.0/0 reject
host all all ::0/0 reject
ステップ3:pgAuditによるデータベース変更の追跡
標準のログはクエリが失敗したタイミングを教えてくれますが、インシデント発生時のセキュリティ調査には役立ちません。pgAudit拡張機能を使用すると、機密テーブルに対するすべてのINSERT、UPDATE、DELETE、ALTERを詳細に記録した構造化監査ログを生成できます。
使用しているPostgreSQLのバージョンに対応する拡張機能パッケージをインストールします。
# Ubuntu / Debian
sudo apt-get update && sudo apt-get install -y postgresql-16-pgaudit
共有ライブラリをプリロードし、管理者操作や書き込み処理を記録するようにPostgreSQLを設定します。
# /etc/postgresql/16/main/postgresql.conf 内で設定
shared_preload_libraries = 'pgaudit'
# 監査ログの設定
pgaudit.log = 'ddl, role, write'
pgaudit.log_catalog = off
pgaudit.log_parameter = on
pgaudit.log_relation = on
pgaudit.log_statement_once = off
PostgreSQLサービスを再起動して共有ライブラリを読み込みます。
sudo systemctl restart postgresql
対象のデータベースに接続し、拡張機能を有効化します。
\c orders_prod
CREATE EXTENSION IF NOT EXISTS pgaudit;
ステップ4:堅牢化した設定の検証とテスト
セキュリティ設定が正しく機能しているかを推測だけで済ませてはいけません。厳格なSSL検証フラグを使用してリモート接続をテストします。
# SSL接続が必須の状態で正常に接続できることを確認
psql "host=10.0.4.50 port=5432 dbname=orders_prod user=api_gateway sslmode=require"
データベース内でテーブル変更クエリを実行し、監査ログが記録されるかをテストします。
CREATE TABLE audit_check (id serial PRIMARY KEY, verified_at timestamptz DEFAULT now());
/var/log/postgresql/にあるアクティブなPostgreSQLログファイルを確認し、構造化されたpgAuditの出力を検証します。
LOG: AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.audit_check,CREATE TABLE audit_check (id serial PRIMARY KEY, verified_at timestamptz DEFAULT now());,<not logged>
TLS 1.3が盗聴を遮断し、厳格なpg_hba.confのルールがラテラルムーブメント(水平移動)を阻止します。そしてpgAuditにより、重要なすべてのクエリに対する確実な監査証跡が確保されます。これら3つのレイヤーを組み合わせることで、攻撃の標的になりやすいデータベースを、本番環境に耐えうる堅牢なシステムへと変貌させることができます。

