❄️

Snowflake「動的データマスキング」実践ガイド:Email・住所・電話番号のマスク方法を試してみた

に公開

😱「分析担当者にも、個人情報が丸見え…」

Snowflake でデータを一元管理していると、こんな課題に直面しませんか?

「分析のためにデータは解放したい。でも、EMAILADDRESS などの個人情報(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_ROLEANALYST_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_ROLEANALYST_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 EMAIL 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 EMAIL 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桁だけが残り、残りの数字は * に置き換わっています。
NOTENULLNULL のまま、それ以外は ********* になりました。

実例

💡 6. 【応用】その他の便利なマスキングパターン

今回は4つのパターンを試しましたが、他にも実用的なポリシーがあります。

パターンA:ハッシュ化(結合は許可したい)

個人情報そのものは見せたくないが、キーとして JOINGROUP 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) を試してみて、いかがでしたでしょうか?

  1. 元のデータは一切変更されない。
  2. CURRENT_ROLE() を使って、ロールごとに振る舞いを CASE 文で定義する。
  3. ALTER TABLE ... MODIFY COLUMN ... で列に適用する。

この3ステップだけで、データセキュリティとデータ活用を両立できるのは、非常に強力だと感じました。
特に、Email や住所、電話番号の「一部だけを見せる」といった柔軟な制御が、REGEXP_REPLACE などを駆使して実現できるのが便利ですね。

Snowflake を使っていて「この列、本当は全員に見せたくないんだよな…」と思っている列が一つでもあれば、まずはこの記事のポリシーを参考に、DDM (Enterprise Edition以上) の適用を検討してみてはいかがでしょうか。

📚 参考出典

Discussion