Databricks Certified Data Engineer Associate 教科書
第7章 ガバナンスとセキュリティ(Governance and Security, 15%)
🎯 この節の学習目標
ALTER TABLE ... SET MASK で実装できるALTER TABLE ... SET ROW FILTER で実装できる7-2 で学んだ GRANT / REVOKE は「テーブル全体を読めるか読めないか」というオブジェクト単位の制御でした。しかし実務の要件はもっと細かいことがよくあります。
Unity Catalog では、この2つをカラムマスク(column mask)と行フィルタ(row filter)としてテーブル本体に直接定義できます。どちらも仕組みは共通で、「判定ロジックを SQL UDF(ユーザー定義関数)として作成し、ALTER TABLE でテーブルに取り付ける」という2段構えです。判定によく使うのが is_account_group_member('グループ名') という組み込み関数で、クエリを実行している本人が指定グループのメンバーなら true を返します。
カラムマスクは、列の値を返す直前に UDF を通す仕組みです。UDF は「マスク対象の列の値」を引数に受け取り、「実際に返す値」を返します。許可されたユーザーには元の値を、それ以外には伏字を返すように書きます。
💡 具体例:メールアドレス列をマスクする
-- 手順1:マスキング関数(SQL UDF)を作成する
-- 引数の型はマスク対象列と互換であること
CREATE OR REPLACE FUNCTION main.security.mask_email(email STRING)
RETURNS STRING
RETURN CASE
WHEN is_account_group_member('pii_readers') THEN email -- 許可グループには生値
ELSE '***MASKED***' -- それ以外は伏字
END;
-- 手順2:テーブルの列にマスクを適用する
ALTER TABLE main.crm.customers
ALTER COLUMN email SET MASK main.security.mask_email;
-- 解除する場合
ALTER TABLE main.crm.customers
ALTER COLUMN email DROP MASK;
適用後は、pii_readers グループのメンバーが SELECT email FROM main.crm.customers を実行すると生のメールアドレスが、それ以外のユーザーには ***MASKED*** が返ります。クエリを書き換える必要はなく、同じテーブルへの同じクエリで、実行者によって結果だけが変わるのがポイントです。
伏字の代わりに「一部だけ見せる」加工も UDF 次第で自由に書けます。たとえば CONCAT('***@', split(email, '@')[1]) のようにドメイン部分だけ残す、といった部分マスクも実装できます。
行フィルタは、クエリ結果に含めるかどうかを行ごとに判定する仕組みです。UDF は「フィルタ条件に使う列の値」を引数に受け取り、BOOLEAN を返します。true の行だけが結果に含まれます。
💡 具体例:担当地域の行だけを見せる
-- 手順1:フィルタ関数を作成する(BOOLEAN を返す)
CREATE OR REPLACE FUNCTION main.security.region_filter(region STRING)
RETURNS BOOLEAN
RETURN
is_account_group_member('sales_admins') -- 管理者は全行
OR (is_account_group_member('sales_east') AND region = 'east') -- 東日本担当は east のみ
OR (is_account_group_member('sales_west') AND region = 'west'); -- 西日本担当は west のみ
-- 手順2:テーブルに行フィルタを適用する
-- ON (列名) で、テーブルのどの列を UDF の引数に渡すかを指定する
ALTER TABLE main.sales.orders
SET ROW FILTER main.security.region_filter ON (region);
-- 解除する場合
ALTER TABLE main.sales.orders DROP ROW FILTER;
適用後、sales_east のメンバーが SELECT count(*) FROM main.sales.orders を実行すると、east の行だけを数えた結果が返ります。フィルタされた行は存在自体が見えなくなるため、集計結果も自動的に「本人が見てよい範囲」のものになります。
📝 試験のポイント
構文の対応関係を混同しないようにしましょう。列を隠す=カラムマスク=ALTER COLUMN ... SET MASK(UDF は列と同じ型の値を返す)、行を絞る=行フィルタ=SET ROW FILTER ... ON (列)(UDF は BOOLEAN を返す)です。また、どちらも「テーブルを SELECT できること」が前提です。マスクやフィルタは 7-2 の GRANT を置き換えるものではなく、SELECT 権限がある人の中で見え方を変える仕組みだと整理してください。
カラムマスクや行フィルタが登場する前からある古典的な方法が、ビューによる制御です。元テーブルへの直接アクセスは与えず、CASE WHEN is_account_group_member(...) や WHERE 句を仕込んだビューだけを公開するやり方です。
-- ビュー方式の例:マスクとフィルタをビューに埋め込む
CREATE VIEW main.crm.customers_safe AS
SELECT
customer_id,
CASE WHEN is_account_group_member('pii_readers')
THEN email ELSE '***MASKED***' END AS email,
region
FROM main.crm.customers
WHERE is_account_group_member('sales_admins') OR region = 'east';
| 観点 | ビューによる制御 | カラムマスク / 行フィルタ |
|---|---|---|
| 適用対象 | ビューという別オブジェクトを公開(元テーブルは隠す) | テーブル本体に直接適用 |
| 利用者から見た名前 | 用途別にビュー名が増える(_safe、_east…) | 全員が同じテーブル名を使い、結果だけ変わる |
| 管理負荷 | 要件が増えるほどビューが乱立し、定義の重複・更新漏れが起きやすい | ポリシーは UDF に集約。テーブルと1対1で管理できる |
| 直接アクセス | 元テーブルの SELECT を渡すと制御を素通りされる | テーブル自体に効くため、直接 SELECT しても制御される |
ビュー方式は手軽で柔軟ですが、部署×地域×役職のように条件が組み合わさると「同じテーブルの派生ビュー」が乱立し、どのビューが最新のセキュリティ要件を反映しているのか分からなくなりがちです。テーブル本体に1つだけ真実のポリシーを取り付けられるのがマスク/フィルタ方式の利点です。
典型的なユースケースは PII(個人識別情報)の保護です。氏名・メールアドレス・電話番号・住所などの列にカラムマスクを適用し、コンプライアンス上許可されたグループだけが生値を見られるようにします。行フィルタは、地域・部門・契約単位でのデータ分離(マルチテナント的な見せ方)によく使われます。1つのテーブルに両方を同時に適用することもできます。
💡 具体例:動作確認の方法
適用後は必ず許可グループのメンバーと非メンバーの両方の立場で同じクエリを実行し、結果が意図どおり変わることを確認します。自分がどのグループに属しているかは SELECT is_account_group_member('pii_readers'); で確認できます(true/false が返ります)。テーブルにどのマスク・フィルタが付いているかは、DESCRIBE EXTENDED や Catalog Explorer のテーブル詳細画面で確認できます。「マスクを適用したつもりが、検証を許可グループのアカウントだけで行ったため、伏字を一度も確認していなかった」という失敗は起こりがちなので、非許可側での確認を必ず含めてください。
✅ この節のまとめ
is_account_group_member() を使う。ALTER TABLE ... ALTER COLUMN 列 SET MASK 関数。UDF は列値を受け取り、許可ユーザーには生値、それ以外には伏字を返す。ALTER TABLE ... SET ROW FILTER 関数 ON (列)。UDF は BOOLEAN を返し、true の行だけが見える。集計結果にも自動で効く。問1. 顧客テーブルの phone 列を、support_leads グループ以外には伏字で見せたい。正しい実装手順はどれか。
ALTER TABLE ... SET ROW FILTER で適用するis_account_group_member('support_leads') で分岐して列値または伏字を返す UDF を作成し、ALTER TABLE ... ALTER COLUMN phone SET MASK で適用するDENY SELECT を phone 列に対して発行するGRANT SELECT をテーブルから REVOKE すれば列も見えなくなる正解:B
列単位で値を伏字化する要件はカラムマスクです。UDF で許可グループには生値、それ以外には伏字を返し、SET MASK で列に取り付けます。Aは行フィルタの手順で、行の絞り込み用です。Cは誤りで、そもそも Unity Catalog に DENY はなく(レガシーの hive_metastore のみ)、あったとしても列単位の SELECT の制御はできません。Dはテーブル全体が読めなくなり、「他の列は見せたい」という要件を満たしません。
問2. 行フィルタ用の SQL UDF が満たすべき条件として正しいものはどれか。
正解:A
行フィルタの UDF は BOOLEAN を返し、true の行のみが結果に含まれます。Bはカラムマスク用 UDF の説明で、行フィルタとは戻り値の役割が異なります。Cのような文字列プロトコルはありません。Dも誤りで、行フィルタはまさに SQL UDF を SET ROW FILTER で取り付ける仕組みです。
問3. これまで部署ごとに数十個のセキュアビューを作って行・列の制御をしてきたが、定義の重複と更新漏れが問題になっている。カラムマスク/行フィルタ方式へ移行する利点として最も適切なものはどれか。
正解:B
マスク/フィルタはテーブル本体に取り付けるため、用途別ビューを量産する必要がなくなり、ポリシーの一元管理ができます。Aは誤りで、性能向上を保証する機能ではありません(判定処理はむしろ追加コストです)。Cも誤りで、マスクは SELECT 権限を前提とした「見え方の制御」であり、権限付与の代替ではありません。Dは逆で、テーブル本体に効くからこそ直接クエリでも制御を素通りできない点が利点です。
問4. 次の SQL を実行した。ALTER TABLE main.sales.orders SET ROW FILTER main.security.region_filter ON (region);
この後、sales_east グループ(east 地域のみ許可)のメンバーが SELECT count(*) FROM main.sales.orders を実行した場合の結果はどれか。
正解:B
行フィルタは結果に含める行そのものを絞るため、count(*) などの集計にも「本人が見てよい行」だけが反映されます。Aは誤りで、集計クエリだけ素通りできる仕様ならセキュリティとして成立しません。Cも誤りで、SELECT 権限があれば通常どおりクエリできます。Dの「NULL に置換」はカラムマスク的な発想で、行フィルタでは行が結果から除外されます。