PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:プロレベル運用 - Day26 権限管理

SQL PostgreSQL
スポンサーリンク

Day26 後半のゴール

「“とりあえず全部GRANT”から、“攻撃前提で権限を設計する”へ」

前半で、ロールと権限の基本構造、アプリ用・開発用・運用用のざっくりした分け方は掴めました。
後半では、「最小権限」「スキーマ単位の設計」「GRANT/REVOKEの運用」「SQLインジェクションを前提にした考え方」まで含めて、プロレベルに近い権限設計の感覚を育てていきます。


最小権限の原則を“DB目線”で言語化する

「“できるから許す”ではなく、“必要だから許す”に切り替える」

セキュリティの世界でよく出てくる「最小権限の原則」は、DBでもそのまま効きます。
ただ、抽象的だと動けないので、PostgreSQL目線で具体的に言い直してみます。

まず、「ロールごとに“何ができると困るか”を先に考える」ことです。
アプリ用ロールなら、「テーブル定義の変更」「他DBへのアクセス」「他ユーザーの権限変更」ができると困る。
開発者ロールなら、「本番データの大量削除」「監査ログの改ざん」ができると困る。
運用ロールなら、「アプリからの通常操作に混ざって勝手にデータを書き換える」ことができると困る。

そのうえで、「困る操作」をまず禁止し、「どうしても必要な操作だけをGRANTする」という順番で考えます。
よくある逆順は、「とりあえず全部できるようにして、あとで必要なものだけ使う」というやり方ですが、これは攻撃者にとって最高の環境です。

PostgreSQLでは、ロール作成時に「LOGINできるか」「CREATEDBできるか」「CREATEROLEできるか」「SUPERUSERか」といったフラグを付けます。
ここで「SUPERUSERを付けない」「CREATEROLEを付けない」だけでも、事故の範囲はかなり狭くなります。

最小権限の原則を一言でまとめるなら、「“できるから許す”ではなく、“必要だから許す”」です。
この視点を持った瞬間、GRANTを書く手が変わります。


スキーマ単位で“世界を分ける”設計

「publicに全部突っ込むのをやめて、“ゾーン”を作る」

SQLiteや小さなMySQL運用だと、「全部 public(もしくはデフォルトスキーマ)にテーブルを置く」という世界になりがちです。
PostgreSQLでは、スキーマをうまく使うことで「権限の境界線」を引きやすくなります。

例えば、こういう分け方を考えます。

アプリが通常使うテーブル群:app スキーマ
監査ログや内部用テーブル群:audit スキーマ
管理・メタ情報用テーブル群:admin スキーマ

このとき、アプリ用ロール app_user には、

app スキーマへの USAGE と、そこにあるテーブルへの SELECT/INSERT/UPDATE/DELETE をGRANTする。
audit スキーマへの USAGEは許すが、テーブルへのINSERT/UPDATE/DELETEは許さない(SELECTだけにするか、完全に禁止するか)。
admin スキーマへのUSAGEすら許さない(存在を見せない)。

というように、「スキーマごとに見せる世界を変える」ことができます。

GRANT文のイメージはこうです。

-- appスキーマ
GRANT USAGE ON SCHEMA app TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;

-- auditスキーマ(読み取りだけ許す例)
GRANT USAGE ON SCHEMA audit TO app_user;
GRANT SELECT ON ALL TABLES IN SCHEMA audit TO app_user;

-- adminスキーマは何もGRANTしない
SQL

こうしておくと、「アプリからのSQLインジェクションが起きても、adminスキーマのテーブルにはそもそも届かない」という状態を作れます。

重要なのは、「スキーマを“フォルダ”ではなく、“権限ゾーン”として設計する」という発想です。


GRANTとREVOKEを“運用の一部”として扱う

「一度決めて終わりではなく、“変化に合わせて権限も変える”」

権限設計は、「最初に決めて終わり」ではありません。
サービスの機能が増えたり、チーム構成が変わったりすると、「誰が何をできるべきか」も変わります。

PostgreSQLでは、GRANTで権限を付与し、REVOKEで権限を取り上げます。
ここで大事なのは、「REVOKEを普通に使う文化」を持つことです。

例えば、開発初期は dev_role に本番DBのUPDATE権限を一部与えていたけれど、
運用が安定してきたら「もう直接触る必要はない」と判断したとします。

そのときに、

REVOKE UPDATE ON ALL TABLES IN SCHEMA app FROM dev_role;
SQL

