Data Catalog のポリシータグで BigQuery カラムレベルのアクセス制御が可能になったので試してみた

2020.04.07

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

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

これまで BigQuery のデータのアクセス制御を指定できる最下層のリソースはデータセットで、テーブルやカラム単位でのアクセス制御はできませんでしたが、Data Catalog のポリシータグを付与することで、カラムレベルのアクセス制御が指定できるようになったそうです。

やりたいこと

  • BigQuery のカラムレベルのアクセス制御を行うにはどうすればいいのか知りたい
  • BigQuery のカラムレベルのアクセス制御を設定した場合の挙動を確認したい

前提

2020/04/07 現在、この Data Catalog のポリシータグを使用した BigQuery カラムレベルのアクセス制御機能はベータ版とのことです。

動作確認するにあたって、GCP で操作対象のプロジェクトに対して課金が有効になっていること、Data Catalog のポリシータグを作成する権限があることを前提としています。

また、Python クライアントライブラリを使用した BigQuery へのアクセス環境は準備済みであるものとします。

Data Catalog のポリシータグを作成

GCP 管理コンソールナビゲーションメニューから Data Catalog を選択し、APIを有効にします。

Data Catalog 画面のポリシータグ項目の「ポリシータグを作成、管理する」リンクをクリックし、ポリシータグ一覧画面で「作成」をクリック。新規ポリシータグを作成します。

「分類名」と「説明」に任意の文字列を入力し、「プロジェクト」と「ロケーション」を選択、「ポリシータグ」に任意のタグ名と説明を入力して「保存」します。

「High」と「Medium」2つのポリシータグを作成しました。

ポリシータグ詳細画面で「アクセス制御の適用」を ON に設定します。

さらに、作成したポリシータグに「メンバーを追加」します。

「新しいメンバー」欄に追加するメンバーアカウントを入力し、「ロール」プルダウンで「データカタログ」の「きめ細かい読み取り」を設定します。

「High」のポリシータグにはサービスアカウントAの参照権限を追加し、

「Medium」のポリシータグにはサービスアカウントAとサービスアカウントBの参照権限を追加しました。

BigQuery のテーブル項目にポリシータグを設定

BigQuery 管理画面から、テーブルを選択し、「スキーマを編集」します。

アクセス制御したいカラムにチェックを入れて、「ポリシータグを追加」ボタンをクリック。

ポリシータグ追加画面で付与するポリシーを「選択」。

高レベルセキュリティの「High」と中レベルセキュリティの「Medium」2つのポリシータグを、それぞれ別のカラムに設定しました。

なお、現在のところ、DDL( CREATE TABLE 文)によるテーブル作成時には、ポリシータグは設定できないとのことです。

ポリシータグ設定済みのテーブルデータを参照してみる

現在、以下の状況です。

  • 高レベルセキュリティの「High」と中レベルセキュリティの「Medium」の2つのポリシータグあり
  • テーブル name_1880name カラムには「High」ポリシー設定済み
  • テーブル name_1880gender カラムには「Medium」ポリシー設定済み
  • テーブル name_1880count カラムにはポリシー未設定
  • サービスアカウントAは「High」「Medium」ポリシーどちらのカラムも参照可能
  • サービスアカウントBは「High」ポリシーは参照不可。「Medium」ポリシーは参照可能

アカウントキーファイルを指定して、高レベル、中レベル両方のカラムを SELECT する Python コードを準備しました。

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

parser = argparse.ArgumentParser(description='select data')
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"],
)

query = (
    'SELECT name, gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` '
    'ORDER BY count DESC LIMIT 3'
)

client = bigquery.Client(
    credentials=credentials,
    project=credentials.project_id,
)
query_job = client.query(query)
rows = query_job.result()
for row in rows:
    print('{} : {} ({})'.format(row.name, row.gender, row.count))

高レベル、中レベルどちらのカラムも参照できるサービスアカウントAのアカウントキーで実行してみると

