サービス境界とアクセスレベルで、特定のユーザーやサービスアカウントからしか BigQuery にアクセスできないようにしてみた

2020.06.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、みかみです。

IAM ユーザーやサービスアカウントからの BigQuery へのアクセスはロールでも制限できますが、BigQuery や GCS などのリソースにサービス境界( VPC Service Controls )を指定しておけば、都度細かくロール設定せずともサービス境界外のアクセスは IN/OUT ともに制限することができます。

やりたいこと

  • サービス境界を使用して、特定の IAM ユーザーやサービスアカウントからしか BigQuery と GCS にアクセスできないようにしたい
  • Access Context Manager でメンバー属性を指定したアクセスレベルを作成してサービス境界に付与したい

前提

作業用アカウントには、サービス境界およびアクセスレベルの作成権限を付与済みです。

また、gcloud コマンドと BigQuery Python クライアントライブラリが実行できる作業環境を準備済みです。

アクセスレベルを作成

特定の IAM ユーザーやサービスアカウントからのアクセスのみ許可するように、メンバー属性を指定したアクセスレベルを作成します。

2020/06/22 現在、GCP 管理コンソールからメンバー属性を指定したアクセスレベルの作成はできないとのことなので、gcloud コマンドで作成します。

IAM ユーザーとサービスアカウント情報を記載した、以下の YAML ファイルを準備しました。

- members:
  - user:xxxx@classmethod.jp
  - serviceAccount:bq-data-owner@cm-da-mikami-yuki-258308.iam.gserviceaccount.com

※ユーザー情報は一部伏字に変更しています。

上記 YAML を指定して、gcloud コマンドでアクセスレベルを作成します。

(test_bq) [ec2-user@ip-10-0-43-239 ~]$ gcloud access-context-manager levels create cm_mikami_member_access \
>     --basic-level-spec=memberspec.yaml \
>     --combine-function=OR \
>     --description='Access level for test member permission.' \
>     --title='Access_Level for Members'
API [accesscontextmanager.googleapis.com] not enabled on project
[797147019523]. Would you like to enable and retry (this will take a
few minutes)? (y/N)?  y

Enabling service [accesscontextmanager.googleapis.com] on project [797147019523]...
Operation "operations/acf.bfbd3280-6594-415d-8396-bcdb9d1e859c" finished successfully.
Create request issued for: [cm_mikami_member_access]
Waiting for operation [operations/accessPolicies/1079020659323/accessLevels/cm_mikami_member_access/create/1592819762890932] to complete...done.
Created level [cm_mikami_member_access].


To take a quick anonymous survey, run:
  $ gcloud survey

Access Context Manager API が有効になっていない場合は、有効にしてリトライするかどうかの確認が表示されるので、y を入力してリトライしました。

GCP 管理コンソールからも、アクセスレベルが作成されたことが確認できました。

サービス境界を作成

GCP 管理コンソールナビゲーションメニューの「セキュリティ」から「VPC Service Controls」を選択し、「新しい境界」をクリックします。

「境界名」に任意の名前を入力し、「プロジェクトを追加」ボタンからアクセス制御したいプロジェクトを追加。「サービスを追加」ボタンから、「BigQuery API」と「Google Cloud Storage API」を追加し、「上りポリシー: アクセスレベル」で先ほど作成したメンバー指定のアクセスレベルを選択して「境界を作成」ボタンをクリック。

サービス境界が作成できました。

IAM ユーザーで GCP 管理コンソールから BigQuery にアクセス

アクセスレベルで指定した IAM ユーザーで、BigQuery 管理画面からアクセスしてみます。

問題なくデータが参照できました。

続いてアクセスレベルで指定していない IAM ユーザーでアクセスしてみます。

確認する IAM ユーザには、対象のプロジェクトの閲覧者と BigQuery データ編集者のロールを付与済みで、サービス境界作成前には BigQuery のデータを参照できることを確認済みです。

