データベースは機密情報が格納される場所であり、その重要性からできるだけ限定されたユーザだけがアクセスできるようにしたいと考えるのは当然です。一歩進めて、理想的にはデータベースへのアクセスに必要なユーザを静的に設定するのではなく、都度必要に応じて動的に生成したいという考え方があります。
しかしながら、動的にユーザを払い出すためには、その都度ユーザに与える権限を設定するという手間が生じます。このプロセスを効率化するために、具体例として「スキーマ内の全テーブルに対して読み取り専用の権限をもつユーザ」を効率よく作成する方法を考えてみましょう。
実装
PostgreSQLには「ROLE」と「USER」という2つの概念が存在します。実質的にはこれら2つは機能的にほとんど一緒であり、ユーザー権限とアクセス制御を管理します。ただし、ここでは説明をわかりやすくするため、少し異なる意味合いでこれら2つの用語を使用します。「ROLE」は権限の定義に用い、一方「USER」はログイン権限を持つ具体的なユーザとして扱います。
権限としてのROLEの定義
CREATE USERはCREATE ROLEの別名になりました
まずは、スキーマ内の全テーブルに対する読み取り権限をROLE ro
として定義しましょう。それはおそらく次のような形になるでしょう。
/* * ro ロールは、どのテーブルの参照も可能な権限を持つ */ CREATE ROLE ro NOINHERIT NOBYPASSRLS NOLOGIN; -- スキーマを利用する権限を付与 GRANT USAGE ON SCHEMA my_schema TO ro; -- スキーマ配下の全テーブルに対する参照権限を付与 GRANT SELECT ON ALL TABLES IN SCHEMA my_schema TO ro; ALTER DEFAULT PRIVILEGES IN SCHEMA my_schema GRANT SELECT ON TABLES TO ro;
このSQLスクリプトでは、まずro
というROLEを作成し、次にこのROLEに特定のスキーマ(my_schema
)を使用する権限と、そのスキーマ内の全テーブルの読み取り権限を付与しています。最後に、このスキーマ内で将来作成される全てのテーブルに対するデフォルトの読み取り権限をro
に付与しています。
ここで作成したro
というROLEはログイン権限を持たず、そのため直接データベースへはログインできません。それは、このROLEにはログインするためのパスワードが存在しないからです。
次に進むステップとしては、このroというROLEを持つユーザーを作成する機能を設定すれば良いです。
「ユーザを作成することだけできる」ユーザの作成
「ユーザを作成」するユーザ自身が任意の操作が可能だと、セキュリティリスクが高まります。そのため、ここではユーザ作成のみを許可する専用のユーザ、admin
を設定します。
/* adminは、ユーザを作成する能力を持つが、自身はテーブルの参照等はできない */ CREATE USER admin CREATEROLE NOINHERIT NOBYPASSRLS PASSWORD 'postgres'; ALTER USER admin SET search_path TO my_schema;
ここで作成したadmin
ユーザには、明示的にGRANTコマンドによる権限付与がなされていないため、データベースにログインしてもテーブル参照等の操作は実行できません。
$ psql -h localhost -U admin -d kiririmode kiririmode=> select * from my_schema.parents; ERROR: permission denied for schema my_schema LINE 1: select * from my_schema.parents;
ROLEを継承したユーザを有効期限付きで作成
次に、上で作成したadmin
ユーザを使用して、`ro`` ROLEを継承する新しいユーザーを作成してみましょう。
kiririmode=> CREATE USER ro_kiririmode PASSWORD 'kiririmode' VALID UNTIL '2023-07-17 18:00:00 JST' IN ROLE ro; CREATE ROLE kiririmode=> ALTER USER ro_kiririmode SET search_path TO my_schema; ALTER ROLE kiririmode=>
ここではVALID UNTIL
オプションを使用して、パスワードが2023-07-17 18:00:00 JST
まで有効なユーザーを作成しています。これにより、その時刻を過ぎるとユーザーのアクセス権限は自動的に失効します。
VALID UNTIL 'timestamp'
VALID UNTIL句は、ロールのパスワードが無効になる日時を設定します。 この句が省略された場合、パスワードは永遠に有効になります。
さらに、IN ROLE rolename
を指定することで、作成するユーザーが指定したrolenameのROLEに所属します。結果として、新しく作成したユーザro_kiririmode
はro
というROLEが持つ権限を継承できます。
このユーザro_kiririmode
を使ってみると、VALID UNTIL
で指定した18:00までにデータベースにログインすれば、テーブルの参照が可能になっていることが確認できます。
❱ ❱ date; psql -h localhost -U ro_kiririmode -d kiririmode 月 7 17 17:58:44 JST 2023 Password for user ro_kiririmode: psql (14.8 (Homebrew), server 15.3 (Debian 15.3-1.pgdg110+1)) WARNING: psql major version 14, server major version 15. Some psql features might not work. Type "help" for help. kiririmode=> select * from parents; id | name ----+-------------- 1 | ウルトラの父 2 | ウルトラの母 (2 rows)
逆にinsertはエラーになりますね。想定通りです。
kiririmode=> insert into parents(id, name) values (3, 'hoge'); ERROR: permission denied for table parents
一方で、新規レコードの挿入(INSERT)を試みるとエラーが発生します。これは我々が想定していた通りの挙動です。なぜなら、ro
というROLEには読み取り専用の権限(SELECT)しか付与していないため、データの挿入や更新などの書き込み操作は許可されていないからです。
$ ❱ date; psql -h localhost -U ro_kiririmode -d kiririmode 月 7 17 18:00:33 JST 2023 Password for user ro_kiririmode: psql: error: connection to server at "localhost" (::1), port 5432 failed: FATAL: password authentication failed for user "ro_kiririmode"
まとめ
上記の通り、事前に必要な権限を持つROLEを定義し、ユーザ作成時にそのROLEを継承するアプローチは効率的です。ただし、このままでは不要なユーザが蓄積されてしまい、セキュリティ上の懸念が生じます。したがって、定期的なユーザの棚卸しは必要でしょう。
有効期限が切れたユーザを特定するためには、以下のSQLクエリを使用できます。
kiririmode=# SELECT usename FROM pg_shadow WHERE valuntil < CURRENT_TIMESTAMP; usename --------------- ro_kiririmode
この結果をもとに、不要なユーザを削除できます。
kiririmode=# DROP USER ro_kiririmode; DROP ROLE
このようにして不要なユーザを適宜削除することで、セキュリティを保ちながらデータベースを適切に管理できます。