Snowflakeマスキングポリシーの罠:CTAS, CLONE, VIEW, MV, DTで個人情報は「どう」継承されるか
😱「完璧にマスクしたはずが、生データが漏洩していた…」
先日、私たちは動的データマスキング(DDM)を使って、個人情報(PII)をロールベースで保護する方法について記事にいたしました。
(Snowflake「動的データマスキング」実践ガイド:Email・住所・電話番号のマスク方法を試してみた)
ANALYST_ROLE で SELECT すると ****@example.com と表示され、MASK_ROLE なら生データが見える。「これで完璧だ!」と安心していました。
...しかし、ある日、悪夢が起こります。
MASK_ROLE を持つ開発者が、本番テーブルのコピーを作ろうとして、何気なく CTAS (Create Table As Select) を実行しました。
CREATE TABLE CUSTOMER_PII_BACKUP AS SELECT * FROM CUSTOMER_PII;
その後、ANALYST_ROLE の分析者が、新しくできた CUSTOMER_PII_BACKUP テーブルを SELECT してみると...
そこには、すべての「生」の個人情報が、マスクされずに表示されていました。
「なぜだ!?」
「マスキングポリシーはテーブルに付いているはずじゃ?」
そう、これこそが Snowflake の DDM を運用する上で、最も危険な「継承」の罠 です。
DDM は万能ではありません。ポリシーが「いつ適用され」「いつ無視されるか」を理解していないと、意図せず生データをコピーし、セキュリティ事故を引き起こします。
この記事では、CTAS、CLONE、VIEW、MV、DYNAMIC TABLE を実行した時、マスキングポリシーがどうなるのか、その挙動を徹底的に検証します。
🎭 大原則:「データ」と「ポリシー」は別物
まず、DDM の大原則を思い出しましょう。
DDM は、テーブルに保存されているデータ自体を書き換えるものではありません。
SELECT クエリが実行された「瞬間」に、クエリを実行した人のロールを見て、結果セットを動的に書き換える機能です。
この「クエリの実行時」というのが最大のポイントです。
CTAS (CREATE TABLE ... AS SELECT) は何をしているでしょうか?
CTAS は、「SELECT クエリを実行し、その "結果" を新しい別のテーブルに "書き込む"」コマンドです。
つまり、MASK_ROLE(生データが見えるロール)の人が CTAS を実行すると、
-
SELECT *が実行され、「生」データ の結果セットが返されます。 - その 「生」データ が、
NEW_TABLEに書き込まれます。 -
NEW_TABLEには、マスキングポリシーは一切適用されません。
これが、データ漏洩のメカニズムです。
🛠️ 検証環境のおさらい
前回の記事と同じ環境を使います。
-
テーブル:
CUSTOMER_PII-
EMAIL列にtest_email_policy(@より前を隠す)が適用済み。
-
-
ロール:
-
MASK_ROLE:CUSTOMER_PIIの生データが見える(特権ロール)。 -
ANALYST_ROLE:CUSTOMER_PIIを見るとEMAILがマスクされる(一般ロール)。
-
🕳️ 【罠】ポリシーが継承されない操作
DDM の恩恵を受けられない、危険な操作から見ていきましょう。
罠1:CTAS (CREATE TABLE ... AS SELECT)
これが最も危険な罠です。CTAS はマスキングポリシーを継承しません。
検証A:MASK_ROLE (特権ロール) で CTAS
USE ROLE MASK_ROLE; -- 生データが見えるロール
CREATE OR REPLACE TABLE PII_BACKUP_BY_ADMIN AS
SELECT * FROM CUSTOMER_PII;
検証B:ANALYST_ROLE (一般ロール) で CTAS
USE ROLE ANALYST_ROLE; -- マスクされたデータが見えるロール
CREATE OR REPLACE TABLE PII_BACKUP_BY_ANALYST AS
SELECT * FROM CUSTOMER_PII;
結果確認:ANALYST_ROLE で両方のテーブルを見る
-- (事前に PII_BACKUP_BY_ADMIN と PII_BACKUP_BY_ANALYST に対する
-- SELECT 権限が ANALYST_ROLE に付与されている前提)
USE ROLE ANALYST_ROLE;
-- 検証A (MASK_ROLE が作ったテーブル) を見る
SELECT EMAIL FROM PII_BACKUP_BY_ADMIN LIMIT 2;
-- 実行結果:
-- taro.tanaka@example.com
-- hanako.suzuki@example.jp
--
-- 😱 生データが漏洩している!ポリシーは付いていない!
-- 検証B (ANALYST が作ったテーブル) を見る
SELECT EMAIL FROM PII_BACKUP_BY_ANALYST LIMIT 2;
-- 実行結果:
-- ****@example.com
-- ****@example.jp
--
-- 😅 こちらはマスク「済み」のデータがコピーされている。
結論: CTAS は、実行したロールに見えている通りのデータを、ポリシー無しでコピーします。
つまり、新しく作成されたテーブルの列にはマスキングポリシーは付いておらず、「作成時に書き込まれた値」が常にそのまま表示されます。
管理者が CTAS を使うと、生データが漏洩します。
罠2:マテリアライズドビュー (MV) の罠 —「漏洩」ではなく「作成エラー」
「CTASがダメなら、MVでパフォーマンスを上げよう」と考えるかもしれません。
しかし、ここには別の罠(仕様上の制限)があります。
検証:MASK_ROLE (特権ロール) で MV を作成
USE ROLE MASK_ROLE; -- 生データが見えるロール
-- 「EMAIL」列(マスク適用済み)を含む MV を作成しようと試みる
CREATE OR REPLACE MATERIALIZED VIEW PII_MV AS
SELECT ID, EMAIL FROM CUSTOMER_PII WHERE ID < 3;
実行結果:
SQL compilation error:
Unsupported feature 'CREATE ON MASKING POLICY COLUMN'.
結論: CTAS とは異なり、Snowflake はこの操作を許可しません。
ベーステーブルの列にマスキングポリシーが適用されている場合、その列を含むマテリアライズドビューを作成することは、仕様上ブロックされます。
罠3:動的テーブル (Dynamic Table) の罠
DYNAMIC TABLE (DT) は、MV とは異なり DDM との共存が可能ですが、CTAS と同様にポリシーを自動で継承しません。
検証:MASK_ROLE (特権ロール) で DT を作成
USE ROLE MASK_ROLE; -- 生データが見えるロール
USE WAREHOUSE MY_WH;
-- 生データが見えるロールで DT を作成
CREATE OR REPLACE DYNAMIC TABLE PII_DT
TARGET_LAG = '1 minute' -- LAG ではなく TARGET_LAG が正しい構文
WAREHOUSE = MY_WH
AS
SELECT ID, EMAIL FROM CUSTOMER_PII WHERE ID < 3;
-- DT の初回リフレッシュを手動で実行
ALTER DYNAMIC TABLE PII_DT REFRESH;
結果確認:ANALYST_ROLE で DT を見る
-- (事前に PII_DT に対する SELECT 権限が
-- ANALYST_ROLE に付与されている前提)
USE ROLE ANALYST_ROLE;
-- ANALYST_ROLE で DT をクエリ
SELECT EMAIL FROM PII_DT;
-- 実行結果:
-- taro.tanaka@example.com
-- hanako.suzuki@example.jp
--
-- 😱 ANALYST_ROLE なのに、生データが見えてしまった!
結論: DYNAMIC TABLE は、CTAS と同様にオーナー(作成者)の権限で見えるデータを物理的に実体化します。
ベーステーブルの DDM ポリシーは、新しく作られた DT の列には自動でコピーされません。
✅ 安全な操作:ポリシーが正しく継承される方法
では、どうすれば安全にオブジェクトを複製・参照できるのでしょうか?
安全策1:CLONE (テーブルの完全複製)
テーブルのデータとメタデータ(ポリシー設定含む)を丸ごとコピーしたい場合は、CLONE を使います。
検証:MASK_ROLE で CLONE
USE ROLE MASK_ROLE; -- CLONE には元のテーブルの SELECT 権限が必要
-- CTAS ではなく CLONE を使用
CREATE OR REPLACE TABLE CUSTOMER_PII_CLONED
CLONE CUSTOMER_PII;
結果確認:ANALYST_ROLE で CLONE されたテーブルを見る
-- (事前に CUSTOMER_PII_CLONED に対する SELECT 権限が
-- ANALYST_ROLE に付与されている前提)
USE ROLE ANALYST_ROLE;
SELECT EMAIL FROM CUSTOMER_PII_CLONED LIMIT 2;
-- 実行結果:
-- ****@example.com
-- ****@example.jp
--
-- 👍 安全!ポリシーがテーブルごと継承されている!
結論: CLONE はオブジェクトをそのまま複製します。DDM ポリシーも正しく継承(複製)されます。 PII テーブルのバックアップや開発環境への複製には CLONE が最適です。
安全策2:VIEW (標準ビュー)
VIEW は SELECT クエリを保存する「ショートカット」であり、データを実体化しません。
検証:MASK_ROLE で VIEW を作成
USE ROLE MASK_ROLE;
CREATE OR REPLACE VIEW PII_VIEW AS
SELECT ID, EMAIL FROM CUSTOMER_PII WHERE ID < 3;
結果確認:ANALYST_ROLE で VIEW を見る
-- (事前に PII_VIEW に対する SELECT 権限が
-- ANALYST_ROLE に付与されている前提)
USE ROLE ANALYST_ROLE;
-- ANALYST_ROLE で VIEW をクエリ
SELECT EMAIL FROM PII_VIEW;
-- 実行結果:
-- ****@example.com
-- ****@example.jp
--
-- 👍 安全!ポリシーが正しく機能している!
結論: 標準ビューは、クエリが実行されるたびに、「ビューを叩いた人(ANALYST_ROLE)」の権限でベーステーブル(CUSTOMER_PII)の DDM ポリシーを評価します。データ共有の用途では非常に安全です。
📊 まとめ:DDM継承チートシート
Snowflake のオブジェクト操作と DDM の関係をまとめます。
| 操作 | ポリシーは継承されるか? | 実行者のロールの影響 | データ漏洩リスク |
|---|---|---|---|
CTAS |
No (継承されない) | 大 (実行者が見るデータが新テーブルに書き込まれる) | 🔴 高い |
CLONE |
Yes (継承される) | ほぼ無し (メタデータごとコピー) | 🟢 低い |
CREATE VIEW |
N/A (ベーステーブルで評価) | N/A (クエリ実行者のロールで動的に評価) | 🟢 低い |
CREATE MV |
N/A (作成自体がエラーになる) | N/A (Snowflakeが漏洩をブロック) | 🟡 中 (※設計変更を強いられる) |
CREATE DYNAMIC TABLE |
No (継承されない) | 大 (オーナーが見るデータが実体化される) | 🟠 注意 (※DT側にもマスクが別途必要) |
😌 おわりに
動的データマスキング (DDM) は、クエリの結果セットに対して適用される「フィルター」のようなものです。
「データそのものがマスクされているわけではない」という点を忘れてはいけません。
この特性を理解していないと、CTAS や CREATE DYNAMIC TABLE を特権ロールで実行した瞬間に、意図せず生データを漏洩させてしまいます。
-
安全なコピー:
CLONEを使う。 -
安全な共有:
VIEWを使う。 -
危険な操作:
CTAS,DYNAMIC TABLEを生データが見えるロールで実行しない(実行する場合は、作成後のオブジェクトにもDDMポリシーを適用する)。 -
ブロックされる操作:
MATERIALIZED VIEWはマスクされた列には作成できない。(パフォーマンスとDDMを両立させたい場合はDYNAMIC TABLEを検討する)
「ポリシーを付けたから安心」ではなく、「ポリシーがどう動くか」を理解することが、Snowflake のセキュリティを守る上で最も重要ですね。
Discussion