(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_1.py key_account_a.json
John : M (9655)
William : M (9532)
Mary : F (7065)

両方のカラム値を正常に取得できます。

高レベルのカラムは参照できないサービスアカウントBのアカウントキーに変更して実行してみると

(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_1.py key_account_b.json
Traceback (most recent call last):
  File "test_select_level_1.py", line 26, in <module>
    rows = query_job.result()
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3196, in result
    super(QueryJob, self).result(retry=retry, timeout=timeout)
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 818, in result
    return super(_AsyncJob, self).result(timeout=timeout)
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 122, in result
    self._blocking_poll(timeout=timeout)
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3098, in _blocking_poll
    super(QueryJob, self)._blocking_poll(timeout=timeout)
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 101, in _blocking_poll
    retry_(self._done_or_raise)()
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func
    on_error=on_error,
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 184, in retry_target
    return target()
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/future/polling.py", line 80, in _done_or_raise
    if not self.done():
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/job.py", line 3085, in done
    timeout=timeout,
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/client.py", line 1287, in _get_query_results
    retry, method="GET", path=path, query_params=extra_params, timeout=timeout
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/cloud/bigquery/client.py", line 556, in _call_api
    return call()
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func
    on_error=on_error,
  File "/home/ec2-user/test_account/lib64/python3.7/site-packages/google/api_core/retry.py", line 184, in retry_target
    return target()
  File "/home/ec2-user/test_account/lib64/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 GET https://bigquery.googleapis.com/bigquery/v2/projects/cm-da-mikami-yuki-258308/queries/01610338-5680-4f09-8564-36c26823ea7a?maxResults=0&location=US: Access Denied: BigQuery BigQuery: User does not have permission to access policy tag "分類テスト : High" on column cm-da-mikami-yuki-258308.airflow_test.name_1880.name.

(job ID: 01610338-5680-4f09-8564-36c26823ea7a)

                                         -----Query Job SQL Follows-----

    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |
   1:SELECT name, gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` ORDER BY count DESC LIMIT 3
    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |    .    |

設定どおり、参照を許可していないサービスアカウントBでは、高レベルポリシーを指定したカラム値が permission エラーで取得できません。

続いて、中レベルのカラムを SELECT する 以下の Python コードを、アカウントA、Bそれぞれのアカウントキーを指定して実行してみます。

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

parser = argparse.ArgumentParser(description='select data')
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"],
)

query = (
    'SELECT gender, count FROM `cm-da-mikami-yuki-258308.airflow_test.name_1880` '
    'ORDER BY count DESC LIMIT 3'
)

client = bigquery.Client(
    credentials=credentials,
    project=credentials.project_id,
)
query_job = client.query(query)
rows = query_job.result()
for row in rows:
    print('{} ({})'.format(row.gender, row.count))
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_2.py key_account_a.json
M (9655)
M (9532)
F (7065)
(test_account) [ec2-user@ip-10-0-43-239 test_account]$ python test_select_level_2.py key_account_b.json
M (9655)
M (9532)
F (7065)

設定したポリシーの通り、A、Bどちらのアカウントでもカラムデータにアクセスできることが確認できました。

制限事項

現在のところ、東京リージョンの BigQuery データに対しては、Data Catalog のポリシータグによるカラムレベルのアクセス制御は指定できないようです。

管理コンソールからの Data Catalog のポリシータグ作成時、ロケーションには 「US」と「EU」のどちらかしか選択できません。

また、BigQuery テーブルのスキーマ編集画面でも、US リージョンのテーブルの場合はカラム選択のチェックボックスと「ポリシータグを追加」ボタンが表示されますが、

東京リージョンの場合、「ポリシータグを追加」ボタンが表示されません。

ポリシータグはチェックボックスや追加ボタンを使った UI 操作での付与の他に、テキストモードでのスキーマ指定でも付与可能とのことなので、東京リージョンであらかじめポリシータグを付与したテーブルを新規作成しようとしてみましたが

ポリシータグとデータセットが同一リージョンではないため、エラーになり、テーブル作成できませんでした。

まだベータ版とのことなので、東京リージョンのサポートを楽しみに待つことにいたします!

まとめ(所感)

管理コンソールからの UI 操作だけで、簡単に BigQuery データへのアクセス制御を設定することができました。今のところまだテーブルレベルでのアクセス制御機能はないようですが、このカラムレベルのポリシー指定により、テーブル単位のアクセス制御も柔軟に対応できますね。

他のデータベースサービスと比べると、DB 接続ユーザーがなかったり GRANT 構文によるアクセス権限設定機能がなかったり、初めはちょっと戸惑いましたが、アカウントの切り替えや、ロール、ポリシーの設定で同等のアクセス制御は実現できることが分かりました。

GCP を勉強し始めてからまだ日は浅いですが、サービスのアップデートのスピードも速く、BigQuery もどんどん使いやすくなっているように思います。 今後のさらなるアップデートにも期待です!

参考