Snowflake「動的データマスキング」実践ガイド:Email・住所・電話番号のマスク方法を試してみた
😱「分析担当者にも、個人情報が丸見え…」
Snowflake でデータを一元管理していると、こんな課題に直面しませんか?
「分析のためにデータは解放したい。でも、EMAIL や ADDRESS などの個人情報(PII)を、そのまま見せるわけにはいかない…」
「かといって、マスキング済みのテーブルを別途作成(二重管理)するのは、コストも手間もかかる…」
まさに、データ活用とセキュリティのジレンマですよね。
Snowflake には、この問題をスマートに解決する「動的データマスキング (Dynamic Data Masking: DDM)」という機能が備わっています。
この記事では、DDM がどのような機能なのか、そして実際に4つの実用的なマスキングポリシー(一般、Email、住所、電話番号)を作成し、「ロール(役割)によってどう見え方が変わるか」 を検証してみた結果を詳しく解説します。
🛡️ 1. 動的データマスキング (DDM) とは?
DDM は、テーブルに保存されている元のデータは一切変更せず、クエリが実行された「瞬間」に、その人のロールに基づいてデータを動的にマスク(変換)する機能です。
アナロジー:「特別なメガネ」
この仕組みは、「特別なメガネ」に例えると分かりやすいかもしれません。
-
元のテーブルデータ: 「高橋さん /
takahashi@example.com/ 東京都...」 -
ANALYST_ROLE(分析ロール):- 「ぼかしメガネ」をかけている。
-
SELECT *を実行すると、「高橋さん /****@example.com/ 東京都****」のようにマスクされた結果が見える。
-
MASK_ROLE(権限ロール):- 「クリアなメガネ」をかけている。
-
SELECT *を実行すると、「高橋さん /takahashi@example.com/ 東京都...」のように元のデータがそのまま見える。
DDM の最大のメリットは、データを二重管理する必要がないことです。1つのテーブルに対して、見る人(ロール)の権限に応じて、返すデータをリアルタイムで制御できるのです。
🛠️ 2. 検証環境のセットアップ
まずは、DDM の動きを検証するための環境を準備します。
ステップ1: 3つのロールを作成
今回は、データが「見える」ロール、「見えない」ロール、そして「ポリシーを管理する」ロールの3つを用意します。
-- (ACCOUNTADMIN など上位のロールで実行)
CREATE ROLE IF NOT EXISTS MASK_ROLE; -- データが「見える」特権ロール
CREATE ROLE IF NOT EXISTS ANALYST_ROLE; -- データが「マスクされる」一般ロール
CREATE ROLE IF NOT EXISTS POLICY_ADMIN; -- ポリシーを管理するロール
-- ロールに必要な権限を付与
GRANT ROLE MASK_ROLE TO ROLE SYSADMIN;
GRANT ROLE ANALYST_ROLE TO ROLE SYSADMIN;
GRANT ROLE POLICY_ADMIN TO ROLE SYSADMIN;
-- ウェアハウスとDB/スキーマの利用権限
GRANT USAGE ON WAREHOUSE MY_WH TO ROLE MASK_ROLE;
GRANT USAGE ON WAREHOUSE MY_WH TO ROLE ANALYST_ROLE;
GRANT USAGE ON WAREHOUSE MY_WH TO ROLE POLICY_ADMIN;
GRANT USAGE ON DATABASE MY_DB TO ROLE POLICY_ADMIN;
GRANT USAGE ON SCHEMA MY_DB.MY_SCHEMA TO ROLE POLICY_ADMIN;
-- POLICY_ADMIN に、テーブル作成権限とポリシー作成・適用権限を付与
GRANT CREATE TABLE ON SCHEMA MY_DB.MY_SCHEMA TO ROLE POLICY_ADMIN;
GRANT CREATE MASKING POLICY ON SCHEMA MY_DB.MY_SCHEMA TO ROLE POLICY_ADMIN;
GRANT APPLY MASKING POLICY ON ACCOUNT TO ROLE POLICY_ADMIN;
ステップ2: サンプルテーブルとデータの作成
POLICY_ADMIN ロールで、検証用の個人情報(PII)テーブルを作成します。
-- (POLICY_ADMIN ロールで実行)
USE ROLE POLICY_ADMIN;
USE WAREHOUSE MY_WH;
USE DATABASE MY_DB;
USE SCHEMA MY_SCHEMA;
CREATE OR REPLACE TABLE CUSTOMER_PII (
ID INT,
NAME VARCHAR,
EMAIL VARCHAR,
ADDRESS VARCHAR,
TEL VARCHAR,
NOTE VARCHAR -- (一般マスキング用)
);
INSERT INTO CUSTOMER_PII VALUES
(1, '田中 太郎', 'taro.tanaka@example.com', '東京都千代田区丸の内1-2-3', '090-1234-5678', '重要顧客'),
(2, '鈴木 花子', 'hanako.suzuki@example.jp', '大阪府大阪市北区梅田4-5-6', '03-9876-5432', '通常顧客'),
(3, '山田 一郎', 'yamada@test.co.jp', '福岡県福岡市中央区天神7-8-9', '0120-111-222', NULL),
(4, '佐藤 次郎', 'jiro@sample.com', '北海道札幌市中央区北1条西1-1', '011-222-3333', 'VIP'),
(5, '高橋 三郎', 'saburo@demo.net', '沖縄県那覇市おもろまち1-2-3', '050-444-5555', 'Test');
ステップ3: ロールへの参照権限付与
MASK_ROLE と ANALYST_ROLE が、このテーブルを SELECT できるようにします。
-- (POLICY_ADMIN または SYSADMIN で実行)
GRANT USAGE ON DATABASE MY_DB TO ROLE MASK_ROLE;
GRANT USAGE ON SCHEMA MY_SCHEMA TO ROLE MASK_ROLE;
GRANT SELECT ON TABLE CUSTOMER_PII TO ROLE MASK_ROLE;
GRANT USAGE ON DATABASE MY_DB TO ROLE ANALYST_ROLE;
GRANT USAGE ON SCHEMA MY_SCHEMA TO ROLE ANALYST_ROLE;
GRANT SELECT ON TABLE CUSTOMER_PII TO ROLE ANALYST_ROLE;
これで準備完了です。
現時点では、どちらのロールでもすべてのデータが丸見えのはずです。
🎭 3. 4つのマスキングポリシーを作成する
いよいよ、この記事の核となるマスキングポリシーを作成します。
ポリシーは「CASE 文」で記述するのが基本です。
USE ROLE POLICY_ADMIN; で実行します。
ポリシー1:一般(全部隠す ※NULLを除く)
NOTE 列用。MASK_ROLE 以外には、すべて ********* で隠します。
NULL 値は NULL のまま表示させるのがポイントです。
-- マスキングポリシーの作成(一般)
CREATE OR REPLACE MASKING POLICY test_policy AS (val VARCHAR)
RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
WHEN val IS NULL THEN NULL -- NULLはそのまま NULL を返す
ELSE '*********'
END;
ポリシー2:Email(ドメイン以降は見せる ※NULL対応)
EMAIL 列用。MASK_ROLE 以外には、@ より前を **** に置換します。
-- マスキングポリシーの作成(EMAIL)
CREATE OR REPLACE MASKING POLICY test_email_policy AS (val VARCHAR)
RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
WHEN val IS NULL THEN NULL -- NULLはそのまま NULL を返す
ELSE REGEXP_REPLACE(val, '.+\@', '****@') -- @より前を置換
END;
ポリシー3:住所(都道府県のみ見せる)
ADDRESS 列用。MASK_ROLE 以外には、都道府県名だけを抽出し、残りを **** にします。
(COALESCE は、都道府県名が抽出できなかった場合(例: 海外住所など)に **** を返すためのセーフティネットです)
-- マスキングポリシーの作成(住所)
CREATE OR REPLACE MASKING POLICY test_address_policy AS (val VARCHAR)
RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
ELSE COALESCE(
-- 正規表現で都道府県名のみを抽出
REGEXP_SUBSTR(val,'(北海道|青森県|岩手県|宮城県|秋田県|山形県|福島県|茨城県|栃木県|群馬県|埼玉県|千葉県|東京都|神奈川県|新潟県|富山県|石川県|福井県|山梨県|長野県|岐阜県|静岡県|愛知県|三重県|滋賀県|京都府|大阪府|兵庫県|奈良県|和歌山県|鳥取県|島根県|岡山県|広島県|山口県|徳島県|香川県|愛媛県|高知県|福岡県|佐賀県|長崎県|熊本県|大分県|宮崎県|鹿児島県|沖縄県)')
|| '****', -- 抽出した都道府県名の後ろに **** を連結
'****' -- もし都道府県名がマッチしなかった場合のデフォルト
)
END;
ポリシー4:電話番号(下4桁のみ見せる ※安全対策版)
TEL 列用。MASK_ROLE 以外には、数字部分の下4桁だけを残します。
数字が4桁未満の場合は REPEAT 関数がエラーになるため、事前に分岐で弾きます。
-- マスキングポリシーの作成(TEL)
CREATE OR REPLACE MASKING POLICY test_tel_policy AS (val VARCHAR)
RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
WHEN val IS NULL OR val = '' THEN val
-- 数字が4桁未満の場合は、数字だけ全部 * にする (REPEATエラー回避)
WHEN REGEXP_COUNT(val, '[0-9]') < 4 THEN REGEXP_REPLACE(val, '[0-9]', '*')
ELSE
-- (1) 数字の数から4を引いた数だけ '*' を生成
REPEAT('*', REGEXP_COUNT(val, '[0-9]') - 4)
-- (2) (1)に、数字部分の右から4桁を連結
|| RIGHT(REGEXP_REPLACE(val, '[^0-9]', ''), 4)
END;
🚀 4. ポリシーをテーブルに適用する
ポリシーは作成しただけでは機能しません。ALTER TABLE ... MODIFY COLUMN ... を使って、テーブルの各列に「適用」します。
-- (POLICY_ADMIN ロールで実行)
ALTER TABLE CUSTOMER_PII MODIFY COLUMN EMAIL
SET MASKING POLICY test_email_policy;
ALTER TABLE CUSTOMER_PII MODIFY COLUMN ADDRESS
SET MASKING POLICY test_address_policy;
ALTER TABLE CUSTOMER_PII MODIFY COLUMN TEL
SET MASKING POLICY test_tel_policy;
ALTER TABLE CUSTOMER_PII MODIFY COLUMN NOTE
SET MASKING POLICY test_policy;
これで、CUSTOMER_PII テーブルの4つの列がマスキングポリシーによって保護されました。
👓 5. 【検証】ロール別に見え方を確認する
いよいよ検証です。MASK_ROLE と ANALYST_ROLE を切り替えて、同じ SELECT 文を実行してみましょう。
シナリオ1:特権ロール(MASK_ROLE)の場合
「クリアなメガネ」をかけた MASK_ROLE でデータを見てみます。
USE ROLE MASK_ROLE;
USE WAREHOUSE MY_WH;
SELECT * FROM CUSTOMER_PII ORDER BY ID;
実行結果 (MASK_ROLE):
| ID | NAME | ADDRESS | TEL | NOTE | |
|---|---|---|---|---|---|
| 1 | 田中 太郎 | taro.tanaka@example.com | 東京都千代田区丸の内1-2-3 | 090-1234-5678 | 重要顧客 |
| 2 | 鈴木 花子 | hanako.suzuki@example.jp | 大阪府大阪市北区梅田4-5-6 | 03-9876-5432 | 通常顧客 |
| 3 | 山田 一郎 | yamada@test.co.jp | 福岡県福岡市中央区天神7-8-9 | 0120-111-222 | NULL |
| 4 | 佐藤 次郎 | jiro@sample.com | 北海道札幌市中央区北1条西1-1 | 011-222-3333 | VIP |
| 5 | 高橋 三郎 | saburo@demo.net | 沖縄県那覇市おもろまち1-2-3 | 050-444-5555 | Test |
すべてのデータがそのまま見えますね。
実例

シナリオ2:一般ロール(ANALYST_ROLE)の場合
次に、「ぼかしメガネ」をかけた ANALYST_ROLE で同じクエリを実行します。
USE ROLE ANALYST_ROLE;
USE WAREHOUSE MY_WH;
SELECT * FROM CUSTOMER_PII ORDER BY ID;
実行結果 (ANALYST_ROLE):
| ID | NAME | ADDRESS | TEL | NOTE | |
|---|---|---|---|---|---|
| 1 | 田中 太郎 | ****@example.com |
東京都**** |
*******5678 |
********* |
| 2 | 鈴木 花子 | ****@example.jp |
大阪府**** |
******5432 |
********* |
| 3 | 山田 一郎 | ****@test.co.jp |
福岡県**** |
******1222 |
NULL |
| 4 | 佐藤 次郎 | ****@sample.com |
北海道**** |
******3333 |
********* |
| 5 | 高橋 三郎 | ****@demo.net |
沖縄県**** |
******5555 |
********* |
狙い通り、NAME 以外の列がすべて動的にマスクされました!
EMAIL は @ より前が、ADDRESS は都道府県以降が隠されています。
TEL はハイフンなどが含まれていても、数字部分の下4桁だけが残り、残りの数字は * に置き換わっています。
NOTE は NULL は NULL のまま、それ以外は ********* になりました。
実例

💡 6. 【応用】その他の便利なマスキングパターン
今回は4つのパターンを試しましたが、他にも実用的なポリシーがあります。
パターンA:ハッシュ化(結合は許可したい)
個人情報そのものは見せたくないが、キーとして JOIN や GROUP BY は許可したい(例: USER_ID)。
そんな時は、ハッシュ化するポリシーが有効です。
CREATE OR REPLACE MASKING POLICY test_hash_policy AS (val VARCHAR)
RETURNS VARCHAR ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
ELSE SHA2(val) -- ハッシュ値に変換
END;
パターンB:日付マスキング(年月のみ見せる)
生年月日(BIRTH_DATE)を「年」や「年月」に丸める(「年代分析」は許可する)パターンです。
CREATE OR REPLACE MASKING POLICY test_birth_year_policy AS (val DATE)
RETURNS DATE ->
CASE
WHEN CURRENT_ROLE() IN ('MASK_ROLE') THEN val
-- 1月1日生まれに丸める
ELSE DATE_FROM_PARTS(YEAR(val), 1, 1)
END;
😌 おわりに
動的データマスキング (DDM) を試してみて、いかがでしたでしょうか?
- 元のデータは一切変更されない。
-
CURRENT_ROLE()を使って、ロールごとに振る舞いをCASE文で定義する。 -
ALTER TABLE ... MODIFY COLUMN ...で列に適用する。
この3ステップだけで、データセキュリティとデータ活用を両立できるのは、非常に強力だと感じました。
特に、Email や住所、電話番号の「一部だけを見せる」といった柔軟な制御が、REGEXP_REPLACE などを駆使して実現できるのが便利ですね。
Snowflake を使っていて「この列、本当は全員に見せたくないんだよな…」と思っている列が一つでもあれば、まずはこの記事のポリシーを参考に、DDM (Enterprise Edition以上) の適用を検討してみてはいかがでしょうか。
Discussion