データベースのセキュリティは「最後に足すもの」ではなく、「最初に設計するもの」です。アプリをスーパーユーザーで動かし、pg_hba.conf を trust で開け、TLSを require で済ませている——そのどれもが、情報漏洩への最短経路です。
この記事は、PostgreSQL を本番で守るための堅牢化チェックリストを、公式ドキュメントに忠実に解説します。ロールと最小権限、ネットワーク認証、パスワード方式、暗号化(中間者攻撃対策)まで。発注者が「このDBは安全に作られているか」を判断する材料にもなります。本番運用ガイド §6の深掘りです。
この記事のルール:ロール・権限・認証・暗号化の仕様、PostgreSQL 18 の変更点は PostgreSQL 18 公式ドキュメント(2026年6月時点) に基づきます。行レベルセキュリティ(RLS) によるマルチテナント認可はデータベース・RLS クラスタで詳述しているため、本稿では触れず、ロール・ネットワーク・暗号化に集中します。
1. 最小権限:アプリをスーパーユーザーで動かさない
第一の原則。アプリの実行ロールにスーパーユーザーやテーブル所有者を使わない。公式の警告:
データベーススーパーユーザーは、ログインの権利を除き、すべての権限チェックを迂回する。…これは危険な権限であり、不用意に使うべきではない。スーパーユーザーでない役割で大半の作業を行うのが最善である。
ロールはクラスタ全体(公式:「ロールはデータベースクラスタ全体でグローバル」)で、LOGIN 属性を持つものが「ユーザー」、持たないものが「グループ」として機能します。本番の正解は——アプリ専用の最小権限LOGINロールを作り、必要なDMLだけ与えることです。
-- 1) オブジェクト所有者(マイグレーション用・通常運用では使わない)
CREATE ROLE app_owner NOLOGIN;
-- 2) アプリ実行用の最小権限ロール(必要なDMLだけ。所有者でもスーパーユーザーでもない)
CREATE ROLE app_user LOGIN PASSWORD '***';
-- 3) スキーマと既存テーブルへ、必要な権限だけ付与
GRANT USAGE ON SCHEMA app TO app_user; -- スキーマのUSAGEは必須
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA app TO app_user; -- serial/identity を使うなら
-- 4) 将来作られるテーブルにも自動で同じ権限を(重要:付け忘れを防ぐ)
ALTER DEFAULT PRIVILEGES IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
アプリが乗っ取られても、
app_userがDROP TABLEもCREATE ROLEもできなければ、被害は「そのロールが触れるデータ」に封じ込められます。これが最小権限による被害局限です。
2. 権限設計:GRANT、デフォルト権限、public スキーマの罠
所有者・PUBLIC・USAGE
公式:「オブジェクトの所有者は、本質的に全権限を持つ」。新規オブジェクトは作成者が所有し全権限を持ちます。他ロールには GRANT で必要な分だけ渡します。
特別な擬似ロール PUBLIC は「全ロール」を意味します。公式:「テーブル・列・シーケンス・スキーマ等に対し、PUBLIC には既定で権限が与えられない」。重要なのは、テーブル権限だけでなくスキーマの USAGE も必要な点——両方揃って初めてアクセスできます。
⚠ public スキーマの落とし穴(PostgreSQL 15 の変更)
歴史的な地雷です。公式:
PostgreSQL 15 以降、既定の構成はこの使い方を支える。それ以前(または旧版からアップグレードしたDB)では、
publicスキーマから PUBLIC のCREATE権限を削除する必要がある(REVOKE CREATE ON SCHEMA public FROM PUBLICを実行)。
旧来は「全ユーザーが public スキーマにオブジェクトを作れる」状態で、公式は「これは決して安全なパターンではない」と断じています。
-- 旧版からアップグレードしたDBでは必ず実行(PG15+で新規作成したDBは既定で対策済み)
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
-- さらに厳格にするなら、public への接続時USAGEも絞る
-- REVOKE ALL ON SCHEMA public FROM PUBLIC;
監視・分析には定義済みロール
「読み取りだけのアナリスト」「監視エージェント」にスーパーユーザーを渡すのは過剰です。公式の定義済みロールを使います。
| ロール | 権限 |
|---|---|
pg_read_all_data | 全テーブル/ビュー/シーケンスのSELECT 相当+全スキーマの USAGE(RLSは迂回しない) |
pg_monitor | 監視ビュー/関数の読み取り(pg_read_all_settings+pg_read_all_stats 等を含む) |
pg_read_all_stats | 全 pg_stat_* の閲覧 |
-- アナリストは読み取り専用ロールで(スーパーユーザー不要)
GRANT pg_read_all_data TO analyst;
-- 監視エージェントは pg_monitor で
GRANT pg_monitor TO monitoring_agent;
3. ネットワーク認証:pg_hba.conf
「誰が・どこから・どのDBに・どの方式で」接続できるかを決めるのが pg_hba.conf。公式の最重要ルール:
最初に一致したレコードが認証に使われる。「フォールスルー」や「バックアップ」はない:あるレコードが選ばれて認証に失敗しても、後続のレコードは考慮されない。一致するレコードがなければアクセスは拒否される。
つまり順序が命で、上から評価して最初の一致で決まります。堅牢化の要点:
# TYPE DATABASE USER ADDRESS METHOD
# ローカルの管理用ソケットのみ peer(OSユーザーと一致)
local all postgres peer
# アプリは TLS 必須(hostssl)+ SCRAM + 送信元を限定
hostssl appdb app_user 10.0.0.0/24 scram-sha-256
# 上記に一致しない全接続を明示的に拒否(デフォルト拒否)
host all all 0.0.0.0/0 reject
hostsslを使い、TLS接続でなければ一致させない(hostはSSL/非SSLどちらも一致)。- 認証は
scram-sha-256。 trustは厳禁——公式:「無条件で接続を許可する。…パスワードも認証も不要で、誰でも任意のユーザーとしてログインできる」。ローカルの信頼できるソケット以外で使ってはいけません。- 送信元IP(CIDR)・DB・ユーザーでできる限り絞る。
4. パスワード:SCRAM、MD5 は非推奨に
公式:password_encryption の既定は scram-sha-256。SCRAM は「現在提供される中で最も安全」です。一方 MD5 は弱い——公式:「MD5 ハッシュアルゴリズムは、今日では執拗な攻撃に対して安全とは見なされない」。
そして PostgreSQL 18 で MD5 は非推奨になりました(公式リリースノート):
MD5 パスワード認証を非推奨化する。…将来のメジャー版でサポートは削除される。
CREATE ROLEとALTER ROLEは、MD5 パスワードを設定する際に非推奨警告を出す。
-- パスワードは SCRAM で保存される(既定)。MD5 ロールが残っていれば SCRAM へ移行
SET password_encryption = 'scram-sha-256'; -- 既定
ALTER ROLE app_user PASSWORD '***'; -- 設定し直すと SCRAM ハッシュで保存される
-- MD5 のまま残っているロールを洗い出す
SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%';
5. 暗号化:sslmode=verify-full で中間者攻撃を防ぐ
通信の暗号化はサーバー設定とクライアント設定の両輪です。サーバーは ssl = on で有効化(server.crt/server.key が必要)。
決定的に重要なのはクライアントの sslmode です。公式の表が示すとおり、暗号化となりすまし(中間者攻撃)防止は別物。
| sslmode | 盗聴防止 | 中間者攻撃(MITM)防止 | 何を検証するか |
|---|---|---|---|
require | ✅ | ❌ | 暗号化のみ。証明書を検証しない |
verify-ca | ✅ | CA次第 | 証明書チェーンをルートCAまで検証 |
verify-full | ✅ | ✅ | 上記+ホスト名が証明書と一致するか検証 |
公式:「第三者が通信途中でデータを改変できれば、サーバーになりすませる。SSL は証明書検証でこれを防ぐ」。require は暗号化するだけでなりすましを防げません。本番のクライアントは verify-full +ルートCAで接続します。
# アプリの接続文字列:verify-full でなりすましを防ぐ(require では不十分)
DATABASE_URL="postgresql://app_user@db.internal/appdb?sslmode=verify-full&sslrootcert=/etc/ssl/certs/db-ca.pem"
// node-postgres でも明示的に検証を有効化(rejectUnauthorized を切らない)
import { Pool } from "pg";
export const pool = new Pool({
connectionString: process.env.DATABASE_URL, // sslmode=verify-full を含む
ssl: { rejectUnauthorized: true, ca: process.env.DB_CA_CERT }, // 証明書を必ず検証
});
やってはいけない:
rejectUnauthorized: falseやsslmode=requireで「とりあえず動かす」。それは暗号化しているのになりすましを許す、最悪の中途半端です。
6. PostgreSQL 18 のセキュリティ強化
公式リリースノートより。
- OAuth 2.0 認証:
pg_hba.confにoauthメソッドが追加。サードパーティのIdP(OAuth 2.0)で認可・認証できる。中央集権的なID管理に有用(--with-libcurlビルドが必要)。 - MD5 非推奨(§4)。
- SCRAM パススルー:クライアントのSCRAM認証を
postgres_fdw/dblinkに引き継げる(use_scram_passthrough)——FDWの資格情報をDBに保存せずに済む。 - TLS:
ssl_ecdh_curveがssl_groupsに改名(複数曲線を指定可、既定に X25519 を含む)。OpenSSL 1.1.1 未満はサポート終了。 - 権限系:
pg_get_acl()、ALTER DEFAULT PRIVILEGESがラージオブジェクト対応、定義済みロールpg_signal_autovacuum_worker追加。
7. 堅牢化チェックリスト
- アプリは最小権限のLOGINロールで動かす(スーパーユーザー・所有者を使わない)。
ALTER DEFAULT PRIVILEGESで将来のテーブルにも正しい権限を自動付与。- 旧版DBは
REVOKE CREATE ON SCHEMA public FROM PUBLIC(PG15+の既定に合わせる)。 - 読み取り/監視は
pg_read_all_data/pg_monitor(スーパーユーザーを配らない)。 pg_hba.confはデフォルト拒否、最初の一致が意図どおりの順序、hostssl+scram-sha-256、送信元IP/DB/ユーザーで限定、trust禁止。- パスワードは SCRAM。残った MD5 ロールを移行(PG18で非推奨)。
- クライアントは
sslmode=verify-full+ルートCA(requireは不可)。 - 余裕があれば OAuth 2.0(PG18)で中央ID管理、OpenSSL ≥ 1.1.1 を担保。
- マルチテナントの行単位の分離は RLS(データベース・RLS クラスタ)で。
8. まとめ
- 最小権限が出発点。アプリをスーパーユーザー・所有者で動かさない。
ALTER DEFAULT PRIVILEGESで付け忘れを防ぐ。 - public スキーマの罠(PG15の変更)に注意——旧版DBは
REVOKE CREATE ... FROM PUBLIC。 pg_hba.confは最初の一致。デフォルト拒否・hostssl・scram-sha-256・送信元限定。trust厳禁。- SCRAM を使い、MD5 を捨てる(PG18で非推奨)。
sslmode=verify-fullでなりすましを防ぐ(requireは暗号化だけで不十分)。- PG18 は OAuth 2.0 認証を追加。
これで PostgreSQL の運用・信頼性シリーズは一巡です。本番運用ガイドを起点に、接続プーリング・バックアップ/PITR・レプリケーション/HA・論理レプリケーション/アップグレード・無停止DDLと合わせて、「壊さない・止めない・詰まらせない・守る・進化させる」を満たす PostgreSQL を設計してください。