民主化を進めるとき、「全員に全データを見せる」ことは絶対に避けないといけない。営業部は自部門担当の顧客だけ、人事は自部門配下の従業員だけ、 カラムは特定ロールだけ非マスク ── こういう 行レベル / 列レベルの可視性制御が Row-Level Security () / Column-Level Security () です。
認可マトリクスを最初に作る
実装前に 「ロール × テーブル × 行/列条件」の認可マトリクスを表にする。エンジニアが暗黙に決めるのではなく、法務 / 人事 / 監査責任者を巻き込んで合意するのが要です。
| ロール | customers | orders | salaries | user_pii |
|---|---|---|---|---|
| 全社員 | 顧客名のみ閲覧、メアド/電話マスク | 金額のみ、顧客IDマスク | 閲覧不可 | 閲覧不可 |
| 営業 (担当部門) | 自部門の顧客フル閲覧 | 自部門の orders フル閲覧 | 閲覧不可 | 顧客のメアド/電話のみ |
| 営業マネージャ | 全社の顧客フル閲覧 | 全社 orders フル閲覧 | 閲覧不可 | 営業範囲の PII |
| 人事 (一般) | 閲覧不可 | 閲覧不可 | 管掌部門のみ | 対象社員のみ |
| 経営 | 全閲覧 | 全閲覧 | 全閲覧 | 全閲覧(要監査) |
| 外部監査 | 閲覧不可 | 閲覧不可 | 閲覧不可(フェイク化) | 閲覧不可 |
: Row-Level Security
-- ロール: g-sales-east@example.com の人だけが「東日本」の顧客行を閲覧可能CREATE ROW ACCESS POLICY sales_east_only ON `proj.dwh.customers` GRANT TO ("group:g-sales-east@example.com") FILTER USING (region = 'east');
-- 同じテーブルに別ポリシーを重ねるCREATE ROW ACCESS POLICY sales_west_only ON `proj.dwh.customers` GRANT TO ("group:g-sales-west@example.com") FILTER USING (region = 'west');
-- 経営層(ポリシー外)には全行が見えるよう、bypass 用の Auth Domain を分離BigQuery では OR 結合される(東 OR 西の行が見える)。 では `EXISTS` を組合せた条件式を組む必要があり、設計が変わる。複数 を跨ぐ場合は 意味論の差を必ず確認。
BigQuery: Column-Level Security(Policy Tags)
-- 1) Data Catalog で Policy Tag を作成(GUI / Terraform)-- 例: pii_email, pii_phone, pii_address
-- 2) テーブルカラムに Policy Tag を付与(DDL)ALTER TABLE `proj.dwh.users`ALTER COLUMN email SET OPTIONS ( policy_tags='projects/proj/locations/us/taxonomies/T/policyTags/pii_email');
-- 3) Tag に対する Reader 権限を group:trusted-pii-readers@example.com にだけ付与
-- 4) Tag 付きカラムは未許可者には NULL or マスク値で返るSELECT id, email FROM users LIMIT 10;-- → 一般ユーザ: email カラムが NULL-- → trusted-pii-readers: 生メールSnowflake: Row Access Policy + Masking Policy
-- ポリシー定義CREATE OR REPLACE ROW ACCESS POLICY sales_region_policy AS (region varchar) RETURNS BOOLEAN -> CASE WHEN CURRENT_ROLE() = 'EXECUTIVE' THEN TRUE WHEN CURRENT_ROLE() = 'SALES_EAST' AND region = 'east' THEN TRUE WHEN CURRENT_ROLE() = 'SALES_WEST' AND region = 'west' THEN TRUE ELSE FALSE END;
-- テーブルに適用ALTER TABLE customers ADD ROW ACCESS POLICY sales_region_policy ON (region);CREATE OR REPLACE MASKING POLICY mask_email AS (val STRING) RETURNS STRING -> CASE WHEN CURRENT_ROLE() IN ('EXECUTIVE', 'PII_READER') THEN val ELSE REGEXP_REPLACE(val, '.+@', '***@') END;
ALTER TABLE users MODIFY COLUMN email SET MASKING POLICY mask_email;RLS/CLS の落とし穴 5 選
1. ポリシー数が爆発する
「部署 × 拠点 × 製品ライン」のように 3 軸で切ると ポリシー数が組合せ的に増える。実装は「ポリシー条件は 1 つの に、条件を テーブルで管理」が現実的。
2. View で Bypass される
RLS ポリシーは テーブル単位で適用される。テーブルから VIEW を作ると、VIEW の所有者の権限で展開されてしまう(BigQuery `Authorized View`)。View も含めて一貫した権限設計が必要。
3. JOIN で漏れる
RLS が効いた `customers` を、RLS のない `orders` と JOIN すると、漏れる側のテーブル経由で隠したはずの情報が露出する。設計時に JOIN 経路の漏洩を必ず確認。
4. EXPLAIN や METADATA で見える
実行計画やメタデータ系(INFORMATION_SCHEMA / ACCOUNT_USAGE)からは 行数・カラム名が見える。メタデータ参照権限も同時に絞る必要がある。
5. 開発環境とのズレ
本番にだけ RLS が効いていて、dev 環境は無防備。 のテストやアプリの結合テストは本番相当の RLS 環境でも回す(本番権限の縮小コピーで)。
運用:認可マトリクスを「コード化」する
認可マトリクスを or JSON で管理し、Terraform / dbt 経由で SQL を生成するパターンが鉄板。人手で SQL を直書きしないこと。差分 PR 経由で監査可能になる。
tables: - name: customers rls: - role: SALES_EAST condition: "region = 'east'" - role: SALES_WEST condition: "region = 'west'" - role: EXECUTIVE condition: "TRUE" cls: - column: email readable_by: [PII_READER, EXECUTIVE] mask: "regex_replace(val, '.+@', '***@')" - column: phone readable_by: [PII_READER, EXECUTIVE] mask: "left(val, 3) || '****'"次の話
EP.06 では、コスト爆発と DWH 詰まりを構造的に防ぐ設計。クエリ予算・タイムアウト・ワークロード隔離を扱います。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。