
Snowflake の Projection Policy でカラムの投影(出力)をロールベースで制御して確認してみた
かわばたです。
マスキングポリシーはカラムの値をマスクしますが、「カラムを最終的なクエリ結果へ出力させたくない」というケースもあります。たとえばデータ共有先や分析用ロールに対して、給与カラムやメールアドレスをクエリ結果へ投影させたくない、といった場面です。
Snowflake の Projection Policy を使うと、カラム単位で最終結果への投影をロールベースで制御できます。
マスキングポリシーや集計ポリシーとは異なるアプローチなので、基本動作からビュー経由の間接参照、マスキングポリシーとの併用まで試してみました。手順と確認結果をまとめます。
機能概要
Projection Policy とは
Projection Policy はスキーマレベルのオブジェクトで、テーブルやビューのカラムに割り当てて使います。割り当てられたカラムは「投影制約(projection-constrained)」の状態になり、ポリシーの条件を満たさないロールからはクエリ結果に出力できなくなります。
ざっくり言うと、マスキングポリシーが「値を *** に置き換える」のに対して、Projection Policy は「カラムを最終結果へ投影させない」制御です。カラム名やスキーマ情報の可視性は権限構成に依存しますが、Projection Policy 自体は
カラムの存在を完全に秘匿する機能ではありません。特に NULLIFY では結果セットに
対象カラムが残るため、カラムの存在は利用者にわかります。
PROJECTION_CONSTRAINT 関数
ポリシー本体では PROJECTION_CONSTRAINT 関数を呼び出して、投影の許可・拒否と拒否時の挙動を指定します。
PROJECTION_CONSTRAINT(ALLOW => {TRUE|FALSE}, ENFORCEMENT => <enforcement_style>)
| パラメータ | 説明 |
|---|---|
ALLOW |
TRUE で投影を許可、FALSE で拒否 |
ENFORCEMENT |
ALLOW => FALSE の場合の挙動。'FAIL'(デフォルト)でクエリをエラーにする。'NULLIFY' で全行 NULL を返す |
ENFORCEMENT の2モードは用途が異なります。
- FAIL: 制約カラムを最終出力に含めるクエリを失敗させる。誤った利用を早期に検知・是正したい場合向き
- NULLIFY: クエリは成功するが、制約カラムの値が全行で NULL になる。
SELECT *を許容しつつ値は見せたくない場合向き
マスキングポリシーとの違い
| 観点 | Projection Policy | マスキングポリシー |
|---|---|---|
| 何を制御するか | カラムの最終結果への投影 | カラムの値 |
| 制限時の見え方 | FAIL: クエリがエラーになる / NULLIFY: 対象カラムが全行で NULL になる |
マスク値(*** 等)に置換 |
| WHERE 句での利用 | 可能(投影ではないため元の値で条件判定される) | マスキングポリシーの式が WHERE 句の参照にも適用されるため、ポリシーが返す値に基づいて条件評価される |
| 適用粒度 | カラム単位 | カラム単位 |
| タグベースポリシー | 非対応 | 対応 |
ポリシーの評価順序
複数のポリシーが同時に設定されている場合、以下の順序で評価されます。
- Row Access Policy で行をフィルタリング
- Projection Policy で投影制約カラムの投影可否を判定
- Masking Policy で値をマスク
FAIL により投影が拒否される場合はクエリが失敗するため、マスキング結果は返りません。
Snowflake のポリシー評価順序は、Row Access Policy、Projection Policy、Masking Policy の順です。
NULLIFY とマスキングポリシーを同一カラムに併用する場合、Projection Policy の判定後に
マスキングポリシーも評価されるため、最終的な表示値はマスキングポリシーの実装に依存します。
この組み合わせは、実際のポリシー定義を用いて個別に検証してください。
ビュー経由の間接参照
投影制約カラムをビューや CTE で間接参照しても、ベーステーブル側の制約は維持されます。たとえば base table の salary に投影ポリシーが設定されていれば、salary を参照するビュー経由でも投影が制限されます。
さらに、ビューの対応カラムにも独自の Projection Policy を設定している場合は、ベーステーブルからビューまでの列リネージ上にあるすべての Projection Policy が投影を許可しなければ、最終結果に投影できません。
セキュリティ上の注意
投影制約は完全な機密性保証ではありません。公式ドキュメントでも以下の点が明記されています。
- 投影制約カラムは WHERE 句やサブクエリで利用可能なので、条件を変えながらクエリを繰り返すことで値を推測できる可能性がある
- 投影制約カラムと結合した別テーブルのカラム値から情報が漏れる可能性がある
- エラーメッセージにカラム値が含まれるケースがある
Projection Policy は「既存の信頼関係がある相手」に対して投影を制御する用途に最適であり、悪意あるユーザーの攻撃を完全に防ぐものではない点を理解しておく必要があります。強い秘匿性が要件である場合は、公開用ビュー・共有用テーブルから対象カラムを物理的に除外する設計を優先してください。
制限事項
- Projection Policy をタグへ直接関連付けて自動適用するタグベースポリシーは非対応。ただし、ポリシー本体で
SYSTEM$GET_TAG_ON_CURRENT_COLUMN関数を使って対象カラムのタグ値を条件に投影可否を判定することは可能 - 仮想カラム、外部テーブルの VALUE カラムには適用不可
- 投影制約カラムは、マスキングポリシーや行アクセスポリシーの条件式から参照できない
- 投影制約カラムの値を別テーブルに INSERT したり、外部関数・ストアドプロシージャの引数に渡すことはできない
前提条件
-
Snowflake: AWS 東京リージョン、Enterprise Edition 以上(今回の検証ではトライアルアカウントを利用しています)
-
必要な権限: ACCOUNTADMIN ロール(検証用)。本番運用では、ポリシー作成に対象スキーマの
CREATE PROJECTION POLICY権限、ポリシーの適用にアカウントレベルのAPPLY PROJECTION POLICY権限(またはポリシーへのAPPLY権限 + テーブルのOWNERSHIP権限)が必要
事前準備
検証用データベース・テーブルの作成
検証用データベース・テーブルの作成
USE ROLE ACCOUNTADMIN;
CREATE DATABASE IF NOT EXISTS proj_policy_test;
USE DATABASE proj_policy_test;
CREATE SCHEMA IF NOT EXISTS sch;
USE SCHEMA sch;
CREATE OR REPLACE TABLE employees (
employee_id INT COMMENT '従業員ID',
employee_name VARCHAR COMMENT '氏名',
email VARCHAR COMMENT 'メールアドレス',
department VARCHAR COMMENT '部署',
salary NUMBER(10, 2) COMMENT '月給',
phone_number VARCHAR COMMENT '電話番号'
);
-- 検証用データの投入(25レコード)
-- salary / email / phone_number を機密カラムとして扱う想定
INSERT INTO employees VALUES
(1, '田中太郎', 'tanaka@example.com', 'Engineering', 850000, '090-1111-0001'),
(2, '佐藤花子', 'sato@example.com', 'Engineering', 650000, '090-1111-0002'),
(3, '鈴木一郎', 'suzuki@example.com', 'Engineering', 950000, '090-1111-0003'),
(4, '高橋美咲', 'takahashi@example.com', 'Engineering', 600000, '090-1111-0004'),
(5, '伊藤健太', 'ito@example.com', 'Engineering', 450000, '090-1111-0005'),
(6, '渡辺直美', 'watanabe@example.com', 'Sales', 700000, '090-1111-0006'),
(7, '山本学', 'yamamoto@example.com', 'Sales', 780000, '090-1111-0007'),
(8, '中村陽子', 'nakamura@example.com', 'Sales', 520000, '090-1111-0008'),
(9, '小林誠', 'kobayashi@example.com', 'Sales', 480000, '090-1111-0009'),
(10, '加藤裕二', 'kato@example.com', 'Sales', 650000, '090-1111-0010'),
(11, '吉田真理', 'yoshida@example.com', 'Marketing', 500000, '090-1111-0011'),
(12, '松本大輔', 'matsumoto@example.com', 'Marketing', 490000, '090-1111-0012'),
(13, '井上さくら', 'inoue@example.com', 'Marketing', 470000, '090-1111-0013'),
(14, '木村拓也', 'kimura@example.com', 'Marketing', 720000, '090-1111-0014'),
(15, '林美穂', 'hayashi@example.com', 'Marketing', 550000, '090-1111-0015'),
(16, '清水翔太', 'shimizu@example.com', 'HR', 680000, '090-1111-0016'),
(17, '斎藤恵', 'saito@example.com', 'HR', 450000, '090-1111-0017'),
(18, '前田健一', 'maeda@example.com', 'HR', 500000, '090-1111-0018'),
(19, '藤井優子', 'fujii@example.com', 'Finance', 530000, '090-1111-0019'),
(20, '岡田龍一', 'okada@example.com', 'Finance', 1100000, '090-1111-0020'),
(21, '石川由美', 'ishikawa@example.com', 'Finance', 520000, '090-1111-0021'),
(22, '三浦健二', 'miura@example.com', 'Engineering', 620000, '090-1111-0022'),
(23, '西田あかり', 'nishida@example.com', 'Sales', 460000, '090-1111-0023'),
(24, '上田浩司', 'ueda@example.com', 'HR', 550000, '090-1111-0024'),
(25, '河野真由', 'kawano@example.com', 'Engineering', 880000, '090-1111-0025');
テーブルの内容を確認しておきます。
SELECT * FROM employees ORDER BY employee_id;