といった形で、「権限を減らす」ことを躊躇しないことが大事です。
権限は増やすのは簡単ですが、減らすのを怖がると、どんどん“何でもできる人”が増えていきます。

運用としては、

新機能を追加するときに、「どのロールにどの権限が必要か」を一緒に設計する。
定期的に「このロールは本当にこの権限が必要か?」を棚卸しする。
不要になった権限はREVOKEして、ログに残しておく。

というサイクルを回すことで、「権限がサービスの変化に追随する」状態を保てます。


SQLインジェクションを“前提”にした権限設計

「アプリが破られても、“DB側で止める”という発想」

セキュリティスペシャリスト目線でいうと、権限設計の一番大事な視点は「攻撃前提」です。
つまり、「アプリケーションがSQLインジェクションで破られたとしても、DB側でどこまで被害を抑えられるか」を考えることです。

SQLインジェクションが起きると、攻撃者は「アプリが使っているDB接続の権限」で好きなSQLを実行できます。
ここで、「その接続ユーザーが何でもできる」状態だと、テーブル削除・権限変更・他DBへのアクセスなど、やりたい放題です。

逆に、「アプリ用ロールに最小限の権限しか与えていない」状態だと、攻撃者ができることも限定されます。

例えば、

app_user が持っているのは、app スキーマのテーブルへの SELECT/INSERT/UPDATE/DELETE だけ。
audit スキーマのテーブルはSELECTのみ、admin スキーマにはアクセス不可。
ロール管理・DB作成・スキーマ作成などの権限は一切なし。

この状態なら、SQLインジェクションが起きても、

アプリ用テーブルのデータを盗む・書き換えることはできる(これはアプリ側の責務)。
監査ログの改ざんは難しい(INSERT/UPDATE権限がない)。
管理用テーブルや他DBへの攻撃はできない。
新しいユーザーやロールを勝手に作ることもできない。

という「被害の上限」が決まります。

権限設計をするときは、「このロールが破られたとき、攻撃者は何ができるか?」を必ず一度イメージしてみてほしいです。
そのうえで、「それでも許容できる範囲」に権限を絞るのが、プロレベルの発想です。


例題:小さなサービスの“現実的な権限設計”を言語化する

「完璧じゃなくていい、でも“スーパーユーザー1本”は卒業する」

最後に、あなたが「小さなWebサービス」のDBを任されたと仮定して、
Day26までの内容を使って“現実的な権限設計”を言葉にしてみます。

データベース myapp_db を1つ用意する。
スキーマを app(アプリ用)と audit(監査用)に分ける。
アプリ用ロール app_user を作り、LOGIN可能だがSUPERUSER/CREATEROLE/CREATEDBは付けない。
app_user には myapp_db への CONNECT、app スキーマへの USAGE、そこにあるテーブルへの SELECT/INSERT/UPDATE/DELETE をGRANTする。
audit スキーマへのUSAGEとSELECTのみをGRANTし、INSERT/UPDATE/DELETEは許さない。
開発者ロール dev_role は、開発用DBでは広い権限を持つが、本番DBでは基本SELECTのみ、必要なときだけ一時的にUPDATEをGRANTし、終わったらREVOKEする。
運用ロール ops_role は、バックアップ・リストア・メンテナンスに必要な権限を持つが、アプリデータの通常操作には使わない。

これだけでも、「スーパーユーザーで本番に接続している」「アプリが何でもできる」という状態からは、かなり前進です。
大規模サービスなら、ここに「テナントごとのロール」「監査専用ロール」「レポート専用ロール」などが増えていきますが、
Day26の時点では、「ロールと権限を“攻撃前提で設計する”」という感覚が持てていれば十分です。


Day26 後半のまとめ

権限管理のプロレベル運用では、「最小権限の原則」をPostgreSQL目線で具体化し、ロール作成時にSUPERUSERやCREATEROLEを付けないことから始めて、「スキーマを権限ゾーンとして設計し、app/audit/adminのように世界を分けたうえで、アプリ用ロールにはappだけを書き換えられる権限、auditは読み取りのみ、adminにはそもそも触れさせない」といった線引きを行う。
さらに、GRANTだけでなくREVOKEを運用の一部として扱い、サービスの変化に合わせて権限を減らすことも普通に行いながら、「アプリがSQLインジェクションで破られたとしても、その接続ロールが持つ権限の範囲でしか攻撃できない」ように被害の上限を設計する――この“攻撃前提で権限を決める”感覚まで持てれば、Day26 後半としてはかなり良いところまで来ている。

タイトルとURLをコピーしました