データベース隔離工学・正規リファレンス
db01.ok.st / PostgreSQL 19 / OpenZFS 8K
PostgreSQL 19 FORCE ROW LEVEL SECURITY と ON CONFLICT DO SELECT によるマルチテナント分離設計
監修:加藤 明生(株式会社オープンナレッジ 創業者・代表取締役CTO / データベーススペシャリスト 第DB-2013-04-00083号 / 登録情報処理安全確保支援士 第007617号)
1. 基本と全体像
「画面プログラムの注意深さ」に頼らず、データベース自体で個室の鍵をかける
| 章構成 | 主なテーマ | 要点と確認ポイント |
|---|---|---|
| 1. 基本と全体像 | WHERE句書き忘れ事故の構造的解消 | テーブル所有者ロールを含め行単位ポリシーを強制する FORCE ROW LEVEL SECURITY と Rust 型安全ゲートの全体像を解説します。 |
| 2. 現場での進め方と対策 | 3大アンチパターン回避と InitPlan 高速化 | コネクションプール混線を防ぐ SET LOCAL と、(SELECT sovereign_auth.current_tenant_id()) による定数化高速スキャンを示します。 |
| 3. 関連する制度とルール | SCS評価制度 ★3/★4・医療第7.0版との整合 | OWASP Top 10 A01(アクセス制御の不備)対策、および3秒・1タップ安全登録における個人学習データの分離保護を整理します。 |
| 4. 詳しい技術仕様としくみ | PostgreSQL 19 最新機能 × OpenZFS 8K 実装 | ON CONFLICT DO SELECT(0B WAL)、uuidv7()、ネイティブ REPACK (CONCURRENTLY)、COPY FORMAT JSON、Rust Typestate コードを公開します。 |
2. 現場での進め方と対策
RLS実装の3大アンチパターン回避と PostgreSQL 19 最適化ポイント
GUARD 01 / 所有者バイパス防止
FORCE RLS + NOBYPASSRLS
ENABLE RLS だけではテーブル所有者が全行を参照できてしまいます。必ず FORCE ROW LEVEL SECURITY と NOBYPASSRLS ロールを併用します。
GUARD 02 / InitPlan 定数化
(SELECT current_tenant_id())
ポリシー内でスカラ副問い合わせとして包むことで、PostgreSQL 19 プランナが InitPlan として1度だけ評価し、複合主キー (tenant_id, id) の高速 Index Scan に直結させます。
GUARD 03 / 0バイトWAL冪等認証
ON CONFLICT DO SELECT
3秒・1タップ安全登録の再ログイン時やWebhook再送時に不要な DO UPDATE を排し、書き込み0バイト・1往復で既存行を安全に取得します。
3. 関連する制度とルール
公的セキュリティ基準(SCS評価制度 ★3/★4・医療ガイドライン第7.0版)との対応
| PostgreSQL 19 × OpenZFS 機構 | 対応する公的基準・リスク分類 | 実務上の保護効果 |
|---|---|---|
| FORCE ROW LEVEL SECURITY + AS RESTRICTIVE | OWASP Top 10 A01 / 経産省SCS評価制度 ★3・★4 アクセス制御 | アプリケーション改修時のSQL条件漏れによる他社情報・他学習者履歴の混入を構造的に防止 |
| sovereign_auth.micro_users + uuidv7() | 個人情報保護法 安全管理措置 / 3秒・1タップ無料安全登録基盤 | 匿名ブラウザ保存の消失・共用PC閲覧リスクを排し、個人ごとの暗号化領域で学習・試算履歴を保全 |
| COPY ... TO ... (FORMAT JSON) + REPACK (CONCURRENTLY) | 厚労省 医療情報ガイドライン第6.0版・第7.0版(見読性・可用性) | 無停止でのテーブル再編成と、CPUシリアライズ負荷を抑えた監査証跡JSONエクスポートを実現 |
4. 詳しい技術仕様としくみ
jit = off / io_method = worker
db01.ok.st PostgreSQL 19 正規DDL & Rust 2024 Affine Typestate 実装仕様
PostgreSQL 19 DDL(db01.ok.st: sovereign_auth.micro_users & FORCE RLS + ON CONFLICT DO SELECT)
CREATE SCHEMA IF NOT EXISTS sovereign_auth;
CREATE OR REPLACE FUNCTION sovereign_auth.current_tenant_id()
RETURNS uuid LANGUAGE sql STABLE PARALLEL SAFE AS $$
SELECT NULLIF(current_setting('app.current_tenant_id', true), '')::uuid;
$$;
CREATE TABLE IF NOT EXISTS sovereign_auth.micro_users (
user_id UUID NOT NULL DEFAULT uuidv7(),
tenant_id UUID NOT NULL,
provider VARCHAR(32) NOT NULL,
subject_hash BYTEA NOT NULL CHECK (octet_length(subject_hash) = 32),
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
PRIMARY KEY (tenant_id, user_id),
UNIQUE (tenant_id, provider, subject_hash)
);
ALTER TABLE sovereign_auth.micro_users ENABLE ROW LEVEL SECURITY;
ALTER TABLE sovereign_auth.micro_users FORCE ROW LEVEL SECURITY;
CREATE POLICY micro_users_tenant_isolation ON sovereign_auth.micro_users
AS RESTRICTIVE FOR ALL TO PUBLIC
USING (tenant_id = (SELECT sovereign_auth.current_tenant_id()))
WITH CHECK (tenant_id = (SELECT sovereign_auth.current_tenant_id()));
-- 0-Byte WAL Idempotent Sign-In / Registration in PostgreSQL 19
INSERT INTO sovereign_auth.micro_users (tenant_id, provider, subject_hash)
VALUES ($1, $2, $3)
ON CONFLICT (tenant_id, provider, subject_hash) DO SELECT
RETURNING user_id, tenant_id, created_at;