25件のデータが入っていれば問題ありません。
検証用ロールの作成
analyst_role(投影を許可するロール)と viewer_role(投影を制限するロール)を用意します。
検証用ロールの作成
USE ROLE ACCOUNTADMIN;
-- 分析用ロール(機密カラムの投影を許可する想定)
CREATE ROLE IF NOT EXISTS analyst_role;
GRANT USAGE ON DATABASE proj_policy_test TO ROLE analyst_role;
GRANT USAGE ON SCHEMA proj_policy_test.sch TO ROLE analyst_role;
GRANT SELECT ON TABLE proj_policy_test.sch.employees TO ROLE analyst_role;
-- 閲覧用ロール(機密カラムの投影を制限する想定)
CREATE ROLE IF NOT EXISTS viewer_role;
GRANT USAGE ON DATABASE proj_policy_test TO ROLE viewer_role;
GRANT USAGE ON SCHEMA proj_policy_test.sch TO ROLE viewer_role;
GRANT SELECT ON TABLE proj_policy_test.sch.employees TO ROLE viewer_role;
-- 検証用ユーザーにロールを付与(環境の既存ユーザー名に置き換えてください)
GRANT ROLE analyst_role TO USER <your_test_user>;
GRANT ROLE viewer_role TO USER <your_test_user>;
試してみた
基本: Projection Policy を作成してカラムに割り当てる
まずは最もシンプルなパターンから。全ロールで salary カラムの投影を禁止するポリシーを作成します。
USE ROLE ACCOUNTADMIN;
USE SCHEMA proj_policy_test.sch;
-- 全ロールで投影を禁止するポリシー
CREATE OR REPLACE PROJECTION POLICY deny_projection
AS () RETURNS PROJECTION_CONSTRAINT ->
PROJECTION_CONSTRAINT(ALLOW => FALSE);
salary カラムに割り当てます。
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY deny_projection;
この状態で SELECT * を実行してみます。
SELECT * FROM employees LIMIT 5;

