マスキングポリシー
マスキングポリシーは、クエリ時にカラム値を変換します。権限のあるロールには実データが表示され、それ以外にはマスクされた値が表示されます。保存されているデータ自体は変更されません。
カラムをマスクするのではなく行全体を非表示にしたい場合は、行アクセスポリシーを使用してください。
使用する場面
- カスタマーサポート — 担当者は注文を確認でき、ID は
3201**********1234のように表示される - 分析 — 集計を壊さずにメールアドレスを
***@***.comとして表示する - VARIANT ログ — 非管理者に対して
secret_key/tokenのような JSON キーを隠す - 部分的なマスキング — 確認用にカード番号の下 4 桁だけを表示する
クイックスタート
CREATE TABLE user_info (id INT, email STRING NOT NULL);
CREATE MASKING POLICY email_mask
AS (val STRING)
RETURNS STRING ->
CASE
WHEN is_role_in_session('managers') THEN val
ELSE '*********'
END;
ALTER TABLE user_info MODIFY COLUMN email SET MASKING POLICY email_mask;
INSERT INTO user_info VALUES (1, 'user@example.com');
SELECT * FROM user_info;
id | email
---|----------
1 | *********
仕組み
- クエリ時のみ —
SELECTはマスクされるが、INSERT/UPDATE/DELETEでは実際の値が使われる - カラム単位 — 1 つのカラムにつき 1 つのポリシー。テーブルをまたいで再利用可能
current_role()よりis_role_in_session()を優先することで、ユーザーがSET ROLEで回避できないようにする
例
条件付きマスキング (USING)
別のカラムに基づいてマスクします。
CREATE MASKING POLICY vip_mask
AS (val STRING, is_vip BOOLEAN)
RETURNS STRING ->
CASE
WHEN is_vip = true THEN val
ELSE '*********'
END;
ALTER TABLE user_info
MODIFY COLUMN email SET MASKING POLICY vip_mask USING (email, is_vip);
INSERT INTO user_info (id, email, is_vip) VALUES
(1, 'vip@example.com', true),
(2, 'normal@example.com', false);
SELECT * FROM user_info;
id | email | is_vip
---|-----------------|-------
1 | vip@example.com | true
2 | ********* | false
ポリシー本体で必要な場合にのみ、カラムを USING に追加してください。
VARIANT サブフィールドのマスキング
object_delete を使って特定の JSON キーを隠します。すべてのアクセスパスでマスクが適用されます(添字、パス関数、cast、json_object_keys)。
CREATE TABLE events (id INT, data VARIANT);
INSERT INTO events VALUES
(1, parse_json('{"name":"alice","content":"secret data","secret_key":"sk_123","age":30}')),
(2, parse_json('{"name":"bob","content":"private info","secret_key":"sk_456","age":25}'));
CREATE ROLE data_admin;
CREATE ROLE data_reader;
CREATE MASKING POLICY mask_variant_sensitive
AS (val VARIANT) RETURNS VARIANT ->
CASE
WHEN is_role_in_session('data_admin') OR is_role_in_session('account_admin') THEN val
ELSE object_delete(val, 'content', 'secret_key')
END;
ALTER TABLE events MODIFY COLUMN data SET MASKING POLICY mask_variant_sensitive;
GRANT SELECT ON default.events TO ROLE data_admin;
GRANT SELECT ON default.events TO ROLE data_reader;
SET ROLE data_reader;
SELECT data FROM events;
-- {"age":30,"name":"alice"}
SELECT data['content'] FROM events; -- NULL
SELECT data['name'] FROM events; -- "alice"
SELECT json_path_query_first(data, '$.content'); -- NULL
SELECT data::STRING FROM events; -- {"age":30,"name":"alice"}
SELECT json_object_keys(data) FROM events; -- ["age","name"]
SELECT * FROM events WHERE data['content'] IS NOT NULL;
-- empty
ネストされたキーの場合:
ELSE delete_by_keypath(val, 'nested:secret')
読み取り / 書き込みの動作
INSERT INTO user_info VALUES (2, 'admin@example.com'); -- stores real email
SELECT * FROM user_info WHERE id = 2; -- returns *********
ポリシーの管理
DESCRIBE MASKING POLICY email_mask;
ALTER TABLE user_info MODIFY COLUMN email UNSET MASKING POLICY;
DROP MASKING POLICY IF EXISTS email_mask;
DROP MASKING POLICY の前に、すべてのカラムとのバインドを解除してください。バインドは POLICY_REFERENCES(POLICY_NAME => 'email_mask') で確認できます。
マスキングと行アクセスの比較
1 つのカラムに両方のポリシーを同時に設定できません。行は表示したままにしたい場合はマスキングを使用し、行自体を見えなくしたい場合は行アクセスを使用してください。
制限事項
- 1 つのカラムにつき 1 つのマスキングポリシー
- 戻り値の型はカラム型と一致している必要がある
- カラムを変更または削除する前にポリシーを解除する必要がある
- いずれかのテーブルから参照されているポリシーは削除できない
CREATE OR REPLACE MASKING POLICYはない — 削除して再作成する- 一時テーブル、ビュー、ストリームではサポートされない
- ポリシー名は、マスキングポリシーと行アクセスポリシー全体でグローバルに一意である必要がある
- ポリシー引数名は作成時に小文字化される
ベストプラクティス
current_role()よりis_role_in_session()を優先してください。USINGは最小限に保ち、ポリシー本体で必要なカラムだけを含めてください。- アプリケーションが
LENGTH/LIKEを呼び出す場合は、型に整合したプレースホルダー(メールアドレスなら***@***.com)を返してください。 - VARIANT では、値全体をマスクするのではなく
object_delete/delete_by_keypathを使用してください。 - 削除前にバインドを解除し、適用後は制限付きロールで確認してください。
権限と参照
- ポリシーを作成するには、
*.*に対するCREATE MASKING POLICYが必要です(作成者には OWNERSHIP が付与されます) - アタッチ/デタッチするには、グローバル
APPLY MASKING POLICYまたはAPPLY ON MASKING POLICY <name>が必要です - 監査:
SHOW GRANTS ON MASKING POLICY <name>
以下も参照してください。