アクセスレベルで指定した通り、許可していない IAM ユーザーでは BigQuery にアクセスできません。

データセットを新規作成しようとしても、エラーとなり作成できません。

GCS を参照しようとしても、同様にアクセスできないことが確認できました。

サービスアカウントで Python クライアントライブラリから BigQuery にアクセス

サービスアカウントからのアクセス制御も確認してみます。

Python クライアントライブラリ経由で BigQuery へデータをロードし、ロードしたデータを参照後にテーブルを削除する、以下の Python コードを準備しました。

from google.cloud import bigquery
from google.oauth2 import service_account
import argparse
import os.path

parser = argparse.ArgumentParser(description='job')
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.data_sample'.format(credentials.project_id)
table = bigquery.Table(table_id)

# load data
job_config = bigquery.LoadJobConfig()
job_config.source_format = bigquery.SourceFormat.CSV
job_config.skip_leading_rows = 1
job_config.autodetect = True
# from url
uri = "gs://test-mikami/data_sample.csv"
job = client.load_table_from_uri(uri, table, job_config=job_config)
print("\tStarting job {}".format(job.job_id))
job.result()
print("table: {} Loaded from uri.".format(table.table_id))

# exec query
query = ('SELECT * FROM `{}` LIMIT 10'.format(table_id))
query_job = client.query(query)
rows = query_job.result()
for row in rows:
    print(row)
print("Selected data")

# delete tables
client.delete_table(table)
print("Deleted table {}.{}.{}".format(table.project, table.dataset_id, table.table_id))

まずはアクセスレベルで許可設定済みのサービスアカウントで実行してみます。

(test_bq) [ec2-user@ip-10-0-43-239 test_role]$ python test_access_level.py keys/bq-data-owner.json
        Starting job 9deeb2a2-4962-454e-a531-3f91cc79821e
table: data_sample Loaded from uri.
Row((1, 'value1'), {'col_1': 0, '_col_2': 1})
Row((2, 'value2'), {'col_1': 0, '_col_2': 1})
Row((3, 'value3'), {'col_1': 0, '_col_2': 1})
Selected data
Deleted table cm-da-mikami-yuki-258308.dataset_1.data_sample

GCS からの データロード、ロードデータの参照、テーブル削除全て問題なく実行できました。

続いて、アクセスレベルでは指定していない、BigQuery 管理者ロールを付与済みのサービスアカウントで実行してみます。

(test_bq) [ec2-user@ip-10-0-43-239 test_role]$ python test_access_level.py keys/bq-admin.json
Traceback (most recent call last):
  File "test_access_level.py", line 29, in <module>
    job = client.load_table_from_uri(uri, table, job_config=job_config)
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/cloud/bigquery/client.py", line 1593, in load_table_from_uri
    load_job._begin(retry=retry, timeout=timeout)
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 640, in _begin
    retry, method="POST", path=path, data=self.to_api_repr(), timeout=timeout
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/cloud/bigquery/client.py", line 556, in _call_api
    return call()
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func
    on_error=on_error,
  File "/home/ec2-user/test_bq/lib/python3.7/site-packages/google/api_core/retry.py", line 184, in retry_target
    return target()
  File "/home/ec2-user/test_bq/lib/python3.7/site-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: VPC Service Controls: Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: 6458ac92e9fb05cb.

VPC Service Controls: Request is prohibited by organization's policy. とのことで、アクセスレベルの指定通り、許可されていないサービスアカウントからのアクセスはできないことが確認できました。

まとめ(所感)

特に個人情報などの重要データを保存する可能性があるプロジェクトの場合、VPC Service Controls と Access Context Manager でサービス境界外からのアクセス制御を指定しておいた方がより安全です。

Access Context Manager で設定するアクセスレベルには、今回確認したメンバー属性の他にも、IP サブネットワークやデバイス ポリシーなどの指定もできるので、要件に合ったアクセス制御を柔軟に実現できそうです。

参考