salary が投影制約に引っかかってクエリがエラーになるはずです。デフォルトの ENFORCEMENT は 'FAIL' なので、投影制約カラムを SELECT リストに含めるとクエリ自体が失敗します。
では、salary を SELECT リストから外してみます。
SELECT employee_id, employee_name, email, department, phone_number
FROM employees
LIMIT 5;

salary を含めなければ問題なくクエリが返ります。この例では salary を最終的な SELECT リストから除外すればクエリは成功します。ただし Projection Policy は単純な SELECT リストだけでなく、ビュー、CTE、UDF、派生列などを含む列リネージを追跡して最終出力への投影を制御します(詳細は後述で確認します)。
検証が終わったのでポリシーを解除しておきます。
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
ENFORCEMENT モードの違いを確認する(FAIL vs NULLIFY)
次に ENFORCEMENT => 'NULLIFY' のポリシーを試します。FAIL と違い、クエリ自体はエラーにならず、制約カラムの値が全行 NULL になります。
-- NULLIFY モードのポリシーを作成
CREATE OR REPLACE PROJECTION POLICY deny_projection_nullify
AS () RETURNS PROJECTION_CONSTRAINT ->
PROJECTION_CONSTRAINT(ALLOW => FALSE, ENFORCEMENT => 'NULLIFY');
-- salary カラムに割り当て
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY deny_projection_nullify;
SELECT employee_id, employee_name, salary, department
FROM employees
LIMIT 5;
今度はクエリが成功し、salary カラムが全行 NULL で返ってくるはずです。SELECT * も成功します。

