この記事では BigQuery の row-level permission を利用した、ユーザ単位で閲覧できる行を制限する方法について解説します。
単純にユーザによらない行単位で閲覧制限をする方法については、以下の記事を参照してください。
BigQuery で row-level permission を利用する方法(その1)
元記事
How do I give different users access to different rows without creating separate views in BigQuery?
CURRENT_USER 関数
ユーザ単位でのアクセス制限は全て CURRENT_USER 関数の機能に依存しています。CURRENT_USER関数は現在クエリを実行しているユーザの email アドレスを返す関数です。試しに以下のクエリを Web UI で実行してみてください。あなたの email アドレスが表示されるはずです。
SELECT CURRENT_USER()
CURRENT_USER 関数を利用した Authorized View
下表のデータが格納された customers テーブルに対して、
| customer:string | id:integer | allowed_viewer:string |
|---|---|---|
| c001 | 1 | me@example.com |
| c002 | 2 | you@example.com |
| c003 | 3 | me@example.com |
次のクエリで Authorized View を作成します。
SELECT
customer,
id
FROM
[private.customers]
WHERE
allowed_viewer = CURRENT_USER()
こうすると、me@example.com というユーザに対しては、
| customer:string | id:integer |
|---|---|
| c001 | 1 |
| c003 | 3 |
という結果が、you@example.com というユーザに対しては、
| customer:string | id:integer |
|---|---|
| c002 | 2 |
というユーザごとに異なった結果が返りようになります。これがユーザ単位でのアクセス制限の基本形です。
グループ単位で閲覧制御をする
上記の方法では、1つの行につき、1つのユーザに対してしか閲覧を許可することができません。しかし、実運用上では複数ユーザやグループ単位でアクセス制御をしたいことが多々あります。これに対する解決策として最も単純なものは「allowed_viewer 列を REPEATED フィールドとする」ことですが、この方法ではデータの更新ができない BigQuery で後からユーザを追加したいというケースに対応できません。
より実用的な方法としては、新しくグループ情報を格納する access_control テーブルを作成し、このテーブルと JOIN することでより柔軟なアクセス制御を実現します。
例
アクセス制御したい private データセットのテーブル customers に対して、allowed_group 列を追加します。以降の説明では、下表のデータが入っているとします。
| customer:string | id:integer | allowed_group:string |
|---|---|---|
| c001 | 1 | admin |
| c002 | 2 | sales |
| c003 | 3 | engineers |
次にグループ情報を管理する access_control テーブルを同じ private データセット内に作成します。データは以下のものとが入っているとします。
| group:string | user_name:string |
|---|---|
| admin | admin@example.com |
| sales | sales@example.com |
| engineers | admin@example.com |
| engineers | engineer@example.com |
これら2つのテーブルを利用して、公開する public データセットに、以下のクエリを持つ public_customers という名前の Authorized View を作成します。
SELECT
c.customer AS customer,
c.id AS id
FROM
[private.customers] c
INNER JOIN (
SELECT
group
FROM
[private.access_control]
WHERE
CURRENT_USER() = user_name
) g
ON
c.allowed_group = g.group
これで、admin@example.com で SELECT * FROM [public.public_customers] というクエリを実行すると、
| customer:string | id:integer |
|---|---|
| c001 | 1 |
| c003 | 3 |
が、sales@example.com だと
| customer:string | id:integer |
|---|---|
| c002 | 2 |
が、engineer@example.com だと
| customer:string | id:integer |
|---|---|
| c003 | 3 |
という結果が返るようになります。これで、グループ単位でのアクセス制御が可能になります。
補足
グループが他のグループを含められるようにする
元記事では文章で簡単に触れられているだけの、グループが他のグループを含められるようにする方法について、自分なりに考えてみました。
| group:string | user_name:string |
|---|---|
| admin | admin@example.com |
| sales | sales@example.com |
| engineers | admin |
| engineers | engineer@example.com |
という enginners グループに admin グループが含まれるデータに対して、以下の様なビューを作ります。access_control テーブルを self join しているところがポイントです。
SELECT
c.customer AS customer,
c.id AS id
FROM
[private.customers] c
INNER JOIN (
SELECT
group
FROM (
SELECT
group
FROM
[private.access_control]
WHERE
CURRENT_USER() = user_name
), (
SELECT
r.group AS group
FROM
[private.access_control] l
LEFT OUTER JOIN EACH
[private.access_control] r
ON
l.group = r.user_name
WHERE
CURRENT_USER() = l.user_name
)
) g
ON
c.allowed_group = g.group
もう少し綺麗/効率よく書けるよってツッコミ歓迎です。
access_control テーブルの更新について
元記事の方には書いてないですが、BigQuery は行の UPDATE/DELETE ができないので、access_control テーブルのデータの更新には、テーブルの再作成が必要になります。ですので、元データは別途何らかのファイルか、別のデータベースに入れておくようにしましょう。