- BigQuery IAM の事前定義ロール(dataViewer / dataEditor / dataOwner / jobUser / user / admin)と「データへの権限」「ジョブ実行の権限」が別物である理由
- 承認済みビュー・承認済みデータセットで「元テーブルを見せずに集計結果だけ共有」する方法
- 行アクセスポリシーで行単位のアクセス制御を組み込む方法
- 列レベルセキュリティ(ポリシータグ)・動的データマスキングの仕組みと、CMEK・監査ログ・ガバナンス統合・データ共有の全体像
前提: ハンズオン 16-1〜16-3 はすべてサンドボックスで完走可・無料枠内。列レベルセキュリティ・CMEK は追加設定(Data Catalog の taxonomy、Cloud KMS の鍵)が要るため読み物として扱う。
BigQuery IAM の基本設計 — 「データへの権限」と「ジョブ実行の権限」
BigQuery で誰かにクエリを実行させたいとき、見落としがちな事実があります。クエリを実行する権限と、そのクエリが参照するデータを読む権限は別物だということです。BigQuery のジョブ(クエリ・ロード・エクスポート等)はプロジェクトのリソースを消費して動く処理であり、ジョブを作る権限はプロジェクトレベルの話です。一方、テーブルの中身を読めるかどうかはデータセットやテーブルに紐づく話です。この 2 つが両方そろって初めて、クエリが成功します。
BigQuery の事前定義ロールのうち、この章で押さえておくべきものを整理します。
| ロール | データセットに付与した場合 | プロジェクトに付与した場合 |
|---|---|---|
roles/bigquery.dataViewer | テーブル・ビューのメタデータ取得、データの読み取り(クエリ・エクスポート) | プロジェクト内の全データセットに同様の権限 |
roles/bigquery.dataEditor | テーブル・ビューの作成/更新/削除、データの読み書き | 加えて新規データセットの作成も可能 |
roles/bigquery.dataOwner | データセット内の全リソースへの完全な権限(行アクセスポリシー・列ポリシー設定を含む) | 加えて新規データセットの作成も可能 |
roles/bigquery.jobUser | 付与不可(プロジェクトのみ) | ジョブ(クエリ等)の作成のみ。データへのアクセス権は一切含まない |
roles/bigquery.user | データセットのメタデータ取得とテーブル一覧のみ | ジョブ作成 + 自分のジョブの一覧/キャンセル + 新規データセットの作成(作成者はそのデータセットの dataOwner になる) |
roles/bigquery.metadataViewer | スキーマ等のメタデータ閲覧のみ(データ本体は読めない) | — |
roles/bigquery.admin | プロジェクト内の全 BigQuery リソースに対する完全な権限 | 同左 |
jobUser と user はよく混同されますが、公式ドキュメントの権限一覧で比較すると違いは明確です。jobUser は bigquery.jobs.create だけを持つ「クエリを投げる権利」に特化したロールで、プロジェクトにしか付与できません。user はそれに加えて「自分でデータセットを作る権利」まで含みます。分析者にはデータへのアクセス権を絞った上で jobUser だけを渡し、データセットの作成まで任せたいチームには user を渡す、という使い分けが実務的です。
データセットへの権限付与は、コンソールの共有設定のほか、SQL の GRANT 文でも行えます。
GRANT `roles/bigquery.dataViewer`
ON SCHEMA `PROJECT_ID`.handson_ch16
TO "group:analysts@example.com";
承認済みビューで「集計結果だけ」共有する
「特定のテーブルは見せたくないが、そこから作った集計結果なら共有したい」という要件はよくあります。BigQuery ではテーブルに列単位・行単位の制御をかける方法のほかに、承認済みビュー(authorized view)という選択肢があります。ビューを別のデータセットに作り、そのビューに「元データセットへのアクセスを代行する権限」を与える仕組みです。
Principals can view the data you share and run queries on it, but they can't access the source dataset directly.
分析者はビューを含むデータセットへの dataViewer 権限だけを持てばよく、元テーブルを含むデータセットへの権限は一切不要になります。元データセット側は「このビューにだけはアクセスを許可する」という認可を、ビュー単位で管理します。
複数のビューを同じ相手に共有する場合、ビュー 1 つずつを個別に認可するのは管理コストが高くなります。ビューをまとめて 1 つのデータセットに入れ、そのデータセット単位で元データセットから認可すると、まとめて管理できます。これを承認済みデータセット(authorized dataset)と呼びます。データセットのアクセス制御リストは、承認済みビュー・承認済みデータセット・承認済み関数を合わせて最大 2,500 件までという上限があるため、ビューの数が増えるプロジェクトでは早めに承認済みデータセットへ寄せるのがベストプラクティスです。
行レベルセキュリティと列レベルセキュリティ
承認済みビューは「別の入り口を作る」方式でしたが、行アクセスポリシーと列レベルセキュリティは同じテーブルに対して、誰がクエリしたかによって見える内容を変える方式です。テーブルに複数のフィルタ層が重なるイメージで捉えると理解しやすくなります。
行レベルセキュリティ(行アクセスポリシー)
CREATE ROW ACCESS POLICY という DDL 文で作成します。FILTER USING に書いた条件に一致する行だけが、GRANT TO で指定したプリンシパルに見えるようになります。
CREATE ROW ACCESS POLICY apac_filter
ON `PROJECT_ID`.handson_ch16.regional_sales
GRANT TO ("group:apac-team@example.com")
FILTER USING (region = 'APAC');
実務では固定のメールアドレスを列挙するより、クエリを実行しているユーザー自身を返す SESSION_USER() 関数を条件に使うほうが汎用的です。
CREATE ROW ACCESS POLICY self_region_filter
ON `PROJECT_ID`.handson_ch16.regional_sales
GRANT TO ("domain:example.com")
FILTER USING (region = SESSION_USER());
行アクセスポリシーを作成すると、BigQuery は grantee_list のメンバーに bigquery.filteredDataViewer という特別なロールを自動的に付与します。このロールは行アクセスポリシー経由でのみ付与される仕組みのロールで、IAM から直接割り当てることはできません。重要なのは、行アクセスポリシーの対象になっているだけではクエリできない点です。テーブルそのものへの bigquery.tables.getData 権限(dataViewer ロール等)を別途持っていないと、アクセス拒否エラーになります。これも「データへの権限」と「アクセス制御の適用対象であること」が別レイヤーである一例です。
1 つのテーブルには通常、少なくとも 2 種類のポリシーを用意します。データ保守や DML を行う管理者向けにすべての行を見せるポリシーと、業務ロジックで絞り込む一般ユーザー向けのポリシーです。最後の 1 つのポリシーは DROP ROW ACCESS POLICY では削除できず、DROP ALL ROW ACCESS POLICIES を使う必要がある点も覚えておいてください。
列レベルセキュリティとデータマスキング
列レベルセキュリティは、ポリシータグ(Knowledge Catalog ベース)またはデータガバナンスタグ(Resource Manager ベース)を列に割り当てることで、特定の列を丸ごと見せない、または加工して見せる仕組みです。設定の流れは次のとおりです。
- taxonomy(分類体系)とポリシータグを定義する(例:「機密度」taxonomy の下に「高」「中」ノードを作る)
bq updateでテーブルのスキーマにポリシータグを割り当てる(スキーマ JSON のpolicyTags.namesにタグの完全修飾 ID を指定)- taxonomy 側でアクセス制御を有効化する
- ポリシータグごとに IAM でアクセスできるプリンシパルを管理する(
Fine-Grained Readerロール等)
列レベルセキュリティはデータセットの ACL に加えて適用されます。つまりユーザーは「データセットへのアクセス権」と「ポリシータグへのアクセス権」の両方を持って初めて、保護された列を読めます。ここでも権限は掛け算で効きます。
列を丸ごと隠すのではなく、値を加工して見せたい場合は動的データマスキングを重ねます。ポリシータグの上にデータポリシーとしてマスキングルールを設定する仕組みで、次のようなルールが用意されています。
| マスキングルール | 効果 |
|---|---|
| Nullify | 値を NULL に置換 |
| デフォルト値 | 型ごとの既定値に置換 |
| Email マスク | user@example.com → XXXXX@example.com |
| 先頭 4 文字のみ / 末尾 4 文字のみ | 残りを XXXXX に置換 |
| 日付の年のみ | 日付を年初日に丸める |
| SHA-256 ハッシュ | 決定的ハッシュ化(同じ値は同じハッシュになるため JOIN 可能) |
| ランダムハッシュ | クエリごとにソルトを変えるハッシュ化 |
| カスタム関数 | ユーザー定義関数で任意の加工 |
マスク済みデータを見るユーザーには Masked Reader ロールを、元データを見られるユーザーには Fine-Grained Reader ロールを割り当てます。同じユーザーに複数のマスキングルールが競合する場合(異なるルールを持つ複数グループに所属している場合など)は、taxonomy の構造とは無関係に、ルール種別ごとに固定された優先順位(カスタムマスキングルーティン → ランダムハッシュ → SHA-256 → メールマスク → 末尾 4 文字 → 先頭 4 文字 → 日付の年 → デフォルト値 → NULL 化)で適用されるルールが決まります。
暗号化・監査ログ・ガバナンス統合・データ共有
ここから先は、初期構築時に組織全体の設定が絡むため「読み物」として位置づけます。仕組みと現行の名称を正確に押さえておいてください。
CMEK(顧客管理暗号鍵)
BigQuery はデフォルトですべてのデータを保存時に暗号化します。テーブルのデータはまずデータ暗号化キー(DEK)で暗号化され、その DEK 自体がさらに鍵暗号化キー(KEK)で保護される、エンベロープ暗号化という仕組みです。既定では KEK も Google が管理しますが、CMEK を使うと KEK を自分の Cloud KMS の鍵に置き換えられます。
設定は、BigQuery のサービスアカウント(bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com)に Cloud KMS の鍵への Cloud KMS CryptoKey Encrypter/Decrypter ロールを付与し、テーブル作成時や組織ポリシーでその鍵を指定する、という流れです。
gcloud kms keys add-iam-policy-binding KMS_KEY \
--project=KMS_PROJECT_ID \
--location=KMS_KEY_LOCATION \
--keyring=KMS_KEY_RING \
--member=serviceAccount:bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com \
--role=roles/cloudkms.cryptoKeyEncrypterDecrypter
鍵を無効化・削除して BigQuery からのアクセスを断つとどうなるかは明確にドキュメント化されています。IAM の権限変更が伝播するまで最大 1 時間はそのまま動きますが、その後は保護されたテーブルへの query・cp・extract・データ読み取りがすべて失敗し、新しいデータの追加もできなくなります。アクセスを復旧しても、その後のクエリ性能が数日間低下することがある、という点も明記されています。鍵の無効化は「即座に読めなくなる強力な取り消し手段」であると同時に、事故で無効化すると影響が長引く操作でもあります。
監査ログ — 「誰が何を読んだか」を追う
BigQuery の操作は Cloud Audit Logs に記録されます。各 API メソッドは ADMIN_READ・ADMIN_WRITE・DATA_READ・DATA_WRITE のいずれかの権限タイプに分類され、ADMIN_WRITE の操作は Admin Activity ログに、DATA_READ・DATA_WRITE・ADMIN_READ の操作は Data Access ログに記録されます。テーブルデータの読み取り(tabledataservice.list やクエリ結果の取得)は DATA_READ に分類されるため、Data Access ログを見れば「誰がどのテーブルのデータを読んだか」を追えます。
ログの本文は protoPayload.metadata フィールドに格納され、現行の推奨フォーマットは BigQueryAuditMetadata です(resource.type が bigquery_project または bigquery_dataset)。Cloud Logging の Logs Explorer で次のようなクエリを使うと、BigQuery 関連の監査ログ全体を絞り込めます。
protoPayload.serviceName="bigquery.googleapis.com"
logName="projects/PROJECT_ID/logs/cloudaudit.googleapis.com%2Fdata_access"
クエリの実行時間やスキャンしたスロット量など「ジョブの性能」を分析したい場合は、監査ログではなく INFORMATION_SCHEMA のジョブメタデータ系ビューを使います。監査ログは「誰が・いつ・何をしたか」の記録、INFORMATION_SCHEMA は「そのジョブがどう実行されたか」の分析、という役割分担です。
ガバナンス統合 — Knowledge Catalog
BigQuery のガバナンス機能はKnowledge Catalogという基盤の上に構築されています。組織内のすべてのデータ資産(BigQuery だけでなく Spanner・Cloud SQL・Pub/Sub・Cloud Storage 等も対象)のメタデータを一元的な棚卸し台帳として保持し、AI・機械学習でメタデータ間の関係性や意味を発見します。Knowledge Catalog 上に構築される主な機能は次のとおりです。
- データ検索 — セマンティック検索(自然言語での検索、プレビュー機能)とキーワード検索でデータ資産を横断的に発見する
- データリネージ(data lineage) — テーブル・列レベルで、データがどこから来てどこへ渡り、どんな変換を経たかを追跡する
- データプロファイリング — テーブルの列の統計的特徴を自動で把握する
- データ品質 — BigQuery と Cloud Storage 上のテーブルに対して定義したルールでデータ品質チェックを定期実行する
- ビジネス用語集 — 組織の用語をグロッサリーとして定義し、データ資産のフィールドに紐づける
列レベルセキュリティのポリシータグも、この Knowledge Catalog の分類体系機能の上に成り立っています。「タグ付けして検索・追跡できるようにする」機能と「タグに基づいてアクセスを制御する」機能が、同じ基盤を共有していると捉えると全体像がつながります。
データ共有 — BigQuery sharing
組織を跨いでデータを共有する仕組みは、現在はBigQuery sharingという名称です(旧称 Analytics Hub。IAM のロール名は現在も roles/analyticshub.admin 等 analyticshub のまま残っています)。データセットや Pub/Sub トピックをコピーせずにそのまま共有できる、パブリッシャー / サブスクライバーのモデルです。
サブスクライバーが購読すると、実体のコピーではなくlinked dataset(元データへの読み取り専用ポインタ)が自分のプロジェクトに作られます。共有されるデータセットには行レベルセキュリティ・列レベルセキュリティもそのまま適用できるため、「組織を跨いで共有しつつ、行/列は制限する」という構成も可能です。パブリッシャー側は誰がどれだけ使ったか(ジョブ数・スキャンバイト数・サブスクライバー数)を使用状況メトリクスとして確認できます。より機密性の高い共同分析には、データを移動・開示せずに複数者で突き合わせ分析ができるデータクリーンルームという選択肢もあります。
演習用データセットを作り、架空のグループへ dataViewer ロールを付与してから、設定内容を確認します。サンドボックスで完走可・無料枠内(クエリのスキャン量はごく少量)。
- 演習用データセットを作成します(ロケーションは US)。
bq mk --location=US --dataset PROJECT_ID:handson_ch16
- SQL の
GRANT文でデータセットにdataViewerロールを付与します。
-- 処理: メタデータ操作のみ(クエリのスキャンは発生しない)
GRANT `roles/bigquery.dataViewer`
ON SCHEMA `PROJECT_ID`.handson_ch16
TO "group:analysts-demo@example.com";
- 付与内容を
bq showで確認します。
bq show --format=prettyjson PROJECT_ID:handson_ch16
期待される出力: access 配列の中に、role: "READER"(dataViewer は基本ロールの READER にマッピングされます)と groupByEmail: "analysts-demo@example.com" のエントリが追加されていることを確認してください。
後片付け:
bq rm -r -f -d PROJECT_ID:handson_ch16
元データを持つデータセットと、ビューだけを持つデータセットの 2 つを作り、承認済みビューを設定します。サンドボックスで完走可・無料枠内(テーブルは数行、クエリのスキャン量は数十バイト程度)。
- 元データ用データセットとビュー用データセットの 2 つを作成します。
bq mk --location=US --dataset PROJECT_ID:handson_ch16_source
bq mk --location=US --dataset PROJECT_ID:handson_ch16_views
- 元データ用データセットに、リテラル値からテーブルを作成します(DML を使わず
CREATE TABLE ... AS SELECTで作るため、サンドボックスでも実行できます)。
CREATE TABLE `PROJECT_ID.handson_ch16_source.regional_sales` AS
SELECT * FROM UNNEST([
STRUCT('APAC' AS region, 'acme-jp' AS customer_id, 120000 AS revenue_jpy),
STRUCT('APAC' AS region, 'acme-kr' AS customer_id, 98000 AS revenue_jpy),
STRUCT('AMER' AS region, 'acme-us' AS customer_id, 210000 AS revenue_jpy)
]);
- ビュー用データセットに、地域ごとの合計だけを返す集計ビューを作成します。
customer_idのような個票の列は含めません。
-- 処理: 数十バイト程度
CREATE VIEW `PROJECT_ID.handson_ch16_views.regional_sales_summary` AS
SELECT region, SUM(revenue_jpy) AS total_revenue_jpy
FROM `PROJECT_ID.handson_ch16_source.regional_sales`
GROUP BY region;
- コンソールで元データ用データセット(
handson_ch16_source)を開き、Sharing > Authorize views をクリックします。 - Authorized view にビューの完全修飾名
PROJECT_ID.handson_ch16_views.regional_sales_summaryを入力し、Add authorization をクリックします。
これでビューが元データセットへのアクセスを代行できるようになりました。ビューにクエリすると集計結果が返ります。
SELECT * FROM `PROJECT_ID.handson_ch16_views.regional_sales_summary`
ORDER BY region;
期待される出力: AMER/210000、APAC/218000 の 2 行。実務では、分析者には handson_ch16_views データセットへの dataViewer だけを付与し、handson_ch16_source へは一切権限を与えません(自分自身はプロジェクトオーナーのため元データセットにも常にアクセスできてしまいますが、別ユーザー・別グループで試すとこの分離を確認できます)。
後片付け:
bq rm -r -f -d PROJECT_ID:handson_ch16_views
bq rm -r -f -d PROJECT_ID:handson_ch16_source
地域別の売上テーブルに行アクセスポリシーを設定し、自分自身のセッションユーザーに対するフィルタが効くことを確認します。サンドボックスで完走可・無料枠内(DDL のみで、DML は使いません)。
- 演習用データセットとテーブルを作成します。
owner_email列に自分の Google アカウントのメールアドレスを 1 行だけ入れておきます。
bq mk --location=US --dataset PROJECT_ID:handson_ch16
CREATE TABLE `PROJECT_ID.handson_ch16.regional_sales` AS
SELECT * FROM UNNEST([
STRUCT('APAC' AS region, 120000 AS revenue_jpy, 'YOUR_EMAIL@example.com' AS owner_email),
STRUCT('AMER' AS region, 210000 AS revenue_jpy, 'someone-else@example.com' AS owner_email)
]);
- まず管理者向けに、全行を見せるポリシーを作ります(最後の 1 件は
DROP ROW ACCESS POLICYで消せないため、先に全件許可のポリシーを作っておくと運用しやすくなります)。
CREATE ROW ACCESS POLICY admin_full_access
ON `PROJECT_ID.handson_ch16.regional_sales`
GRANT TO ("user:YOUR_EMAIL@example.com")
FILTER USING (TRUE);
- 次に、
SESSION_USER()を使い「自分がowner_emailの行だけ見える」フィルタポリシーを作ります。
CREATE ROW ACCESS POLICY owner_only_filter
ON `PROJECT_ID.handson_ch16.regional_sales`
GRANT TO ("domain:example.com")
FILTER USING (owner_email = SESSION_USER());
- ポリシーの一覧を確認し、クエリを実行します。
bq ls --row_access_policies handson_ch16.regional_sales
-- 処理: 数十バイト程度
SELECT region, revenue_jpy, owner_email
FROM `PROJECT_ID.handson_ch16.regional_sales`;
期待される出力: admin_full_access(user: 指定、常に全行)と owner_only_filter(domain: 指定、SESSION_USER() と一致する行のみ)の 2 つのポリシーが適用され、両方の和集合として結果が返ります。自分の Google アカウントは admin_full_access の対象でもあるため、この演習では 2 行とも見えます。owner_email = 'someone-else@example.com' の行だけを見せたい別ユーザーがいた場合、そのユーザーは owner_only_filter だけの対象になり、1 行しか見えません。
後片付け:
DROP ALL ROW ACCESS POLICIES ON `PROJECT_ID.handson_ch16.regional_sales`;
bq rm -r -f -d PROJECT_ID:handson_ch16
つまずきポイント
grantee_list に載っていても、そのテーブル自体への dataViewer(bigquery.tables.getData)権限がなければアクセス拒否になります。列レベルセキュリティも同様に、データセット ACL とポリシータグ権限の両方が要ります。「制御を追加したのにまだ見えない/見えるはずなのに見えない」ときは、まずこの 2 層のどちらが欠けているかを疑ってください。DROP ROW ACCESS POLICY で消そうとしてエラーになる: テーブルに残っている最後の 1 つのポリシーは通常の DROP ROW ACCESS POLICY では削除できない仕様です。全部消したいときは DROP ALL ROW ACCESS POLICIES を使ってください。理解度チェック
Q1. roles/bigquery.jobUser と roles/bigquery.dataViewer の両方を持たないと BigQuery でクエリを実行してデータを取得できないのはなぜですか?
BigQuery では「ジョブ(クエリ)を作成する権限」と「テーブルのデータを読む権限」が独立した権限体系になっているためです。jobUser はプロジェクト内でジョブを作成する権限だけを持ち、データへのアクセス権は含みません。dataViewer はデータへの読み取り権限を持ちますが、それだけではジョブ自体を投げられません。両方がそろって初めてクエリが成功します。
Q2. roles/bigquery.jobUser と roles/bigquery.user の違いを説明してください。
jobUser はジョブの作成(bigquery.jobs.create)に特化したロールで、プロジェクトにしか付与できません。user はジョブ作成に加えて、自分のジョブの一覧・キャンセルや、プロジェクト内に新規データセットを作成する権限まで含みます(作成したデータセットでは自動的に dataOwner になります)。
Q3. 承認済みビューと行レベルセキュリティは、どちらも「見せたくないデータを隠す」ための機能ですが、仕組みとして何が違いますか?
承認済みビューは、別のデータセットに作ったビューに元データセットへのアクセスを個別に認可する「別の入り口を作る」方式です。分析者は元データセットへの権限を一切持ちません。行レベルセキュリティは、同じテーブルに対してクエリしたユーザーによって見える行を動的に変える「同じ入り口の中で絞り込む」方式です。
Q4. CMEK(顧客管理暗号鍵)を有効化すると、BigQuery の既定の暗号化と比べて何が変わりますか?
BigQuery は CMEK の有無にかかわらず、すべてのデータを保存時にエンベロープ暗号化で保護しています。CMEK が変えるのは、データ暗号化キーを保護する鍵暗号化キーを Google 管理から自分の Cloud KMS の鍵に置き換える点だけです。暗号化そのものの有無を切り替える機能ではありません。
Q5.(シナリオ)人事部が持つ従業員テーブルを分析チームに渡したい。ただし従業員 ID や氏名などの個票は一切見せず、部門ごとの人数・平均給与といった集計値だけを渡したい。最も適した方法は?
A. 行アクセスポリシーで部門ごとにフィルタする B. 列ポリシータグで氏名列だけマスキングする C. 承認済みビューで集計クエリの結果だけを見せる D. データセット全体に dataViewer を付与する
C。 個票そのものへのアクセスを一切許さず、集計済みの結果だけを渡したいので、別データセットに集計ビューを作り元データセットへの直接アクセス権を与えない承認済みビューが最適です。A は行を絞るだけで個票自体は見えてしまいます。B は氏名だけは隠せますが従業員 ID 等の個票行は残ります。D は個票を含む元データセットをそのまま渡すことになり、要件に反します。
まとめと次章へ
- BigQuery IAM では「ジョブを実行する権限」(
jobUser系)と「データを読む権限」(dataViewer系)が独立した軸で、両方そろって初めてクエリが成功する - 承認済みビュー・承認済みデータセットは、元テーブルへの直接アクセスを許さず「集計結果だけ」を別の入り口として共有する仕組み
- 行アクセスポリシーと列ポリシータグ(+データマスキング)は、同じテーブルに対して重ねがけできる 2 層のフィルタ。どちらも「テーブル自体への権限」に追加で適用される点に注意する
- CMEK・監査ログ・Knowledge Catalog によるガバナンス統合・BigQuery sharing(旧 Analytics Hub)による組織を跨ぐ共有は、いずれも「誰が管理者か」「誰が何をしたか」「見つけて追跡できるか」「安全に共有できるか」という守りの仕組みを、既存の IAM モデルの上に積み重ねたもの
- 次章では、これらのセキュリティ機構を踏まえた上で、コストとパフォーマンスの運用管理を扱う