FAIL と NULLIFY の使い分け:
- FAIL: 制約カラムを投影するクエリを失敗させたい場合に使う。誤った利用を早期に検知・是正できるが、
SELECT *がエラーになるので利用者への周知が必要 - NULLIFY:
SELECT *を許容しつつ値は見せたくない場合に使う。マスキングポリシーのNULLマスクに近い挙動だが、マスキングポリシーとは制御レイヤーが異なる
ポリシーを解除しておきます。
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
ロールベースの条件付き投影を試す
実運用では「特定のロールだけ投影を許可する」パターンが多いはずです。CURRENT_ROLE() を条件に使って、analyst_role のみ投影を許可するポリシーを作ります。
USE ROLE ACCOUNTADMIN;
USE SCHEMA proj_policy_test.sch;
-- analyst_role のみ投影を許可するポリシー
CREATE OR REPLACE PROJECTION POLICY allow_analyst_only
AS () RETURNS PROJECTION_CONSTRAINT ->
CASE
WHEN CURRENT_ROLE() = 'ANALYST_ROLE'
THEN PROJECTION_CONSTRAINT(ALLOW => TRUE)
ELSE PROJECTION_CONSTRAINT(ALLOW => FALSE, ENFORCEMENT => 'FAIL')
END;
-- salary カラムに割り当て
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY allow_analyst_only;
まず analyst_role で確認します。
USE ROLE analyst_role;
USE WAREHOUSE <your_warehouse_name>;
USE DATABASE proj_policy_test;
USE SCHEMA sch;
SELECT employee_id, employee_name, salary, department
FROM employees
LIMIT 5;

analyst_role では salary が正常に表示されれば問題ありません。
次に viewer_role で確認します。
USE ROLE viewer_role;
USE WAREHOUSE <your_warehouse_name>;
USE DATABASE proj_policy_test;
USE SCHEMA sch;
SELECT employee_id, employee_name, salary, department
FROM employees
LIMIT 5;

viewer_role ではエラーになるはずです。
ACCOUNTADMIN でも確認してみます。
USE ROLE ACCOUNTADMIN;
SELECT employee_id, employee_name, salary, department
FROM employees
LIMIT 5;

ポリシーを解除しておきます。
USE ROLE ACCOUNTADMIN;
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
WHERE 句での利用可否を確認する(セキュリティ上の注意点)
投影制約カラムは SELECT リストには出せませんが、WHERE 句でのフィルタリングは可能です。これが「Projection Policy は完全な機密性保証ではない」と公式ドキュメントが述べている理由の一つです。
USE ROLE ACCOUNTADMIN;
-- 全ロール投影禁止のポリシーを salary に適用
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY deny_projection;
-- salary を SELECT リストに含めず、WHERE 句で使う
SELECT employee_id, employee_name, department
FROM employees
WHERE salary > 800000;

クエリが成功し、給与80万超の従業員だけが絞り込まれて返ってきます。salary の値自体は結果に出ていませんが、条件を変えながら繰り返しクエリを投げれば値の範囲を推測できてしまいます。
たとえば WHERE salary > 900000 → WHERE salary > 950000 と閾値を変えれば、特定の人の給与を絞り込めます。Projection Policy だけで機密性を完全に守るのは難しいので、必要に応じてマスキングポリシーや行アクセスポリシーと組み合わせることが重要です。
ポリシーを解除しておきます。
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
ビュー経由の間接参照を確認する
投影制約カラムをビューで参照した場合、ビュー経由でも制約が効くかを確認します。
USE ROLE ACCOUNTADMIN;
USE SCHEMA proj_policy_test.sch;
-- salary に投影ポリシーを設定
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY deny_projection;
-- salary を含むビューを作成
CREATE OR REPLACE VIEW employee_summary AS
SELECT employee_id, employee_name, department, salary
FROM employees;
-- ビューに SELECT 権限を付与
GRANT SELECT ON VIEW employee_summary TO ROLE viewer_role;
USE ROLE viewer_role;
USE WAREHOUSE <your_warehouse_name>;
USE DATABASE proj_policy_test;
USE SCHEMA sch;
-- ビュー経由で salary を投影しようとする
SELECT * FROM employee_summary LIMIT 5;

