データベースのユーザーと権限管理
データベースのユーザーと権限管理とは
アプリが [[リレーショナルデータベース]] に接続するとき、設定ファイルにこんな文字列を書きます。
postgres://app_user:s3cret@db.example.com:5432/shop
^^^^^^^^
これがDBの「ユーザー」
初学者が見落としがちなのが、この app_user の部分です。データベースはOSと同じように「ユーザー」という概念を持っていて、接続してきた相手が誰かを認証し、そのユーザーに許された操作しか実行しません。
つまり「DBに接続できた」と「何でもできる」はまったく別です。同じ SELECT * FROM orders でも、あるユーザーでは結果が返り、別のユーザーでは permission denied for table orders で拒否されます。権限管理とは、この「誰として繋ぐか」を用途ごとに設計する作業のことです。
GRANTとREVOKE
権限は「誰に」「どのオブジェクトに」「どの操作を」の3点セットで指定します。付与が GRANT、取り消しが REVOKE です。
CREATE USER app_user WITH PASSWORD 's3cret';
-- ordersテーブルに対して読み書きだけ許可(削除は含めない)
GRANT SELECT, INSERT, UPDATE ON orders TO app_user;
-- 誤って付けた権限を取り上げる
REVOKE DELETE ON orders FROM app_user;
操作の種類は、おおまかに次のように分かれます。
| 分類 | 代表的な権限 | 意味 |
|---|---|---|
| データ操作 | SELECT / INSERT / UPDATE / DELETE | 行を読む・追加する・書き換える・消す |
| 定義変更 | CREATE / ALTER / DROP | テーブルを作る・定義を変える・丸ごと消す |
| 管理 | ユーザー作成・権限付与・設定変更 | DB全体の運営に関わる操作 |
つまずきやすいのが「新しく作ったテーブルには権限が自動で付かない」点です。[[スキーママイグレーション]] で CREATE TABLE した直後に GRANT を書き忘れると、開発環境(たいてい強い権限で動かしている)では動くのに本番だけ permission denied になります。PostgreSQL の ALTER DEFAULT PRIVILEGES のように「今後作られるテーブルにも既定で権限を付ける」設定を先に入れておくのが定石です。
ロールで権限を束ねる
ユーザーが増えるたびに個別に GRANT を打っていると、すぐに管理できなくなります。そこで権限を ロール(役割)にまとめ、ユーザーにはロールを与えます。
CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
GRANT readonly TO analytics_user; -- 分析担当はこれ1行で完了
[[最小権限の原則]] で説明されている RBAC を、データベースの世界で実現したものがこれです。なお PostgreSQL ではユーザーとロールは同じ実体で、ログイン属性の有無で呼び分けているだけです。
用途別にアカウントを分ける
実務では、1つのシステムに対して複数のDBアカウントを用意します。
| アカウント | 与える権限 | 使う場面 |
|---|---|---|
| アプリ用 | 必要なテーブルへの SELECT / INSERT / UPDATE(必要なら DELETE) | Webアプリの常時接続 |
| 参照専用 | SELECT のみ | 管理画面の集計、障害調査、BIツール |
| マイグレーション用 | CREATE / ALTER / DROP などの定義変更 | デプロイ時のスキーマ適用のみ |
| 管理者 | ほぼ全部 | 初期構築・障害対応を人が手で行うとき |
分ける単位は「別のユーザー+別の接続文字列」です。接続情報が増えるので、[[シークレット管理]] の仕組みに載せて配布します。
この分割の効き目は具体的です。アプリ用アカウントに DROP 権限がなければ、[[SQLインジェクション]] を突破されても攻撃者はテーブルを消せません。マイグレーション用の強い権限は、デプロイの数十秒間しか使われないので、漏れたときの露出時間も短くなります。
スーパーユーザーで動かす危険
開発中に権限エラーが出て、面倒だからと postgres や root といったスーパーユーザーで接続し、そのまま本番へ——これが最も多い事故のパターンです。
- 侵入されたとき、全テーブルの読み書きどころかユーザーの追加や設定変更までできてしまう
- [[Row Level Security(RLS)]] はテーブル所有者とスーパーユーザーをバイパスします。ポリシーを丁寧に書いても、繋いでいるユーザーが強すぎれば無効になる
DELETE FROM users;のような操作ミスを、DB側の権限で止められない
自分のアプリがどのユーザーで繋いでいるかは SELECT current_user; で確認できます。「接続できたから正常」で終わらせず、そのユーザーで DROP TABLE が通ってしまわないかを一度試してください。
初学者向けポイント
- ローカル環境で参照専用ユーザーを1つ作り、そのユーザーで
UPDATEが弾かれる様子を実際に見ておくと理解が一気に進みます permission deniedはバグではなく設計どおりの拒否であることが多い。権限を足す前に「その操作は本当にアプリに必要か」を考える- [[コネクションプーリング]] のプールは同じユーザーの接続を束ねる仕組みです。用途別にアカウントを分けるなら、プールも用途ごとに分かれます
関連技術とのつながり
- [[最小権限の原則]] — この記事はその原則をデータベース層で実装する具体策
- [[SQLインジェクション]] — 突破されたときの被害範囲を決めるのが接続ユーザーの権限
- [[Row Level Security(RLS)]] — 行単位の制御。ただし強すぎるユーザーではバイパスされる
- [[スキーママイグレーション]] — 定義変更用の強い権限を、適用時だけ使う運用
- [[リレーショナルデータベース]] — ユーザー・ロール・権限を持つ本体
Q: データベースの接続文字列に含まれるユーザー名は何を決めている?
- [ ] 接続の暗号化方式
- [x] そのユーザーに許された操作しか実行できないという権限の範囲
- [ ] クエリの実行速度の上限
解説: DBは接続してきた相手を認証し、そのユーザーに許された操作だけを実行します。「接続できた=何でもできる」ではありません。
Q: アプリ用のDBアカウントとは別に、マイグレーション用のアカウントを用意する理由はどれ?
- [x] CREATE / ALTER / DROP といった定義変更の強い権限を、適用時だけに限定するため
- [ ] マイグレーションの実行速度を上げるため
- [ ] アプリの接続数を増やすため
解説: 常時稼働するアプリに定義変更権限を持たせないことで、侵入や不具合が起きても構造を壊されなくなります。
Q: スーパーユーザーでアプリを接続する危険として本文が挙げているのはどれ?
- [ ] クエリのキャッシュが効かなくなる
- [ ] 接続プールが使えなくなる
- [x] Row Level Security のポリシーがバイパスされて効かなくなる
解説: RLSはテーブル所有者やスーパーユーザーをバイパスするため、ポリシーを書いても接続ユーザーが強すぎると無効になります。