背景:PostgreSQL運用時によくある設定の落とし穴
Ubuntu環境でPostgreSQLをセットアップする際、初期設定のまま運用を始めてしまうケースは少なくありません。代表的な例として、listen_addresses = '*' と全開放していたり、推測しやすいパスワードを使用していたり、マイナンバーやクレジットカード番号などの機密情報を平文(plaintext)のまま保存している構成が見受けられます。
このような設定は重大なセキュリティリスクを招きます。サーバーにパブリックIPが割り当てられている場合や同一内部ネットワーク内に存在する場合、わずかなSQLインジェクション脆弱性からでも全データが漏洩する危険性があります。
実際の現場プロジェクトでは、以下の3層による多層防御を構築することが推奨されます。
- ネットワークおよび認証層:
pg_hba.confを用いて接続元IPアドレスを厳密に制限し、パスワード認証には最新の暗号化方式であるscram-sha-256を義務付けます。 - データ保存層(Data at Rest): 拡張機能
pgcryptoを使用して機密性の高いカラム単位で暗号化を実施します。万が一数十GB規模のバックアップファイル(.sql)が攻撃者に奪取されたとしても、内部データは無意味な文字列として保護されます。 - 監視および追跡層(Audit): 拡張機能
pgauditを導入し、スキーマ変更やデータ操作に関するすべてのクエリを監査ログとして確実に記録します。
必要なパッケージと拡張機能のインストール
本記事の検証はUbuntu 22.04およびPostgreSQL 16の環境で実施しています。PostgreSQL 14や15をご利用の場合も基本的な手順は同様です。
# パッケージリポジトリを更新し、PostgreSQL 16と関連拡張機能をインストール
sudo apt update
sudo apt install -y postgresql-16 postgresql-contrib postgresql-16-pgaudit
# サービスを起動し、システムの自動起動を有効化
sudo systemctl enable postgresql
sudo systemctl start postgresql
パッケージ postgresql-contrib には pgcrypto が含まれており、postgresql-16-pgaudit はPostgreSQLコミュニティが公式に提供している強力な監査ログツールです。
postgres123 や admin@2026 のような単純なパスワードは絶対に使用しないでください。英大文字・小文字、数字、特殊文字を組み合わせた24〜32文字以上のランダムな文字列を生成して設定しましょう。
強力なパスワードを手軽に生成するには、ToolCraftのPassword Generatorが便利です。このツールはブラウザ側(クライアントサイド)のみで処理が完結し、サーバーへデータが送信されないため安全に利用できます。
# 生成した強力なパスワードでpostgresユーザーのパスワードを変更
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'k8#M9$xP2@vL7!qZ4*wR1&yN6';"
データベースにおける3層防御の実装
1. postgresql.confとpg_hba.confによるネットワークアクセスの厳格化
PostgreSQLの接続設定は、主に /etc/postgresql/16/main/ ディレクトリ配下の2つのファイルで管理されます。
まず、postgresql.conf を編集します。
sudo nano /etc/postgresql/16/main/postgresql.conf
接続待ち受けを * にするのではなく、データベースサーバー自身のプライベートIP(例: 10.0.1.5)を明示的に指定し、パスワードハッシュアルゴリズムをSCRAM-SHA-256に変更します。
# localhostおよび指定したプライベートIPからの接続のみをリッスン
listen_addresses = 'localhost, 10.0.1.5'
# Postgresで最も安全なパスワード暗号化方式を有効化
password_encryption = scram-sha-256
次に、pg_hba.conf でIPアドレスのホワイトリストを設定します。
sudo nano /etc/postgresql/16/main/pg_hba.conf
各設定行は TYPE DATABASE USER ADDRESS METHOD の構文に従います。安全性の低い trust や md5 を使用している行を削除し、以下のようにセキュアな構成を記述します。
# TYPE DATABASE USER ADDRESS METHOD
# システムユーザー用ローカルソケット接続
local all postgres peer
local all all scram-sha-256
# バックエンドサーバー群(10.0.2.0/24)からapp_productionへのアクセスのみ許可
host app_production app_user 10.0.2.0/24 scram-sha-256
# その他すべてのIPからの接続を拒否
host all all all reject
マイクロサービスやバックエンドサーバー向けにサブネットを適切に分割する際は、ToolCraftのSubnet Calculatorを活用すると、正確なCIDR範囲やサブネットマスクを素早く算出できます。
2. pgcryptoを用いた機密データのカラム単位暗号化
サーバー自体のroot権限が奪取されたり、SQLバックアップファイルが流出したりした場合、OSレベルのディスク暗号化(LUKSなど)だけではデータを保護できません。このような状況において、pgcrypto によるアプリケーション/カラムレベルの暗号化が最後の砦となります。
対象のアプリケーションデータベースに接続し、拡張機能を有効化します。
\c app_production
CREATE EXTENSION IF NOT EXISTS pgcrypto;
身分証明書番号(ID card)をバイナリ形式の BYTEA 型として保存するテーブルを作成します。
CREATE TABLE customer_profiles (
id SERIAL PRIMARY KEY,
full_name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
encrypted_id_card BYTEA NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- pgp_sym_encryptを使用してAES-128共通鍵暗号化を行いデータを挿入
INSERT INTO customer_profiles (full_name, email, encrypted_id_card)
VALUES (
'Nguyen Van A',
'[email protected]',
pgp_sym_encrypt('079199001234', 'SecretKey_2026_Prod_DbApp')
);
暗号化されたデータを復号して取得するクエリ例:
SELECT
id,
full_name,
email,
pgp_sym_decrypt(encrypted_id_card, 'SecretKey_2026_Prod_DbApp') AS id_card_decrypted
FROM customer_profiles
WHERE email = '[email protected]';
暗号化キーはHashiCorp VaultやAWS Secrets Managerなどを利用し、バックエンドアプリケーションの環境変数として安全に管理してください。SQLファイル内へのハードコードは厳禁です。
3. pgauditによる詳細な監査ログの記録
pgaudit 拡張機能は、実行されたSQLステートメント、実行ユーザー、対象テーブルの詳細なログを記録します。これはSOC 2やPCI-DSSなどの各種セキュリティコンプライアンス要件を満たす上で不可欠な設定です。
/etc/postgresql/16/main/postgresql.conf の末尾にライブラリ読み込み設定を追加します。
# PostgreSQL起動時にpgauditをロード
shared_preload_libraries = 'pgaudit'
# DDL、ROLE、およびデータ書き込み操作(INSERT/UPDATE/DELETE)をログ記録
pgaudit.log = 'ddl, role, write'
pgaudit.log_catalog = off
pgaudit.log_level = 'log'
pgaudit.log_parameter = on
モジュールを読み込ませるため、サービスを再起動します。
sudo systemctl restart postgresql
監査対象のデータベースで拡張機能を有効化します。
\c app_production
CREATE EXTENSION IF NOT EXISTS pgaudit;
動作検証と運用の注意点
1. 許可されていないIPからの接続テスト
ホワイトリスト外のサーバー(例: 192.168.1.50)から接続コマンドを実行してみます。
psql -h 10.0.1.5 -U app_user -d app_production
アクセスは即座に拒否され、エラーが出力されます。
psql: error: connection to server at "10.0.1.5", port 5432 failed: FATAL: no pg_hba.conf entry for host "192.168.1.50", user "app_user", database "app_production", no encryption
2. 監査ログの確認
別のターミナルウィンドウを開き、ログをリアルタイムで監視します。
sudo tail -f /var/log/postgresql/postgresql-16-main.log
テスト用のテーブル作成および削除クエリを実行します。
CREATE TABLE test_audit (id INT);
DROP TABLE test_audit;
ログファイルに操作内容が明確な構造で記録されていることが確認できます。
2026-10-06 10:45:12.312 UTC [12450] app_user@app_production LOG: AUDIT: SESSION,1,1,DDL,CREATE TABLE,TABLE,public.test_audit,CREATE TABLE test_audit (id INT);,<not logged>
2026-10-06 10:45:18.891 UTC [12450] app_user@app_production LOG: AUDIT: SESSION,2,1,DDL,DROP TABLE,TABLE,public.test_audit,DROP TABLE test_audit;,<not logged>
ディスク容量に関する注意点: 毎秒約1,000トランザクションが発生するようなシステム環境では、pgaudit のログ出力が1日あたり2〜5GBに達する可能性があります。ディスク容量の枯渇(disk full)を防ぐため、logrotate によるログローテーションおよび gzip 圧縮ポリシーを必ず設定しておきましょう。

