この記事では、行セキュリティポリシーの関連コンテンツを紹介します。
説明
GRANTを通じて利用可能なSQL標準の特権システムに加えて、テーブルには、通常のクエリで返される行、またはデータ変更コマンドで挿入、更新、または削除される行をユーザーごとに制限する行セキュリティポリシーを設定できます。 この機能は、行レベルセキュリティとも呼ばれます。 既定では、テーブルにはポリシーがありません。そのため、ユーザーがSQL特権システムに従ってテーブルへのアクセス特権を持っている場合、その中のすべての行をクエリまたは更新に等しく使用できます。
テーブルで行セキュリティが有効になっている場合 (ALTER tableを使用して... 行レベルのセキュリティを有効にする場合、行を選択または行を変更するためのテーブルへのすべての通常のアクセスは、行セキュリティポリシーによって許可されている必要があります。 (ただし、テーブルの所有者は通常、行セキュリティポリシーの対象にはなりません。) テーブルにポリシーが存在しない場合、default-denyポリシーが使用されます。つまり、行は表示されず、変更できません。 TRUNCATE
やREFERENCES
など、テーブル全体に適用される操作は、行セキュリティの対象にはなりません。
行セキュリティポリシーは、コマンド、ロール、またはその両方に固有のものにすることができます。 ポリシーは、ALL
コマンド、SELECT
、INSERT
、UPDATE
、またはDELETE
に適用するように指定できます。 複数のロールを特定のポリシーに割り当てることができ、通常のロールのメンバーシップと継承のルールが適用されます。
ポリシーに従って表示または変更可能な行を指定するには、ブール値の結果を返す式が必要です。 この式は、ユーザーのクエリからの条件または関数の前に、行ごとに評価されます。 (このルールの唯一の例外は、情報を漏洩しないことが保証されている漏洩防止
関数であり、オプティマイザは、行セキュリティチェックの前にそのような関数を適用することを選択することができる。) 式がtrue
を返さない行は処理されません。 別個の式を指定して、可視の行と変更が可能な行とを独立して制御することができる。 ポリシー式は、クエリの一部として、クエリを実行するユーザーの特権で実行されますが、セキュリティ定義関数を使用して、呼び出し元のユーザーが使用できないデータにアクセスできます。
BYPASSRLS
属性を持つスーパーユーザーとロールは、テーブルにアクセスするときに常に行セキュリティシステムをバイパスします。 テーブルの所有者は通常、行のセキュリティもバイパスしますが、テーブルの所有者はALTER Tableを使用して行のセキュリティの対象とすることができます... FORCE ROWレベルセキュリティ
行セキュリティの有効化と無効化、およびテーブルへのポリシーの追加は、常にテーブル所有者のみの特権です。
ポリシーは、CREATE POLICYコマンドを使用して作成され、ALTER POLICYコマンドを使用して変更され、DROP POLICYコマンドを使用して削除されます。 特定のテーブルの行セキュリティを有効または無効にするには、ALTER tableコマンドを使用します。
各ポリシーには名前があり、テーブルに対して複数のポリシーを定義できます。 ポリシーはテーブル固有であるため、テーブルの各ポリシーには一意の名前が必要です。 異なるテーブルが同じ名前のポリシーを持つ場合があります。
複数のポリシーが特定のクエリに適用される場合、それらはOR
(デフォルトである許容ポリシーの場合) またはAND
(制限ポリシーの場合) のいずれかを使用して結合されます。 これは、特定のロールがメンバーであるすべてのロールの特権を持つというルールに似ています。 許容ポリシーと制限ポリシーについては、以下でさらに説明します。
簡単な例として、マネージャー
ロールのメンバーのみが行にアクセスでき、アカウントの行のみにアクセスできるように、アカウント
リレーションにポリシーを作成する方法を次に示します。
CREATE TABLEアカウント (マネージャテキスト、会社テキスト、contact_emailテキスト);
ALTER TABLEアカウントはROWレベルセキュリティを有効にします。CREATE POLICY account_managers ON accounts TO manager
USING (manager = current_user);
上記のポリシーは、USING
句と同じWITH CHECK
句を暗黙的に提供するため、コマンドによって選択された行の両方に制約が適用されます (したがって、マネージャーはSELECT
、UPDATE
、またはDELETE
別のマネージャに属する既存の行) およびコマンドによって変更された行 (異なるマネージャに属する行はINSERT
またはUPDATE
を介して作成できません) 。
ロールが指定されていない場合、または特別なユーザー名PUBLIC
が使用されている場合、ポリシーはシステム上のすべてのユーザーに適用されます。 すべてのユーザーがusers
テーブル内の自分の行にのみアクセスできるようにするには、簡単なポリシーを使用できます。
POLICY user_policyの作成
USING (user_name = current_user);
これは前の例と同様に機能します。
テーブルに追加されている行に対して、表示されている行とは異なるポリシーを使用するには、複数のポリシーを組み合わせることができます。 このポリシーのペアでは、すべてのユーザーがusers
テーブルのすべての行を表示できますが、独自の行を変更するだけです。
POLICYを作成ユーザーのuser_sel_policy
選択のため
USING (true);
ユーザーのPOLICY user_mod_policyの作成
USING (user_name = current_user);
SELECTコマンドでは、これら2つのポリシーはORを使用して結合され、すべての行を選択できるという効果があります。 他のコマンドタイプでは、2番目のポリシーのみが適用されるため、効果は以前と同じです。
行セキュリティは、ALTER TABLE
コマンドで無効にすることもできます。 行セキュリティを無効にしても、テーブルで定義されているポリシーは削除されません。 テーブル内のすべての行が表示され、標準のSQL特権システムに従って変更可能になります。
以下は、本番環境でこの機能を使用する方法のより大きな例です。 テーブルpasswd
はUnixパスワードファイルをエミュレートします。
-- 単純なpasswdファイルベースの例
テーブルの作成passwd (
user_nameテキストUNIQUE NOT NULL、
pwhashテキスト、
uid int PRIMARYキー、
gid int NOT NULL,
real_nameテキストNULLではなく、
home_phoneテキスト、
extra_infoテキスト、
home_dirテキストNOT NULL,
シェルテキストNOT NULL
);
CREATE ROLE admin; -- 管理者
ROLE bobを作成します。-通常のユーザー
ROLE aliceを作成します。-通常のユーザー
-テーブルを作成する
passwdの価値に挿入して下さい
('admin' 、'xxx' 、0,0、'Admin' 、'111-222-3333 '、null、'/root' 、'/bin/dash');
passwdの価値に挿入して下さい
('bob' 、'xxx' 、1,1、'Bob' 、'123-456-7890 '、null、'/home/bob' 、'/bin/zsh');
passwdの価値に挿入して下さい
('alice' 、'xxx' 、2,1、'Alice' 、'098-765-4321 '、null、'/home/alice' 、'/bin/zsh');
-テーブルで行レベルのセキュリティを有効にしてください
ALTER TABLE passwd ENABLE ROWレベルセキュリティ;
-- ポリシーの作成
-管理者はすべての行を表示し、行を追加できます
CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
-通常のユーザーはすべての行を表示できます
CREATE POLICY all_view ON passwd FOR SELECT USING (true);
-通常のユーザーは自分のレコードを更新できますが、
-通常のユーザーが設定できるシェルを制限する
更新のためのpasswdでPOLICY user_modを作成する
USING (current_user = user_name)
チェック付き (
current_user = user_name AND
シェルIN ('/bin/bash' 、'/bin/sh' 、'/bin/dash' 、'/bin/zsh' 、'/bin/zsh' 、'/bin/tcsh')
);
-- 管理者のすべての通常の権限を許可する
選択、挿入、更新、削除を許可します。
-ユーザーは公開列でのみ選択アクセスを取得します
選択を許可
(user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)
ON passwd TO public;
-ユーザーが特定の列を更新できるようにする
許可の更新
(pwhash、real_name、home_phone、extra_info、shell)
ON passwd TO public;
他のセキュリティ設定と同様に、システムが期待どおりに動作していることをテストして確認することが重要です。 上記の例を使用すると、これは許可システムが適切に機能していることを示します。
-- adminはすべての行とフィールドを表示できます
postgres=> set role admin;
セット
postgres=> テーブルpasswd;
user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | シェル
----------- -------- -----------------------------------------------------------------------------------
admin | xxx | 0 | 0 | 管理者 | 111-222-3333 | | /ルート | /ビン /ダッシュ
ボブ | xxx | 1 | 1 | ボブ | 123-456-7890 | | /home/bob | /bin/zsh
alice | xxx | 2 | 1 | アリス | 098-765-4321 | | /home /Alice | /bin/zsh
(3行)
-アリスができることをテストする
postgres=> set role alice;
セット
postgres=> テーブルpasswd;
エラー: テーブルpasswdの許可が拒否されました
postgres=> passwdからuser_name、real_name、home_phone、extra_info、home_dir、shellを選択します。
user_name | real_name | home_phone | extra_info | home_dir | シェル
--------------------------------------------------------------------------------------------
admin | 管理者 | 111-222-3333 | | /ルート | /ビン /ダッシュ
ボブ | ボブ | 123-456-7890 | | /ホーム /ボブ | /bin/zsh
alice | アリス | 098-765-4321 | | /home /Alice | /bin/zsh
(3行)
postgres=> update passwd set user_name = 'joe';
エラー: テーブルpasswdの許可が拒否されました
-アリスは自分のreal_nameを変更できますが、他の人は変更できません
postgres=> update passwd set real_name = 'Alice Doe';
更新1
postgres=> update passwd set real_name='JohnDo' (user_name = 'admin);
更新0
postgres=> update passwd set shell = '/bin/xx';
エラー: 新しい行は「passwd」のWITH CHECKオプションに違反します
postgres=> passwdから削除します。エラー: テーブルpasswdの許可が拒否されました
postgres=> passwd (user_name) 値 ('xxx') に挿入します。エラー: テーブルpasswdの許可が拒否されました
-アリスは自分のパスワードを変更できます。RLSは他の行の更新を静かに防ぎます
postgres=> update passwd set pwhash = 'abc';
更新1
これまでに構築されたすべてのポリシーは許容ポリシーであり、複数のポリシーが適用されると、「OR」ブール演算子を使用して組み合わされることを意味します。 許容ポリシーは、意図された場合に行へのアクセスのみを許可するように構築することができるが、許容ポリシーを制限ポリシー (レコードが通過しなければならず、「and」ブール演算子を使用して結合される) と結合する方がより簡単であり得る。 上記の例に基づいて、passwd
テーブルのレコードにアクセスするために、管理者がローカルのUnixソケットを介して接続することを要求する制限ポリシーを追加します。
CREATE POLICY admin_local_only ON passwd管理者の制限として
USING (pg_catalog.inet_client_addr() はNULL);
次に、ネットワーク経由で接続している管理者は、制限されたポリシーのためにレコードを表示しないことがわかります。
=> SELECT current_user;
current_user
--------------
admin
(1行)
=> select inet_client_addr();
inet_client_addr
------------------
127.0.0.1
(1行)
=> SELECT current_user;
current_user
--------------
admin
(1行)
=> テーブルpasswd;
user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | シェル
-----------+--------+-----+-----+-----------+------------+------------+----------+-------
(0 rows)
=> UPDATE passwdセットpwhash = NULL;
更新0
一意または主キー制約や外部キー参照などの参照整合性チェックは、常に行のセキュリティをバイパスして、データ整合性が維持されるようにします。 スキーマおよび行レベルポリシーを開発するときは、そのような参照整合性チェックによる情報の「秘密チャネル」漏洩を回避するように注意する必要があります。
コンテキストによっては、行セキュリティが適用されていないことを確認することが重要です。 たとえば、バックアップを取得するときに、行のセキュリティによって一部の行がバックアップから削除された場合、悲惨なことになる可能性があります。 このような状況では、row_security
設定パラメーターをoff
に設定できます。 これ自体は行のセキュリティをバイパスするものではありません。クエリの結果がポリシーによってフィルタリングされた場合にエラーがスローされます。 次に、エラーの理由を調査して修正することができます。
上記の例では、ポリシー式は、アクセスまたは更新される行の現在の値のみを考慮します。 これは最も単純で最もパフォーマンスの高いケースです。可能な場合は、このように動作するように行セキュリティアプリケーションを設計することをお勧めします。 他の行または他のテーブルを参照してポリシーを決定する必要がある場合は、ポリシー式でサブSELECT
またはSELECT
を含む関数を使用して実行できます。 ただし、このようなアクセスは、注意を払わないと情報が漏洩する可能性のある競合状態を引き起こす可能性があることに注意してください。 例として、次のテーブルデザインを考えてみましょう。
-- 特権グループの定義
CREATE TABLEグループ (group_id int PRIMARY KEY,
group_nameテキストNULLではない);
グループ値に挿入する
(1、'low ') 、
(2、'medium') 、
(5、「高」);
aliceにすべてのグループを承認します。-- aliceは管理者です
一般にグループを選択してください。-ユーザーの特権レベルの定義
CREATE TABLEユーザー (user_nameテキストPRIMARY KEY,
group_id int NOT NULL REFERENCESグループ);
ユーザーの値に挿入する
('alice' 、5) 、
('bob' 、2) 、
('mallory' 、2);
すべてのユーザーを認可してください。ユーザーの選択を承認して公開する。-保護する情報を保持しているテーブル
テーブル情報の作成 (infoテキスト、
group_id int NOT NULL REFERENCESグループ);
情報値に挿入する
(「かろうじて秘密」、1) 、
(「わずかに秘密」、2) 、
('非常に秘密' 、5);
ALTER TABLE情報有効な行レベルセキュリティ;
-セキュリティgroup_idが
-- 行のgroup_id以上
SELECTのポリシーfp_sオン情報を作成する
USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
更新のための情報についてポリシーを作成するfp_u
USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
-- 私達は情報テーブルを保護するためにRLSにだけ頼る
すべての情報を公開します。
アリス
が「わずかに秘密の」情報を変更したいが、その行の新しいコンテンツでマロリー
を信頼すべきではないと判断したとします。
BEGIN;
UPDATEユーザーSET group_id = 1 WHERE user_name = 'mallory';
UPDATE情報SET info = 'secret from mallory' WHERE group_id = 2;
コミット;
それは安全に見えます。マロリー
が「マロリーからの秘密」文字列を見ることができるはずのウィンドウはありません。 ただし、ここにはレース条件があります。 マロリー
が同時にやっている場合は、
SELECT * FROM情報WHERE group_id = 2 FOR UPDATE;
彼女の取引はREAD COMMITTED
モードであるため、彼女は「malloryからの秘密」を見ることができます。 それは、彼女のトランザクションがアリス
の直後に情報
行に到達した場合に発生します。 alice
のトランザクションがコミットするのをブロックし、for UPDATE
句のおかげで更新された行の内容をフェッチします。 ただし、サブSELECT
にはfor UPDATE
がなかったため、暗黙的なSELECT
の更新行をusers
から取得しません。代わりに、users
行はクエリの開始時に取得されたスナップショットで読み取られます。 したがって、ポリシー式は、mallory
の特権レベルの古い値をテストし、更新された行を表示できるようにします。
この問題にはいくつかの方法があります。 1つの簡単な答えは、SELECTを使用することです... FOR SHARE
in sub-SELECT
in rowセキュリティポリシー。 ただし、影響を受けるユーザーに参照テーブル (ここではユーザー
) のUPDATE
権限を付与する必要があります。 (ただし、別の行セキュリティポリシーを適用して、実際にその特権を行使しないようにすることもできます。または、サブSELECT
をセキュリティ定義関数に埋め込むこともできます。) また、参照されるテーブルに対する行共有ロックの大量の同時使用は、特にその更新が頻繁である場合、パフォーマンスの問題を引き起こす可能性があります。 参照されたテーブルの更新が頻繁でない場合に実用的な別の解決策は、参照されたテーブルを更新するときにACCESS EXCLUSIVE
ロックを使用して、古い行の値を調べることができないようにすることです。 または、参照されるテーブルの更新をコミットした後、新しいセキュリティ状況に依存する変更を行う前に、すべての同時トランザクションが終了するのを待つだけでもよい。