BigQuery にはポリシータグという仕組みがあり、これを使うことで BigQuery のリソースに対してきめ細やかなアクセス制御を行うことができる。
この記事では、ポリシータグを使って任意のカラムに対するアクセス制御を実現する方法を紹介する。
ポリシータグの仕組み
ポリシータグは、それ自体がひとつの独立したリソースである。そのためポリシータグを利用したい場合、まずは「ポリシータグの作成」という工程を踏まなければならない。
そして作成したポリシータグを、アクセス制御したい任意のカラムに対して付与する。そうするとそれ以降、明示的に許可されたプリンシパル(サービスアカウントやユーザーアカウント)以外は、そのカラムにアクセスできなくなる。roles/ownerであっても例外ではなく、明示的に許可されていなければアクセスできない。
「明示的な許可」は、対象のポリシータグに対する特定のロールをプリンシパルに設定する、という形で行われる。そうするとそのプリンシパルは、対象のポリシータグが付与されているカラムにアクセスできるようになる。
登場人物が多いのでややこしいかもしれない。しかし実際に作業してみるとそれほど複雑な話ではないと理解できるので、以下に具体例を示す。
ポリシータグの作成、カラムに対するポリシータグの付与、ポリシータグに対するロールの設定、という順番で進める。
前提
一連の作業は全てsample-pjというプロジェクトで行うものとする。
基本的に gcloud CLI を使って操作していく。
gcloud CLI の基本的な使い方については以下に書いた。
Google Cloud Data Catalog API と BigQuery Data Policy API が有効になっている必要があるので、まだ有効になっていなければ、以下のコマンドを実行して有効にする。
$ gcloud services enable datacatalog.googleapis.com $ gcloud services enable bigquerydatapolicy.googleapis.com
タクソノミーとポリシータグを作成する
ポリシータグは必ず、何らかのタクソノミーに属している形にしなければならない。そのため、ポリシータグを作るためにはまず、タクソノミーというリソースを用意する必要がある。
東京リージョンのタクソノミーの一覧を取得してみる。
$ gcloud data-catalog taxonomies list --location=asia-northeast1 Listed 0 items.
何もないことが分かるので、新しく作成する。
タクソノミーの作成時に指定が必要なのはdisplayNameとactivatedPolicyTypesで、activatedPolicyTypesは必ずFINE_GRAINED_ACCESS_CONTROLにする必要がある。displayNameは何でもよいので、今回はsample-taxonomyにしておく。
https://datacatalog.googleapis.com/v1/projects/[PROJECT_NAME]/locations/asia-northeast1/taxonomiesに POST リクエストを送ることで、東京リージョンにタクソノミーが作られる。
$ curl -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ -d '{ "displayName": "sample-taxonomy", "activatedPolicyTypes": ["FINE_GRAINED_ACCESS_CONTROL"] }' \ "https://datacatalog.googleapis.com/v1/projects/sample-pj/locations/asia-northeast1/taxonomies"
再びタクソノミー一覧を取得すると、確かにsample-taxonomyが作られている。
$ gcloud data-catalog taxonomies list --location=asia-northeast1 --- activatedPolicyTypes: - FINE_GRAINED_ACCESS_CONTROL displayName: sample-taxonomy name: projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827 service: {} taxonomyTimestamps: createTime: '2025-07-11T09:59:12.313Z' updateTime: '2025-07-11T09:59:12.313Z'
このタクソノミー(projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827)に属しているポリシータグ一覧もgcloudで取得できるので、確認してみる。
$ gcloud data-catalog taxonomies policy-tags list \ --taxonomy="4086922810988755827" \ --location=asia-northeast1 Listed 0 items.
この時点ではまだ何も無いので、ポリシータグを新しく作成する。
エンドポイントはhttps://datacatalog.googleapis.com/v1/[親となるタクソノミーのname]/policyTags。これに POST リクエストを送る。
作成するポリシータグの属性として指定するのはdisplayNameだけでよい。今回はsample-policy-tagにする
curl -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ -d '{ "displayName": "sample-policy-tag", }' \ "https://datacatalog.googleapis.com/v1/projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags"
再びポリシータグ一覧を確認してみると、確かに作られている。
$ gcloud data-catalog taxonomies policy-tags list \ --taxonomy="4086922810988755827" \ --location=asia-northeast1 --- displayName: sample-policy-tag name: projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags/1693282666088238152
動作確認用のテーブルとサービスアカウントを作成する
次に、動作確認用のテーブルを作成する。
policyデータセットにuserテーブルを作ることにする。
$ bq --location=asia-northeast1 mk --dataset sample-pj:policy $ bq mk --table policy.user "id:string, name:string"
policy.userにデータを投入し、以下の状態にする。
$ bq head -n 10 policy.user +-----+---------+ | id | name | +-----+---------+ | 001 | Alice | | 002 | Bob | | 003 | Charlie | | 004 | David | +-----+---------+
次に、サービスアカウントを作成する。
bq-advanced-userとbq-standard-userを作成する。
どちらに対しても、プロジェクト全体に対するroles/bigquery.dataEditorとroles/bigquery.jobUserを設定する。
そうするとbq-advanced-userとbq-standard-userのどちらもで、policy.userの中身を全て見れる。
$ bq head -n 10 policy.user +-----+---------+ | id | name | +-----+---------+ | 001 | Alice | | 002 | Bob | | 003 | Charlie | | 004 | David | +-----+---------+
サービスアカウントの発行やロールの付与、 gcloud で使用するサービスアカウントの切り替え、などはこの記事では説明しないが、以下の記事などで触れている。
numb86-tech.hatenablog.com numb86-tech.hatenablog.com
特定のカラムにポリシータグを付与する
ここまでで、ポリシータグ、テーブル、サービスアカウントを用意できたので、次はuserテーブルのカラムにポリシータグを付与する。
今回はnameカラムにsample-policy-tagを付与する。
まずは現在のスキーマを確認。
$ bq show --schema --format=prettyjson policy.user [ { "name": "id", "type": "STRING" }, { "name": "name", "type": "STRING" } ]
スキーマを更新するために、以下の内容のschema.jsonを用意する。現在のスキーマにpolicyTagsを追加している。
[ { "name": "id", "type": "STRING" }, { "name": "name", "type": "STRING", "policyTags": { "names": ["projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags/1693282666088238152"] } } ]
この状態で$ bq update --schema=schema.json policy.userを実行すると、スキーマが更新されてポリシータグが付与される。
$ bq show --schema --format=prettyjson policy.user [ { "name": "id", "type": "STRING" }, { "name": "name", "policyTags": { "names": [ "projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags/1693282666088238152" ] }, "type": "STRING" } ]
この時点で、nameカラムにアクセスできるプリンシパルは存在しない。
出力結果にnameが含まれるようなコマンドや SQL クエリ(SELECT id, nameやSELECT *など)は、権限がないため失敗する。
bq-advanced-userもbq-standard-userもそうだし、roles/ownerを付与されているプリンシパルであっても例外ではない。
$ bq head -n 10 policy.user BigQuery error in head operation: User does not have permission to access data protected by policy tag "sample-taxonomy : sample-policy-tag" on column sample-pj:policy.user.name. $ bq query --use_legacy_sql=false 'SELECT id, name FROM policy.user' BigQuery error in query operation: Error processing job 'sample-pj:bqjob_r378908fa72c54ad_000001970c566c6a_1': Access Denied: BigQuery BigQuery: User has neither fine-grained reader nor masked get permission to get data protected by policy tag "sample-taxonomy : sample-policy-tag" on column policy.user.name. $ bq query --use_legacy_sql=false 'SELECT * FROM policy.user' BigQuery error in query operation: Error processing job 'sample-pj:bqjob_r7d7aa40316e7103b_000001970c656d12_1': Access Denied: BigQuery BigQuery: User has neither fine-grained reader nor masked get permission to get data protected by policy tag "sample-taxonomy : sample-policy-tag" on column policy.user.name.
出力結果にnameが含まれない SQL クエリは成功する。
$ bq query --use_legacy_sql=false 'SELECT id FROM policy.user' +-----+ | id | +-----+ | 001 | | 002 | | 003 | | 004 | +-----+ $ bq query --use_legacy_sql=false 'SELECT * EXCEPT (name) FROM policy.user' +-----+ | id | +-----+ | 001 | | 002 | | 003 | | 004 | +-----+
オーナーとして BigQuery Studio でテーブルを見てみると、以下のような表示になっている。

