Google Cloud には権限借用( Impersonation )という仕組みがあり、これを使うと、「プリンシパル(ユーザーアカウントやサービスアカウント)が一時的に他のサービスアカウントになりすまして Google Cloud を操作する」ということができる。これにより、本来そのプリンシパルには出来ない操作を特定の状況で一時的にできるようにする、といったことが可能になる。
dbt も権限借用に対応しており、 dbt の実行時にのみ BigQuery に対する編集権限を付与することなどができるようになる。
この記事では、 dbt で権限借用を利用する方法や、それを行った際に Google Cloud のログにどのように記録されるのか、どのように活用しうるのか、などを見ていく。
この記事の内容は以下の環境で動作確認している。
- dbt-core v1.11.8
- dbt-bigquery v1.11.1
事前準備
Google Cloud のsample-pjというプロジェクトの BigQuery に、srcというデータセットを作り、そこにuserテーブルを用意する。データも入れておく。
以下の状況にしておく。
$ bq show sample-pj:src.user Table sample-pj:src.user Last modified Schema Total Rows Total Bytes Expiration Time Partitioning Clustered Fields Total Logical Bytes Total Physical Bytes Labels ----------------- ---------------------------- ------------ ------------- ------------ ------------------- ------------------ --------------------- ---------------------- -------- 11 Apr 21:39:40 |- id: integer (required) 2 28 28 1131 |- name: string (required)
$ bq head sample-pj:src.user +----+-------+ | id | name | +----+-------+ | 1 | alice | | 2 | bob | +----+-------+
次に、検証用のサービスアカウントを 2 つ用意する。
まずはdbt-runner。
$ gcloud iam service-accounts create dbt-runner \ --project=sample-pj Created service account [dbt-runner]. $ gcloud projects add-iam-policy-binding sample-pj \ --member="serviceAccount:dbt-runner@sample-pj.iam.gserviceaccount.com" \ --role="roles/bigquery.jobUser" $ gcloud projects add-iam-policy-binding sample-pj \ --member="serviceAccount:dbt-runner@sample-pj.iam.gserviceaccount.com" \ --role="roles/bigquery.dataEditor"
roles/bigquery.jobUserとroles/bigquery.dataEditorを付与している。これにより BigQuery のジョブの実行やデータの読み書きを行えるようになっているので、dbt-runnerは BigQuery を対象としたdbtコマンドを実行できる。
$ gcloud projects get-iam-policy sample-pj --flatten="bindings[].members" --filter="bindings.members:dbt-runner@sample-pj.iam.gserviceaccount.com" --format="json(bindings.role)" [ { "bindings": { "role": "roles/bigquery.dataEditor" } }, { "bindings": { "role": "roles/bigquery.jobUser" } } ]
次にnormal-user。
$ gcloud iam service-accounts create normal-user \ --project=sample-pj Created service account [normal-user]. $ gcloud projects add-iam-policy-binding sample-pj --member="serviceAccount:normal-user@sample-pj.iam.gserviceaccount.com" --role="roles/bigquery.metadataViewer"
このサービスアカウントにはroles/bigquery.metadataViewerのみを付与している。メタデータ(テーブルのスキーマなど)は取得できるが、テーブルに格納されているデータにアクセスすることはできない。
$ gcloud projects get-iam-policy sample-pj --flatten="bindings[].members" --filter="bindings.members:normal-user@sample-pj.iam.gserviceaccount.com" --format="json(bindings.role)" [ { "bindings": { "role": "roles/bigquery.metadataViewer" } } ]
そして、normal-userがdbt-runnerを権限借用できるようにする。
具体的には、dbt-runnerに対して、normal-userをroles/iam.serviceAccountTokenCreatorとして追加する。
$ gcloud iam service-accounts add-iam-policy-binding dbt-runner@sample-pj.iam.gserviceaccount.com --member="serviceAccount:normal-user@sample-pj.iam.gserviceaccount.com" --role="roles/iam.serviceAccountTokenCreator"
こうするとnormal-userは、必要に応じて「dbt-runnerとして認証されるアクセストークン」を発行しそれを利用することができるようになる。
これ以降はnormal-userとして各種操作を行うので、認証を行う。
まずはnormal-userのサービスアカウントキーを発行する。
$ gcloud iam service-accounts keys create normal-user-key.json --iam-account=normal-user@sample-pj.iam.gserviceaccount.com
normal-user-key.jsonが発行されたので、これを使って gcloud CLI の認証を行う。
$ gcloud auth login --cred-file=normal-user-key.json
dbt では ADC の認証情報を使うので、それも設定しておく。
$ export GOOGLE_APPLICATION_CREDENTIALS=normal-user-key.json
GOOGLE_APPLICATION_CREDENTIALSとは何か、そもそも ADC とは何か、ということについては以下の記事を参照。
normal-user の権限を確認する
normal-userとして認証されている状態になったので、このアカウントで何ができるのか、何ができないのか、確認しておく。
まず、データセット一覧やテーブルのスキーマを得ることはできる。
これは、roles/bigquery.metadataViewerを付与されているため。
$ bq ls --project_id=sample-pj datasetId --------------------- src $ bq show sample-pj:src.user Table sample-pj:src.user Last modified Schema Total Rows Total Bytes Expiration Time Partitioning Clustered Fields Total Logical Bytes Total Physical Bytes Labels ----------------- ---------------------------- ------------ ------------- ------------ ------------------- ------------------ --------------------- ---------------------- -------- 11 Apr 21:51:07 |- id: integer (required) 2 28 28 1131 |- name: string (required)
しかし、テーブルの中身をみることはできない。roles/bigquery.metadataViewerにはbigquery.tables.getDataは含まれていないためである。
$ bq head sample-pj:src.user BigQuery error in head operation: Access Denied: Table sample-pj:src.user: Permission bigquery.tables.getData denied on table sample-pj:src.user (or it may not exist).
次にdbt runを実行できるか確認する。
以下のような dbt プロジェクトを用意する。
# profiles.yml impersonation: target: dev outputs: dev: type: bigquery method: oauth project: sample-pj dataset: dest threads: 1 location: asia-northeast1
# models/sources.yml sources: - name: src database: sample-pj schema: src tables: - name: user columns: - name: id data_type: integer - name: name data_type: string
-- models/user.sql select id, name, upper(name) as name_upper from {{ source('src', 'user') }}
この内容でdbt runを実行すると以下のエラーになる。
Database Error in model user (models/user.sql)
Access Denied: Project sample-pj: User does not have bigquery.jobs.create permission in project sample-pj.
normal-userに付与されているロールはroles/bigquery.metadataViewerのみであり、このロールはbigquery.jobs.createを持っていないため、エラーになっている。
権限借用を利用する
profiles.ymlで定義しているdev target の設定を変え、dbt-runnerの権限を借用してdbtコマンドを実行するようにする。
具体的には、impersonate_service_accountフィールドを用意し、権限を借りるサービスアカウント、つまりdbt-runnerを設定する。
impersonation: target: dev outputs: dev: type: bigquery method: oauth project: sample-pj dataset: dest threads: 1 location: asia-northeast1 impersonate_service_account: dbt-runner@sample-pj.iam.gserviceaccount.com
この状態でdbt runを実行すると、成功する。
意図通り、destデータセットにuserが作成されている。
$ bq show sample-pj:dest.user Table sample-pj:dest.user Last modified Schema Type Expiration Labels ----------------- ----------------------- ------ ------------ -------- 11 Apr 23:30:40 |- id: integer VIEW |- name: string |- name_upper: string
impersonate_service_accountを設定したことで、dbtコマンド実行時は、roles/bigquery.jobUserとroles/bigquery.dataEditorを付与されたdbt-runnerとして振る舞うようになったためである。
bqコマンド実行時は、引き続きデータにアクセスできない。
$ bq head sample-pj:src.user BigQuery error in head operation: Access Denied: Table sample-pj:src.user: Permission bigquery.tables.getData denied on table sample-pj:src.user (or it may not exist). $ bq head sample-pj:dest.user BigQuery error in head operation: Access Denied: Table sample-pj:dest.user: Permission bigquery.tables.getData denied on table sample-pj:dest.user (or it may not exist).
dbtコマンドの実行時以外は権限借用が行われず、normal-userとしてコマンドを実行するためである。
Google Cloud のログにはどのように記録されるか
権限借用して dbt を実行した場合、 BigQuery のINFORMATION_SCHEMAには、dbt-runnerがジョブの実行者として記録される。
SELECT job_id, user_email, state FROM `region-asia-northeast1`.INFORMATION_SCHEMA.JOBS WHERE project_id = 'sample-pj' AND creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY) AND EXISTS ( SELECT 1 FROM UNNEST(labels) AS label WHERE label.key = 'dbt_invocation_id' ) ORDER BY creation_time DESC LIMIT 100
| job_id | user_email | state |
|---|---|---|
| 9ca4f964-4f7f-45eb-b6e0-39703193626b | dbt-runner@sample-pj.iam.gserviceaccount.com | DONE |
Cloud Logging にはより詳細な情報が記録されており、「normal-userが権限借用した」ということが分かるようになっている。
$ gcloud logging read \ 'resource.type="bigquery_project" AND protoPayload.methodName="google.cloud.bigquery.v2.JobService.InsertJob" AND protoPayload.authenticationInfo.principalEmail="dbt-runner@sample-pj.iam.gserviceaccount.com"' \ --project=sample-pj \ --freshness=1d \ --limit=1 \ --format=json | jq '.[0].protoPayload.authenticationInfo' { "oauthInfo": { "oauthClientId": "111552143688807246465" }, "principalEmail": "dbt-runner@sample-pj.iam.gserviceaccount.com", "serviceAccountDelegationInfo": [ { "firstPartyPrincipal": { "principalEmail": "normal-user@sample-pj.iam.gserviceaccount.com" } } ] }
principalEmailが BigQuery のジョブを実行したプリンシパルで、serviceAccountDelegationInfoが権限借用に関する情報。
normal-userがdbt-runnerの権限を借用しジョブを実行した、ということが読み取れる。
権限設計の選択肢が広がる
扱っているデータの内容にもよるが、 BigQuery のアクセス制御には求められる要件や考慮事項が多い。
データ利活用しやすいように、利活用者が便利に、かつ安全にデータを利用できるようになっていることが望ましい。
近年では AI を使ったデータ利活用のニーズも高まっており、アクセス制御はますます重要になっている。積極的に AI を利用していきたい一方で、データガバナンスやコンプライアンスといった観点における考慮事項が増加している。
今回の例の場合、normal-userはdbtの実行時以外は BigQuery のデータにアクセスできない。
そのため以下のような権限設定をしたプロジェクトを用意して Claude Code を使えば、現在どのようなデータ構造なのか、どのようなテーブルがあるのか、といったことを Claude Code に調査させたり、目的のために今後どのようなテーブルやデータがあるとよさそうかを会話したり、といったことができる。
{ "permissions": { "allow": [ "Read(/**)", "Edit(/**)", "Write(/**)", "Bash(bq ls *)", "Bash(bq show *)" ] } }
Claude Code にメタデータを渡すのは問題ないが実データは渡したくない、といった要件のときなどに使える。
Claude Code に調査をさせつつ、自分はdbtを使った開発を行う、ということが可能になる。
これはあくまでも説明のための例であり、ここまで単純なケースや要件は少ないかもしれない。
しかし、権限借用は便利な仕組みであり、カラムレベルでのアクセス制御など他のアクセス制御の手法と組み合わせることで、権限設計の幅が広がるはずである。