# PostgreSQL セキュリティ堅牢化（ロール・最小権限・pg_hba.conf・SCRAM・TLS/verify-full・OAuth・v18対応）

> PostgreSQL を本番品質で守るセキュリティ堅牢化ガイド。スーパーユーザーで動かさない最小権限ロール設計、GRANT/REVOKEとデフォルト権限、public スキーマの落とし穴(PG15の変更)、監視用の定義済みロール、pg_hba.confの認証設計、SCRAMとMD5非推奨、TLS/sslmode=verify-fullで中間者攻撃を防ぐ方法、PostgreSQL 18のOAuth 2.0認証までを公式ドキュメントに忠実に解説します。

- 公開日: 2026-06-20
- 著者: 友田 陽大
- タグ: PostgreSQL, セキュリティ, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/postgresql-security-hardening-roles-privileges-ssl-scram-guide

## 要点

- アプリをスーパーユーザーやテーブル所有者で動かさない。スーパーユーザーは全権限チェックを迂回する(公式)。必要なDMLだけを持つ最小権限のLOGINロールを作る
- 権限：所有者は全権限を持つ。PUBLICには既定で権限を与えない。スキーマのUSAGE＋オブジェクト権限の両方が必要。ALTER DEFAULT PRIVILEGESで将来のテーブルにも正しい権限を
- PostgreSQL 15以降、PUBLICはpublicスキーマのCREATE権限を失った。アップグレードDBでは REVOKE CREATE ON SCHEMA public FROM PUBLIC を実行する
- pg_hba.confは上から最初の一致で認証が決まる。scram-sha-256を使い、hostsslでTLSを強制し、送信元IP/DB/ユーザーで絞る。trustは厳禁。MD5はPG18で非推奨
- クライアントは sslmode=verify-full で接続する。requireは暗号化するが証明書を検証しない=中間者攻撃を防げない。PG18はOAuth 2.0認証を追加

---

データベースのセキュリティは「最後に足すもの」ではなく、「最初に設計するもの」です。アプリをスーパーユーザーで動かし、`pg_hba.conf` を `trust` で開け、TLSを `require` で済ませている——そのどれもが、**情報漏洩への最短経路**です。

この記事は、PostgreSQL を本番で守るための**堅牢化チェックリスト**を、公式ドキュメントに忠実に解説します。ロールと最小権限、ネットワーク認証、パスワード方式、暗号化（中間者攻撃対策）まで。発注者が「このDBは安全に作られているか」を判断する材料にもなります。[本番運用ガイド §6](/blog/postgresql-production-operations-guide)の深掘りです。

> **この記事のルール**：ロール・権限・認証・暗号化の仕様、PostgreSQL 18 の変更点は **PostgreSQL 18 公式ドキュメント（2026年6月時点）** に基づきます。**行レベルセキュリティ（RLS）** によるマルチテナント認可は[データベース・RLS クラスタ](/blog/category/database)で詳述しているため、本稿では触れず、ロール・ネットワーク・暗号化に集中します。

---

## 1. 最小権限：アプリをスーパーユーザーで動かさない

第一の原則。**アプリの実行ロールにスーパーユーザーやテーブル所有者を使わない**。公式の警告：

> データベーススーパーユーザーは、ログインの権利を除き、**すべての権限チェックを迂回**する。…これは**危険な権限であり、不用意に使うべきではない**。スーパーユーザーでない役割で**大半の作業を行うのが最善**である。

ロールはクラスタ全体（公式：「ロールはデータベースクラスタ全体でグローバル」）で、`LOGIN` 属性を持つものが「ユーザー」、持たないものが「グループ」として機能します。本番の正解は——**アプリ専用の最小権限LOGINロール**を作り、必要なDMLだけ与えることです。

```sql
-- 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` スキーマにオブジェクトを作れる」状態で、公式は「**これは決して安全なパターンではない**」と断じています。

```sql
-- 旧版からアップグレードした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_*` の閲覧 |

