2026年5月25日
2026年5月25日
PostgreSQLのロールと権限を設定する方法|CREATE ROLE・GRANT・デフォルト権限の組み立て方
PostgreSQLの権限設計は、「権限をまとめたグループロールを作り、ログイン用ロールをそのメンバーにする」形にすると管理しやすくなります。ただし、権限はデータベース・スキーマ・テーブルの3階層それぞれで付与が必要で、さらに「これから作られるテーブル」には ALTER DEFAULT PRIVILEGES を別に設定しなければなりません。この記事では、アプリ用・読み取り専用・マイグレーション用のロールを作り、権限が正しく効いているかを確認するところまでを手順で説明します。
ロールはユーザーとグループを兼ねる
PostgreSQLには「ユーザー」と「グループ」の区別がなく、どちらもロールです。CREATE USER は CREATE ROLE ... LOGIN の別名で、違いはログインできる属性が既定で付くかどうかだけです。
ロールに付けられる主な属性は次のとおりです。
LOGIN:接続に使えるロールにする。付けなければグループ用のロールになりますSUPERUSER:すべての権限チェックを素通りします。アプリには付けませんCREATEDB/CREATEROLE:データベースやロールを作れるようにしますREPLICATION:レプリケーション接続用CONNECTION LIMIT n、VALID UNTIL '日時':同時接続数とパスワードの有効期限
属性(ロール自体の性質)と、テーブルなどへの権限(GRANT で付与するもの)は別物です。SUPERUSER 以外の属性は、テーブルを読む・書く権限には影響しません。
作成するロールの設計例
ここでは appdb というデータベースの app スキーマを例に、次の構成にします。
app_owner:テーブルを所有し、マイグレーション(テーブル作成・変更)を実行するロールapp_rw:読み書き権限をまとめたグループロール(ログイン不可)app_ro:読み取り権限をまとめたグループロール(ログイン不可)web_app:アプリが接続に使うロール。app_rwのメンバーanalyst:分析担当者の接続用ロール。app_roのメンバー
アプリ用のロールをテーブルの所有者にしないのがポイントです。所有者はそのテーブルに対して DROP や ALTER ができるため、アプリにSQLインジェクションなどの脆弱性があった場合の被害が大きくなります。
ロールを作成する
PostgreSQLのスーパーユーザー(多くの環境では sudo -u postgres psql)で実行します。
CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_rw NOLOGIN;
CREATE ROLE app_ro NOLOGIN;
CREATE ROLE web_app LOGIN IN ROLE app_rw;
CREATE ROLE analyst LOGIN IN ROLE app_ro CONNECTION LIMIT 3;
パスワードはSQLに直接書かず、psqlのメタコマンドで設定します。ALTER ROLE ... PASSWORD 'xxx' と書くと、平文がpsqlの履歴ファイルやサーバーログ(log_statement の設定によって)に残ることがあるためです。
\password web_app
\password analyst
\password はクライアント側でハッシュ化してから送信します。PostgreSQL 14以降は password_encryption の既定値が scram-sha-256 です。古いサーバーから移行した環境では md5 のままになっていないか、SHOW password_encryption; で確認しておきます。
app_owner をNOLOGINにしているのは、マイグレーション実行時だけ管理者が SET ROLE app_owner; で切り替える運用を想定しているためです。CIなどから直接接続させたい場合は LOGIN を付けて専用のパスワードを設定します。
データベースとスキーマを用意する
CREATE DATABASE appdb OWNER app_owner;
REVOKE ALL ON DATABASE appdb FROM PUBLIC;
GRANT CONNECT ON DATABASE appdb TO app_rw, app_ro;
\c appdb
CREATE SCHEMA app AUTHORIZATION app_owner;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT USAGE ON SCHEMA app TO app_rw, app_ro;
PUBLIC はすべてのロールを表す特別な名前で、既定ではどのデータベースにも CONNECT と TEMPORARY が許可されています。ここを外しておかないと、同じサーバー上の無関係なロールもこのデータベースに接続できます。
スキーマについては、PostgreSQL 15で大きな変更がありました。15以降では public スキーマへの CREATE 権限が PUBLIC から外され、新しく作ったデータベースでは一般ロールが public にテーブルを作れなくなっています。古い手順書どおりに進めて「permission denied for schema public」が出るのはこのためです。この記事のように専用スキーマを作り、所有者を明示するのが素直な対処です。
スキーマの USAGE は「スキーマの中を見る」権限です。これがないと、テーブルに SELECT 権限があっても参照できません。
テーブルの権限と、将来のテーブルへのデフォルト権限
既存のテーブル・シーケンスには ALL TABLES IN SCHEMA でまとめて付与できます。
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_ro;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_rw;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_rw;
ここで最もつまずきやすいのは、このGRANTは実行した時点で存在するテーブルにしか効かないことです。翌週のマイグレーションで追加されたテーブルには権限がなく、アプリが「permission denied for table」で止まります。これを防ぐのが ALTER DEFAULT PRIVILEGES です。
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT ON TABLES TO app_ro;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rw;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT USAGE, SELECT ON SEQUENCES TO app_rw;
FOR ROLE app_owner は「app_ownerが作成したオブジェクトに適用する」という意味です。この指定を省くと、コマンドを実行したロールが作成するオブジェクトにしか適用されません。管理者(postgres)で実行して FOR ROLE を忘れ、app_ownerで作ったテーブルに権限が付かない、というのは典型的な失敗です。マイグレーションは毎回 app_owner として実行する運用にそろえておくと、この設定が素直に効きます。
シーケンスの権限は、serial 型の列を使うテーブルにINSERTするときに必要です。nextval() を呼ぶためにシーケンスの USAGE が要るためです。
あわせて、TRUNCATE はアプリ用ロールには付けていません。DELETE と違って行単位のトリガーが動かず、一瞬で全件を消せる強い操作だからです。
権限が効いているかを確認する
設定したら、psqlのメタコマンドで確認します。\du はロールの属性と所属、\dn+ app はスキーマの所有者と権限、\dp app.* はテーブルごとの権限(\z でも同じ)、\ddp はデフォルト権限の一覧です。メタコマンドは行末までを引数として読むため、同じ行にSQLの -- コメントを書かないでください。
\du
\dn+ app
\dp app.*
\ddp
\dp の出力は web_app=arwd/app_owner のような形式で、r=SELECT、a=INSERT、w=UPDATE、d=DELETE、D=TRUNCATE、x=REFERENCES、t=TRIGGER を表し、/ の後ろは付与したロールです。
特定のロールから見た権限は関数で直接確かめられます。グループロール経由の権限も含めて判定されるので、「メンバーにしたはずなのに使えない」ときの切り分けに便利です。
SELECT has_schema_privilege('analyst', 'app', 'USAGE'),
has_table_privilege('analyst', 'app.orders', 'SELECT'),
has_table_privilege('analyst', 'app.orders', 'INSERT');
最終的には、実際にそのロールで接続して確認するのが確実性の高い方法です。psql -U analyst -d appdb -h localhost で接続し、SELECT が通り INSERT が拒否されることを見ておきます。
なお、ログインできるかどうかは権限とは別に、pg_hba.conf の認証設定で決まります。権限は正しいのに接続できない場合は、pg_hba.conf にそのロール・データベース・接続元の組み合わせが許可されているかを確認し、変更後は SELECT pg_reload_conf(); で再読み込みします。
事前定義ロールを活用する
用途によっては、自分でGRANTを積み上げなくても事前定義ロールで足ります(2026年10月現在の主なもの)。
pg_read_all_data/pg_write_all_data(PostgreSQL 14以降):全スキーマ・全テーブルの読み取り/書き込み。バックアップ用ロールなどに便利ですが範囲が広いので、アプリには使いませんpg_monitor:監視ツール用。各種統計ビューを参照できますpg_signal_backend:他のセッションのクエリをキャンセル・終了できますpg_maintain(PostgreSQL 17以降):VACUUM・ANALYZE・REINDEXなどの保守操作
また、PostgreSQL 16では CREATEROLE の扱いが変わり、CREATEROLE を持つロールが管理できるのは、自分が ADMIN OPTION を持つロールに限られるようになりました。古いバージョンのように「CREATEROLEがあれば実質的に何でもできる」状態ではなくなっています。
ロールを削除するときの手順
所有物や権限が残っているロールは DROP ROLE できません。削除の前に、そのロールが使われていた各データベースで次を実行します。作業前にはpg_dumpなどでバックアップを取っておきます。
REASSIGN OWNED BY old_user TO app_owner;
DROP OWNED BY old_user;
REASSIGN OWNED で所有オブジェクトを別のロールへ移し、DROP OWNED で残った権限設定(とデフォルト権限)を取り除きます。DROP OWNED は所有オブジェクトそのものも削除するため、順番を逆にするとテーブルが消えます。両方をデータベースごとに実行した後で、最後に DROP ROLE old_user; を実行します。
まとめ
- ロールはユーザーとグループを兼ねる。権限はNOLOGINのグループロールにまとめ、接続用ロールをメンバーにする
- アプリ用ロールはテーブルの所有者にしない。所有とマイグレーションは専用ロールに任せる
- 権限はデータベース(CONNECT)・スキーマ(USAGE)・テーブルの3階層で付与し、
PUBLICの既定権限を外す GRANT ... ON ALL TABLESは既存テーブルだけに効く。将来のテーブルにはALTER DEFAULT PRIVILEGES FOR ROLE 作成者を設定する\dp、\ddp、has_table_privilege()、実際のログインで確認し、接続できない場合はpg_hba.confも見る