BigQuery でテーブルレベルのアクセス制御(テーブル ACL )ができるようになったので挙動を確認してみた
こんにちは、みかみです。
これまで BigQuery でアクセス制御できる最下層のリソースはデータセットでした。
先日、テーブル単位でアクセス権を設定することができるようになったので、挙動を確認してみました。
やりたいこと
- テーブルレベルでアクセス制御(テーブル ACL )を設定するにはどうすればいいのか知りたい
- テーブル ACL を設定した場合の挙動を確認したい
- テーブルへのアクセス制御設定とデータセットなど他のリソースへのアクセス制御設定は競合しないのか確認したい
- テーブル ACL は継承されることがあるのかどうか確認したい
前提
BigQuery のテーブルレベルのアクセス制御機能は、現在ベータ版とのことです。
動作検証では Python クライアントライブラリ経由( BigQuery API )で、サービスアカウントを使用してアクセス制御状態を確認しました。
bq
コマンド、Python クライアントライブラリは、CLOUD SHELL から実行しました。
- Python Client for Google BigQuery
- コマンドライン ツール リファレンス | BigQuery ドキュメント
- Cloud Shell の使用 | Cloud Shell ドキュメント
テーブルにアクセスポリシーを設定
以下の通り、検証用のサービスアカウントを作成しました。
BigQuery の管理コンソールからアクセス制御を設定したいテーブルを選択し、「テーブルを共有」ボタンをクリックします。
権限設定画面で「メンバーを追加」欄にアクセス制御を追加したいサービスアカウントを入力し、プルダウンから付与するロールを選択して「追加」をクリック。
table_sample
テーブルに対して、2 つのサービスアカウントに BigQuery データオーナーと BigQuery データ閲覧者のロールを付与しました。
データセット / テーブルへのアクセス権を確認
以下の 4 つのサービスアカウントで、データセットやテーブルにアクセスできるかどうか確認してみます。
サービスアカウント | ロール(to Dataset) | ロール(to Table) |
---|---|---|
bq-data-owner | BigQuery データオーナー | BigQuery データオーナー(継承) |
table-acl-data-owner | なし | BigQuery データオーナー |
table-acl-data-viewer | なし | BigQuery データ閲覧者 |
table-acl-test | なし | なし |
動作検証に使用するサービスアカウントキーを Cloud Shell にアップロードします。 Cloud Shell へのファイルアップロード / ダウンロードは、メニューから「アップロード」/「ダウンロード」を選択することで GUI 経由で簡単に実行できます。
データセット一覧を取得
まずは各サービスアカウントで、データセットにアクセスできるかどうか確認してみます。
table-acl-*
で始まるサービスアカウントには、データセットへのアクセス権は付与していません。
パラメータでサービスアカウントキーファイルを指定して、BigQuery のデータセット情報を取得する以下の Python コードを準備しました。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='project') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) datasets = list(client.list_datasets()) project = client.project if datasets: print("Datasets in project {}:".format(project)) for dataset in datasets: print("\t{}".format(dataset.dataset_id)) else: print("{} project does not contain any datasets.".format(project))
まずはデータセットへのアクセス権があるサービスアカウントを指定して実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 list_dataset.py keys/bq-data-owner.json Datasets in project cm-da-mikami-yuki-258308: (省略) dataset_1 dataset_2 load_from_gcs (省略)
データセットアクセスできることが確認できました。
続いてデータセットへのアクセス権を付与していないサービスアカウントで実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 list_dataset.py keys/table-acl-data-owner.json cm-da-mikami-yuki-258308 project does not contain any datasets.
指定したロールの通り、データセットにはアクセスできないことが確認できました。
テーブル一覧を取得
以下の Python コードで、テーブル一覧を取得してみます。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='project') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) dataset_id = '{}.dataset_1'.format(credentials.project_id) tables = client.list_tables(dataset_id) print("Tables contained in '{}':".format(dataset_id)) for table in tables: print("{}.{}.{}".format(table.project, table.dataset_id, table.table_id))
データセットおよび配下の全てのテーブルへのアクセス権があるサービスアカウントで実行すると
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 list_table.py keys/bq-data-owner.json Tables contained in 'cm-da-mikami-yuki-258308.dataset_1': cm-da-mikami-yuki-258308.dataset_1.animals (省略) cm-da-mikami-yuki-258308.dataset_1.table_sample cm-da-mikami-yuki-258308.dataset_1.table_sample_encrypt cm-da-mikami-yuki-258308.dataset_1.table_sample_encrypt_copy cm-da-mikami-yuki-258308.dataset_1.table_sample_partition cm-da-mikami-yuki-258308.dataset_1.table_sample_partition_copy cm-da-mikami-yuki-258308.dataset_1.test_create cm-da-mikami-yuki-258308.dataset_1.view_dogs cm-da-mikami-yuki-258308.dataset_1.view_dogs_2 cm-da-mikami-yuki-258308.dataset_1.view_sample
データセット配下のテーブル一覧を取得できます。
続いて、データセット配下の特定のテーブル( table_sample
)にのみ BigQuery データオーナーのロールを付与したサービスアカウントで実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 list_table.py keys/table-acl-data-owner.json Tables contained in 'cm-da-mikami-yuki-258308.dataset_1': Traceback (most recent call last): File "list_table.py", line 23, in <module> for table in tables: File "/usr/local/lib/python3.7/dist-packages/google/api_core/page_iterator.py", line 212, in _items_iter for page in self._page_iter(increment=False): File "/usr/local/lib/python3.7/dist-packages/google/api_core/page_iterator.py", line 243, in _page_iter page = self._next_page() File "/usr/local/lib/python3.7/dist-packages/google/api_core/page_iterator.py", line 369, in _next_page response = self._get_next_page_response() File "/usr/local/lib/python3.7/dist-packages/google/api_core/page_iterator.py", line 419, in _get_next_page_response method=self._HTTP_METHOD, path=self.path, query_params=params File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 574, in _call_api return call() File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/usr/local/lib/python3.7/dist-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 GET https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables: Access Denied: Dataset cm-da-mikami-yuki-258308:dataset_1: User does not have bigquery.tables.list permission for dataset cm-da-mikami-yuki-258308:dataset_1.
User does not have bigquery.tables.list permission
とのことで、テーブルリストは取得できませんでした。
BigQuery の事前定義ロール「BigQuey データオーナー」には bigquery.tables.list
権限も付与されているはずですが、特定のテーブルのみへのロール付与の場合は対象のテーブル含めて一覧の取得はできませんでした。
テーブル情報を取得
以下の Python コードで、テーブル情報を取得してみます。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='project') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) table_id = '{}.dataset_1.table_sample'.format(credentials.project_id) table = client.get_table(table_id) print( "Got table '{}.{}.{}'.".format(table.project, table.dataset_id, table.table_id) ) print("Table schema: {}".format(table.schema)) print("Table description: {}".format(table.description)) print("Table has {} rows".format(table.num_rows))
まずはデータセットおよびテーブルに BigQuery データオーナーのロールが付与されているサービスアカウントで実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 get_table.py keys/bq-data-owner.json Got table 'cm-da-mikami-yuki-258308.dataset_1.table_sample'. Table schema: [SchemaField('name', 'STRING', 'REQUIRED', '名前', (), None), SchemaField('gender', 'STRING', 'NULLABLE', '性別', (), None), SchemaField('count', 'INTEGER', 'NULLABLE', '人数', (), None)] Table description: テーブルコピーのテスト用 Table has 34073 rows
テーブル情報を取得することができました。
続いて、テーブルに対してのみロールを付与したサービスアカウントで実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 get_table.py keys/table-acl-data-owner.json Got table 'cm-da-mikami-yuki-258308.dataset_1.table_sample'. Table schema: [SchemaField('name', 'STRING', 'REQUIRED', '名前', (), None), SchemaField('gender', 'STRING', 'NULLABLE', '性別', (), None), SchemaField('count', 'INTEGER', 'NULLABLE', '人数', (), None)] Table description: テーブルコピーのテスト用 Table has 34073 rows gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 get_table.py keys/table-acl-data-viewer.json Got table 'cm-da-mikami-yuki-258308.dataset_1.table_sample'. Table schema: [SchemaField('name', 'STRING', 'REQUIRED', '名前', (), None), SchemaField('gender', 'STRING', 'NULLABLE', '性別', (), None), SchemaField('count', 'INTEGER', 'NULLABLE', '人数', (), None)] Table description: テーブルコピーのテスト用 Table has 34073 rows
BigQuery データオーナー、BigQuery データ閲覧者どちらのロールでも、アクセス許可したテーブルであれば、テーブル情報が参照できることが確認できました。
なお、アクセス権を設定していない別のテーブル情報を取得しようとしてみると
(省略) #table_id = '{}.dataset_1.table_sample'.format(credentials.project_id) table_id = '{}.dataset_1.table_sample_partition'.format(credentials.project_id) table = client.get_table(table_id) (省略)
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 get_table.py keys/table-acl-data-owner.json Traceback (most recent call last): File "get_table.py", line 21, in <module> table = client.get_table(table_id) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 697, in get_table retry, method="GET", path=table_ref.path, timeout=timeout File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 574, in _call_api return call() File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/usr/local/lib/python3.7/dist-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 GET https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample_partition: Access Denied: Table cm-da-mikami-yuki-258308:dataset_1.table_sample_partition: User does not have bigquery.tables.get permission for table cm-da-mikami-yuki-258308:dataset_1.table_sample_partition.
アクセス制御設定の通り、アクセスを許可していないテーブル情報は取得できません。
さらに、データセットにもテーブルにもアクセス権を設定していないサービスアカウントで実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 get_table.py keys/table-acl-test.json Traceback (most recent call last): File "get_table.py", line 20, in <module> table = client.get_table(table_id) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 697, in get_table retry, method="GET", path=table_ref.path, timeout=timeout File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 574, in _call_api return call() File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/usr/local/lib/python3.7/dist-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 GET https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/datasets/dataset_1/tables/table_sample: Access Denied: Table cm-da-mikami-yuki-258308:dataset_1.table_sample: User does not have bigquery.tables.get permission for table cm-da-mikami-yuki-258308:dataset_1.table_sample.
こちらも設定の通り、許可していないサービスアカウントからはアクセスできないことが確認できました。
テーブルデータへのアクセス権を確認
アクセス制御を設定したテーブルに対して、SQL を実行してデータアクセス時の挙動を確認してみます。
Python クライアントライブラリ経由で SQL クエリを実行する場合はジョブ実行となりますが、検証対象の BigQuery データオーナー、BigQuery データ閲覧者ロールにはどちらも bigquery.jobs.create
権限がないため、ジョブを実行できません。
GCP 管理コンソールナビゲーションメニュー「IAM と管理」から「IAM」を選択し、検証するサービスアカウントに「BigQuery ジョブユーザー」ロールを付与して IAM を追加しました。
テーブルデータの操作
以下のサービスアカウントで、テーブルデータの参照とレコード追加・更新・削除処理の可否を確認してみます。
サービスアカウント | ロール | 参照 | 追加 / 更新 / 削除 |
---|---|---|---|
table-acl-data-owner | BigQuery データオーナー | 可 | 可 |
table-acl-data-viewer | BigQuery データ閲覧者 | 可 | 不可 |
まずは参照、追加、更新、削除全ての操作が可能な BigQuery データオーナーロールを付与したサービスアカウントで、以下の Python コードを実行してみます。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='project') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) query = 'SELECT name FROM dataset_1.table_sample ORDER BY count LIMIT 3' query_job = client.query(query) results = query_job.result() for row in results: print("name: {}".format(row.name)) query = 'INSERT INTO dataset_1.table_sample VALUES("Yuki", "F", 1)' query_job = client.query(query) results = query_job.result() print("insert comp.") query = 'UPDATE dataset_1.table_sample SET count = 2 WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() print("update comp.") query = 'SELECT name, count FROM dataset_1.table_sample WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() for row in results: print("name: {}, count: {}".format(row.name, row.count)) query = 'DELETE FROM dataset_1.table_sample WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() print("delete comp.")
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query.py keys/table-acl-data-owner.json name: Kisa name: Faylynn name: Quynh insert comp. update comp. name: Yuki, count: 2 delete comp.
全ての操作が問題なく実行できることが確認できました。
続いて参照権しかない BigQuery データ閲覧者ロールのサービスアカウントを指定して実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query.py keys/table-acl-data-viewer.json name: Kisa name: Faylynn name: Quynh Traceback (most recent call last): File "query.py", line 27, in <module> results = query_job.result() File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 3207, in result super(QueryJob, self).result(retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 812, in result return super(_AsyncJob, self).result(timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/api_core/future/polling.py", line 130, in result raise self._exception google.api_core.exceptions.Forbidden: 403 Access Denied: Table cm-da-mikami-yuki-258308:dataset_1.table_sample: User does not have bigquery.tables.updateData permission for table cm-da-mikami-yuki-258308:dataset_1.table_sample. (job ID: 88da6965-414d-4737-a7c0-5fe34210b69d) -----Query Job SQL Follows----- | . | . | . | . | . | 1:INSERT INTO dataset_1.table_sample VALUES("Yuki", "F", 1) | . | . | . | . | . | gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$
設定したロールの通り、参照のみ可能なことが確認できました。
テーブルに対して BigQuery ジョブ実行権限は付与できる?
BigQuery ジョブユーザーロールが付与されていない状態で Python クライアントライブラリから SQL を実行しようとすると、以下の通り bigquery.jobs.create
権限エラーが発生します。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query.py keys/table-acl-data-owner.json Traceback (most recent call last): File "query.py", line 20, in <module> query_job = client.query(query) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 2471, in query query_job._begin(retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 3156, in _begin super(QueryJob, self)._begin(client=client, retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 638, in _begin retry, method="POST", path=path, data=self.to_api_repr(), timeout=timeout File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 574, in _call_api return call() File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/usr/local/lib/python3.7/dist-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 POST https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/jobs: Access Denied: Project cm-da-mikami-yuki-258308: User does not have bigquery.jobs.create permission in project cm-da-mikami-yuki-258308. (job ID: 44b3d65c-0cfd-4376-a947-2b941b4c0751) -----Query Job SQL Follows----- | . | . | . | . | . | . | 1:SELECT name FROM dataset_1.table_sample ORDER BY count LIMIT 3 | . | . | . | . | . | . |
BigQuery 管理コンソールから、テーブルに対して BigQuery ジョブジョブユーザーのロールを付与しようとしても、プルダウンにジョブユーザーのロールが表示されていないため選択できません。
テーブルに対するアクセス権設定は、bq
コマンドでも実行できます。
現在のポリシーを取得します。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq get-iam-policy --format=prettyjson \ > cm-da-mikami-yuki-258308:dataset_1.table_sample \ > > policy.json
以下のポリシーファイルが取得できました。
{ "bindings": [ { "members": [ "serviceAccount:table-acl-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataOwner" }, { "members": [ "serviceAccount:table-acl-data-viewer@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataViewer" } ], "etag": "BwWr9k9uSBw=", "version": 1 }
上記ファイルに roles/bigquery.jobUser
ロールを追記しました。
{ "bindings": [ { "members": [ "serviceAccount:table-acl-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataOwner" }, { "members": [ "serviceAccount:table-acl-data-viewer@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataViewer" }, { "members": [ "serviceAccount:table-acl-data-viewer@cm-da-mikami-yuki-258308.iam.gserviceaccount.com", "serviceAccount:table-acl-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.jobUser" } ], "etag": "BwWr9k9uSBw=", "version": 1 }
ポリシーを更新しようとすると
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq set-iam-policy \ > cm-da-mikami-yuki-258308:dataset_1.table_sample \ > policy.json BigQuery error in set-iam-policy operation: IAM setPolicy failed for Table cm-da-mikami-yuki-258308:dataset_1.table_sample: Role roles/bigquery.jobUser is not supported for this resource.
Role roles/bigquery.jobUser is not supported for this resource.
とのことで、テーブルに対して BigQuery ジョブユーザーロールは付与できませんでした。
BigQuery の事前定義済みロールがどのリソースに対して設定できるかは、以下のドキュメント「最下位のリソース」欄で確認できます。
BigQuery ジョブユーザーロール( roles/bigquery.jobUser
)が付与できる最下位のリソースは「プロジェクト」とのことなので、データセットやテーブルには付与できません。
一方、データセットやテーブルには、カスタムロールを設定することができます。
- IAM のカスタムロールについて | Cloud IAM ドキュメント
- IAM カスタムロールを使用して BigQuery データ ウェアハウスへのアクセスを管理する| Google Cloud ブログ
では、ジョブ実行可能な bigquery.jobs.create
権限を持つカスタムロールを作成し、そのカスタムロールをテーブル ACL で設定すればジョブ実行できるのでしょうか?
BigQuery データ閲覧者ロールの権限 + bigquery.jobs.create
権限を付与したカスタムロールを作成し、テーブルの権限設定画面からカスタムロールでサービスアカウントを追加しました。
カスタムロールで追加したサービスアカウントを指定して、以下の SELECT
SQL を実行してみます。
from google.cloud import bigquery from google.oauth2 import service_account import argparse import os.path parser = argparse.ArgumentParser(description='project') parser.add_argument('file', help='account key file') args = parser.parse_args() key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.file) credentials = service_account.Credentials.from_service_account_file( key_path, scopes=["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client( credentials=credentials, project=credentials.project_id, ) query = 'SELECT name FROM dataset_1.table_sample ORDER BY count LIMIT 3' query_job = client.query(query) results = query_job.result() for row in results: print("name: {}".format(row.name))
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query_select.py keys/table-acl-custom-viewer.json Traceback (most recent call last): File "query_select.py", line 20, in <module> query_job = client.query(query) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 2471, in query query_job._begin(retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 3156, in _begin super(QueryJob, self)._begin(client=client, retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 638, in _begin retry, method="POST", path=path, data=self.to_api_repr(), timeout=timeout File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/client.py", line 574, in _call_api return call() File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 286, in retry_wrapped_func on_error=on_error, File "/usr/local/lib/python3.7/dist-packages/google/api_core/retry.py", line 184, in retry_target return target() File "/usr/local/lib/python3.7/dist-packages/google/cloud/_http.py", line 423, in api_request raise exceptions.from_http_response(response) google.api_core.exceptions.Forbidden: 403 POST https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/jobs: Access Denied: Project cm-da-mikami-yuki-258308: User does not have bigquery.jobs.create permission in project cm-da-mikami-yuki-258308. (job ID: d119cdfd-6289-4083-a81b-6b250c1e4e19) -----Query Job SQL Follows----- | . | . | . | . | . | . | 1:SELECT name FROM dataset_1.table_sample ORDER BY count LIMIT 3 | . | . | . | . | . | . |
ジョブはプロジェクトに関連付けられるため、テーブルに対してのみ権限を付与した状態では、やはり実行できませんでした。
データセットとテーブルのアクセス制御はどちらが優先?
BigQuery では、データセットに対してもアクセス権を設定できます。
では、データセットとテーブル両方に対して、異なる権限を付与した場合、どちらのアクセス制御が優先されるのでしょうか?
データセットに BigQuery データ閲覧者 ロール、テーブルに BigQuery データオーナーロールを付与したサービスアカウントで、テーブルデータを編集できるかどうか確認してみます。
(省略) query = 'SELECT name FROM dataset_1.table_sample ORDER BY count LIMIT 3' query_job = client.query(query) results = query_job.result() for row in results: print("name: {}".format(row.name)) query = 'INSERT INTO dataset_1.table_sample VALUES("Yuki", "F", 1)' query_job = client.query(query) results = query_job.result() print("insert comp.") query = 'UPDATE dataset_1.table_sample SET count = 2 WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() print("update comp.") query = 'SELECT name, count FROM dataset_1.table_sample WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() for row in results: print("name: {}, count: {}".format(row.name, row.count)) query = 'DELETE FROM dataset_1.table_sample WHERE name = "Yuki"' query_job = client.query(query) results = query_job.result() print("delete comp.")
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query.py keys/table-acl-data-owner.json name: Kisa name: Faylynn name: Quynh insert comp. update comp. name: Yuki, count: 2 delete comp.
SELECT
、INSERT
、UPDATE
、DELETE
いずれの SQL も、問題なく実行できました。
逆に、データセットに BigQuery データオーナーロール、テーブルに BigQuery データ閲覧者ロールを付与したサービスアカウントで確認してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query.py keys/table-acl-data-viewer.json name: Kisa name: Faylynn name: Quynh insert comp. update comp. name: Yuki, count: 2 delete comp.
こちらも問題なく全ての SQL が実行できることが確認できました。
Cloud IAM のアクセス モデルでは、権限は追加型です。リソースの権限は、ポリシー階層で説明されているように、親リソースから継承されます。リソースに追加された権限によって、追加のアクセス権が付与されます。テーブル ACL では追加のアクセス権の付与ができるだけで、データセットや Google Cloud プロジェクトのアクセス権は削除できません。
「権限は追記型」とのことで、プロジェクト、データセット、テーブルなど複数のリソースに対してアクセス制御設定を行っている場合には、一番強い権限が適用されるようです。
テーブルをコピーしたらアクセスポリシーもコピーされる?
アクセス制御設定済みのテーブルをコピーした場合、コピー先のテーブルにもテーブル ACL が継承されるかどうか確認してみます。
コピー元テーブルのアクセスポリシーは以下の通りです。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq get-iam-policy --format=prettyjson \ > cm-da-mikami-yuki-258308:dataset_1.table_sample { "bindings": [ { "members": [ "serviceAccount:table-acl-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataOwner" }, { "members": [ "serviceAccount:table-acl-data-viewer@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataViewer" } ], "etag": "BwWsCNv+ccY=", "version": 1 }
テーブルをコピーして、コピー先テーブルのアクセスポリシーを確認してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq cp dataset_1.table_sample dataset_1.table_sample_copy Waiting on bqjob_r22259c341760c060_00000173b88be49b_1 ... (0s) Current status: DONE Table 'cm-da-mikami-yuki-258308:dataset_1.table_sample' successfully copied to 'cm-da-mikami-yuki-258308:dataset_1.table_sample_copy' gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq get-iam-policy --format=prettyjson \ > cm-da-mikami-yuki-258308:dataset_1.table_sample_copy { "etag": "ACAB" }
ポリシーはコピーされていません。
念のため、コピー先テーブルに対して SQL を実行してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query_select.py keys/table-acl-data-owner.json Traceback (most recent call last): File "query_select.py", line 22, in <module> results = query_job.result() File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 3207, in result super(QueryJob, self).result(retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 812, in result return super(_AsyncJob, self).result(timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/api_core/future/polling.py", line 130, in result raise self._exception google.api_core.exceptions.Forbidden: 403 Access Denied: Table cm-da-mikami-yuki-258308:dataset_1.table_sample_copy: User does not have permission to query table cm-da-mikami-yuki-258308:dataset_1.table_sample_copy. (job ID: 35b50984-7b08-48ef-9b07-fa4a98ca6c2f) -----Query Job SQL Follows----- | . | . | . | . | . | . | 1:SELECT name FROM dataset_1.table_sample_copy ORDER BY count LIMIT 3 | . | . | . | . | . | . | gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$
コピー先のテーブルに対するアクセス権は設定されていないため、データも参照できません。
テーブルコピーではアクセス権は継承されないため、テーブル ACL を設定している場合にテーブル名変更などでテーブルコピーを実行する時には、アクセス権も再設定する必要があります。
アクセス可能なテーブルをソースとするビューにはアクセスできる?
以下の SQL で、table_sample
をソーステーブルとするビューを作成しました。
CREATE VIEW cm-da-mikami-yuki-258308.dataset_1.v_sample AS SELECT name, count FROM dataset_1.table_sample WHERE gender = 'M';
table_sample
のアクセスポリシーは以下の通りです。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ bq get-iam-policy --format=prettyjson \ > cm-da-mikami-yuki-258308:dataset_1.table_sample { "bindings": [ { "members": [ "serviceAccount:table-acl-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataOwner" }, { "members": [ "serviceAccount:table-acl-data-viewer@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" ], "role": "roles/bigquery.dataViewer" } ], "etag": "BwWsCNv+ccY=", "version": 1 }
table_sample
に BigQuery データ閲覧者ロールを設定済みの table-acl-data-viewer
のサービスアカウントで、新しく作成したビューにもアクセスできるか確認してみます。
gcp_da_user@cloudshell:~ (cm-da-mikami-yuki-258308)$ python3 query_select.py keys/table-acl-data-viewer.json Traceback (most recent call last): File "query_select.py", line 23, in <module> results = query_job.result() File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 3207, in result super(QueryJob, self).result(retry=retry, timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/cloud/bigquery/job.py", line 812, in result return super(_AsyncJob, self).result(timeout=timeout) File "/usr/local/lib/python3.7/dist-packages/google/api_core/future/polling.py", line 130, in result raise self._exception google.api_core.exceptions.Forbidden: 403 Access Denied: Table cm-da-mikami-yuki-258308:dataset_1.v_sample: User does not have permission to query table cm-da-mikami-yuki-258308:dataset_1.v_sample. (job ID: 685e3508-792f-401c-9473-396a5c231b58) -----Query Job SQL Follows----- | . | . | . | . | . | 1:SELECT name FROM dataset_1.v_sample ORDER BY count LIMIT 3 | . | . | . | . | . |
テーブルのアクセスポリシーの適用範囲は対象テーブルに限定され、ビューには適用されないことが確認できました。
承認済みビュー機能からも分かるように、ユーザー(サービスアカウント)の権限と、ソーステーブルからデータを SELECT
するビューの権限は別管理となります。
テーブル ACL でアクセス設定済みのユーザーにビューのアクセス権も付与したい場合は、ビューに対にてもテーブル同様アクセスポリシーを設定するか、承認済みビューとして別のアクセス可能なデータセットにビューを作成する必要があります。
まとめ(所感)
テーブルレベルのアクセス制御により、BigQuery データに対してこれまでよりも細やかなアクセスコントロールが可能になりました。
テーブルコピーやビューの作成ではソーステーブルのアクセスポリシーは継承されないことが確認できたので、意図していないユーザーにデータが公開されるような問題も発生しないはずです。
現在まだベータ版ですが、テーブルレベルのアクセス制御がサポートされたことにより、特に他データベースサービスからの移行時などにはより使いやすくなるのではないでしょうか?
参考
- BigQuery がテーブルレベルのアクセス制御に対応 | Google Cloud ブログ
- テーブルへのアクセス制御の概要 | BigQuery ドキュメント
- BigQuery テーブル ACL のよくある質問| BigQuery ドキュメント
- データセットへのアクセスの制御| BigQuery ドキュメント
- 事前定義ロールと権限| BigQuery ドキュメント
- クイックスタート: クライアント ライブラリの使用 | BigQuery ドキュメント
- コマンドライン ツール リファレンス | BigQuery ドキュメント
- IAM のカスタムロールについて | Cloud IAM ドキュメント
- IAM カスタムロールを使用して BigQuery データ ウェアハウスへのアクセスを管理する| Google Cloud ブログ
- Python Client for Google BigQuery