```sql
-- アナリストは読み取り専用ロールで（スーパーユーザー不要）
GRANT pg_read_all_data TO analyst;
-- 監視エージェントは pg_monitor で
GRANT pg_monitor TO monitoring_agent;
```

---

## 3. ネットワーク認証：pg_hba.conf

「誰が・どこから・どのDBに・どの方式で」接続できるかを決めるのが `pg_hba.conf`。公式の最重要ルール：

> **最初に一致したレコード**が認証に使われる。「フォールスルー」や「バックアップ」は**ない**：あるレコードが選ばれて認証に失敗しても、後続のレコードは考慮されない。一致するレコードがなければ**アクセスは拒否**される。

つまり**順序が命**で、上から評価して最初の一致で決まります。堅牢化の要点：

```text
# 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 パスワードを設定する際に**非推奨警告を出す**。

```sql
-- パスワードは 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で接続します。

```bash
# アプリの接続文字列：verify-full でなりすましを防ぐ（require では不十分）
DATABASE_URL="postgresql://app_user@db.internal/appdb?sslmode=verify-full&sslrootcert=/etc/ssl/certs/db-ca.pem"
```

```ts
// 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. 堅牢化チェックリスト

1. **アプリは最小権限のLOGINロール**で動かす（スーパーユーザー・所有者を使わない）。
2. **`ALTER DEFAULT PRIVILEGES`** で将来のテーブルにも正しい権限を自動付与。
3. 旧版DBは **`REVOKE CREATE ON SCHEMA public FROM PUBLIC`**（PG15+の既定に合わせる）。
4. 読み取り/監視は **`pg_read_all_data` / `pg_monitor`**（スーパーユーザーを配らない）。
5. `pg_hba.conf` は**デフォルト拒否**、最初の一致が意図どおりの順序、**`hostssl`＋`scram-sha-256`**、送信元IP/DB/ユーザーで限定、**`trust` 禁止**。
6. パスワードは **SCRAM**。残った **MD5 ロールを移行**（PG18で非推奨）。
7. クライアントは **`sslmode=verify-full`** ＋ルートCA（`require` は不可）。
8. 余裕があれば **OAuth 2.0**（PG18）で中央ID管理、OpenSSL ≥ 1.1.1 を担保。
9. マルチテナントの行単位の分離は **RLS**（[データベース・RLS クラスタ](/blog/category/database)）で。

---

## 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 の**運用・信頼性シリーズ**は一巡です。[本番運用ガイド](/blog/postgresql-production-operations-guide)を起点に、[接続プーリング](/blog/postgresql-connection-pooling-pgbouncer-serverless-guide)・[バックアップ/PITR](/blog/postgresql-backup-pitr-pg-dump-wal-archiving-guide)・[レプリケーション/HA](/blog/postgresql-streaming-replication-high-availability-failover-guide)・[論理レプリケーション/アップグレード](/blog/postgresql-logical-replication-cdc-zero-downtime-upgrade-guide)・[無停止DDL](/blog/postgresql-zero-downtime-schema-migration-lock-safe-ddl-guide)と合わせて、「壊さない・止めない・詰まらせない・守る・進化させる」を満たす PostgreSQL を設計してください。

---

### 参考（PostgreSQL 18 公式ドキュメント）

- [Chapter 22. Database Roles](https://www.postgresql.org/docs/18/user-manag.html)
- [5.8. Privileges（GRANT / デフォルト権限）](https://www.postgresql.org/docs/18/ddl-priv.html)
- [22.5. Predefined Roles](https://www.postgresql.org/docs/18/predefined-roles.html)
- [21.1. The pg_hba.conf File](https://www.postgresql.org/docs/18/auth-pg-hba-conf.html)
- [21.5. Password Authentication（SCRAM）](https://www.postgresql.org/docs/18/auth-password.html)
- [19.9. Secure TCP/IP Connections with SSL / 34.19. SSL Support（sslmode）](https://www.postgresql.org/docs/18/libpq-ssl.html)