「プレビュー」からもnameが除外されている。

ロールを設定してアクセスを許可する
冒頭で説明したように、ポリシータグに対するロールを設定することで、そのポリシータグが付与されているカラムにアクセスできるようになる。
具体的にはroles/datacatalog.categoryFineGrainedReaderというロールを付与する。
今回はbq-advanced-userのみがnameカラムにアクセスできるようにする。
以下のコマンドを実行するとbq-advanced-userが1693282666088238152(sample-policy-tag)に対してroles/datacatalog.categoryFineGrainedReaderを持つように設定できる。
$ gcloud data-catalog taxonomies policy-tags add-iam-policy-binding \ 1693282666088238152 \ --taxonomy="4086922810988755827" \ --member="serviceAccount:bq-advanced-user@sample-pj.iam.gserviceaccount.com" \ --role="roles/datacatalog.categoryFineGrainedReader" \ --location=asia-northeast1
1693282666088238152の IAM ポリシーを確認してみると、正しく設定されている。
$ gcloud data-catalog taxonomies policy-tags get-iam-policy \ "1693282666088238152" \ --taxonomy="4086922810988755827" \ --location=asia-northeast1 bindings: - members: - serviceAccount:bq-advanced-user@sample-pj.iam.gserviceaccount.com role: roles/datacatalog.categoryFineGrainedReader etag: BwY2CIPBGmE= version: 1
これで、bq-advanced-userはnameカラムにアクセスできるようになる。
先ほどエラーになった以下のコマンドはいずれも成功するようになる。
$ bq head -n 10 policy.user$ bq query --use_legacy_sql=false 'SELECT id, name FROM policy.user'$ bq query --use_legacy_sql=false 'SELECT * FROM policy.user'
+-----+---------+ | id | name | +-----+---------+ | 001 | Alice | | 002 | Bob | | 003 | Charlie | | 004 | David | +-----+---------+
bq-standard-userや他のプリンシパルでは引き続きエラーになる。
まとめ
ポリシータグでカラムのアクセス制御を行うために必要な作業は以下の通り。その作業を行うための権限を持った事前定義ロールの例も書いておく。
- タクソノミーとポリシータグを作成する
roles/datacatalog.categoryAdmin
- テーブルのスキーマを変更して、アクセス制御したいカラムにポリシータグを付与する
roles/bigquery.dataOwner
- アクセスを許可したいプリンシパルに対して
roles/datacatalog.categoryFineGrainedReaderを設定するroles/datacatalog.categoryAdmin
dbt によるポリシータグの付与
カラムに対するポリシータグの付与を dbt で行うこともできる。
dbt_project.ymlとカラム定義それぞれに、必要な情報を書けばよい。
まずdbt_project.ymlでpersist_docsを有効にしておく。
models: my_dbt_project: +persist_docs: columns: true
次に、以下の内容のdbt_sample.sqlを用意する。
{{
config(
materialized="table",
)
}}
SELECT 'abc' AS foo, 'xyz' AS bar
そしてそのdbt_sampleのカラム定義を書く際に、policy_tagsを記述する。
version: 2 models: - name: dbt_sample columns: - name: foo - name: bar policy_tags: - 'projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags/1693282666088238152'
この状態で$ dbt runを実行すると、指定したポリシータグが付与される。
$ bq show --schema --format=prettyjson policy.dbt_sample [ { "description": "", "name": "foo", "type": "STRING" }, { "description": "", "name": "bar", "policyTags": { "names": [ "projects/sample-pj/locations/asia-northeast1/taxonomies/4086922810988755827/policyTags/1693282666088238152" ] }, "type": "STRING" } ]
なお、$ dbt runを実行するプリンシパルが必要な権限を持っている必要があるが、事前定義ロールを使う場合はroles/datacatalog.categoryAdminとroles/bigquery.jobUserを設定すれば条件を満たせる。