ビュー経由でも base table の投影ポリシーが効いて、salary の投影がブロックされます。ビューを作っただけではポリシーを迂回できないことが確認できました。
ポリシーを解除してビューを削除しておきます。
USE ROLE ACCOUNTADMIN;
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
DROP VIEW IF EXISTS employee_summary;
マスキングポリシーとの併用を確認する
最後に、Projection Policy とマスキングポリシーを同一テーブルの別カラムに適用して、併用時の挙動を確認します。
salary: Projection Policy でviewer_roleの投影を制限(NULLIFY モード)email: マスキングポリシーでviewer_roleの値をマスク
USE ROLE ACCOUNTADMIN;
USE SCHEMA proj_policy_test.sch;
-- salary 用: analyst_role 以外では NULLIFY する Projection Policy
CREATE OR REPLACE PROJECTION POLICY salary_projection
AS () RETURNS PROJECTION_CONSTRAINT ->
CASE
WHEN CURRENT_ROLE() = 'ANALYST_ROLE'
THEN PROJECTION_CONSTRAINT(ALLOW => TRUE)
ELSE PROJECTION_CONSTRAINT(ALLOW => FALSE, ENFORCEMENT => 'NULLIFY')
END;
-- email 用: analyst_role 以外ではマスクする Masking Policy
CREATE OR REPLACE MASKING POLICY mask_email AS (val STRING)
RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() = 'ANALYST_ROLE' THEN val
ELSE '***MASKED***'
END;
-- ポリシーを割り当て
ALTER TABLE employees
MODIFY COLUMN salary
SET PROJECTION POLICY salary_projection;
ALTER TABLE employees
MODIFY COLUMN email
SET MASKING POLICY mask_email;
まず analyst_role で確認します。
USE ROLE analyst_role;
USE WAREHOUSE <your_warehouse_name>;
USE DATABASE proj_policy_test;
USE SCHEMA sch;
SELECT employee_id, employee_name, email, salary, department
FROM employees
LIMIT 5;

analyst_role では email も salary もそのまま表示されれば問題ありません。
次に viewer_role で確認します。
USE ROLE viewer_role;
USE WAREHOUSE <your_warehouse_name>;
USE DATABASE proj_policy_test;
USE SCHEMA sch;
SELECT employee_id, employee_name, email, salary, department
FROM employees
LIMIT 5;

viewer_role では以下のようになるはずです。
salary: Projection Policy でanalyst_role以外のロールの投影を制限(NULLIFY モード)email: マスキングポリシーでanalyst_role以外のロールの値をマスク
同一テーブルの異なるカラムに Projection Policy とマスキングポリシーを設定し、両ポリシーが同時に機能することを確認できました。今回は異なるカラムへの適用なので、それぞれ独立に効いている形です。同一カラムに両ポリシーを設定する設計では、Projection Policy の判定(FAIL / NULLIFY)とマスキングポリシーの適用順序を個別に検証してください。
ポリシーを解除しておきます。
USE ROLE ACCOUNTADMIN;
ALTER TABLE employees
MODIFY COLUMN salary
UNSET PROJECTION POLICY;
ALTER TABLE employees
MODIFY COLUMN email
UNSET MASKING POLICY;
最後に
Projection Policy を試してみました。マスキングポリシーとの使い分けがポイントで、整理するとこうなります。
- マスキングポリシー: カラム値をロールなどの条件に応じて置き換える
- Projection Policy: カラムを最終的なクエリ結果へ投影できるかを制御する(
FAILでクエリをエラーにする /NULLIFYで値を NULL として返す) - 集計ポリシー: 個別レコードの返却を抑制し、最小グループサイズを満たす集計を求める
FAIL / NULLIFY の選択もわかりやすく、要件に合わせてモードを選べるのが良いです。ロールベースの条件分岐も CASE 式で素直に書けます。
一方で、投影制約カラムは WHERE 句や内部クエリで利用でき、結合や繰り返しクエリにより情報を推測される可能性があります。カラムの存在や値を強く秘匿する必要がある場合は、公開用のビュー・テーブルから対象カラムを除外する設計を優先し、必要に応じてマスキングポリシー、行アクセスポリシー、Differential Privacy などと組み合わせることが重要です。
この記事が何かの参考になれば幸いです!






