Databricksにおける機密情報の保護 - PIIデータに対する暗号化、シークレット、ユーザーコンテキストフィルタの活用

[Securing Sensitive Data \- Using Encryption, Secrets, and User Context Filter on PII Data](https://community.databricks.com/t5/technical-blog/securing-sensitive-data-using-encryption-secrets-and-user/ba-p/114761)の翻訳です。

:::note warn
本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

# プロブレムステートメント

いくつかの文脈設定からスタートさせてください。ここで解決するプロブレムステートメントは、このソリューションが他の類似した状況に適用できるように若干汎用的なものとなっています。

以下の要件で、PIIを伴う給与データソースからDatabricksへのデータ取り込みを考えてみます:

- あるテーブルには、機密でマネージャ(適切なアクセス権を持つメンバー)によってのみ参照可能ないくつかのカラムが含まれています。
- 利用可能なデータには、アクセス権として使用されるチームの階層構造があります。マネージャ以上が個人の従業員の機密情報にアクセスできます。
- このソリューションの管理プロセスはシンプルかつ、スケーリングできるようにオペレーションの効率性を提供しなくてはなりません。
- 手動での介入を最小化し、メンテナス性を向上しなくてはなりません。
- このソリューションは、業界標準に準拠し、高いレベルのセキュリティを提供しなくてはなりません。

# ハイレベルなアーキテクチャ

以下で説明するように、このソリューションは業界標準に基づいて構築されます:

- **セキュリティのベストプラクティス:** このソリューションでは、Databricksのベストプラクティスである[Unity Catalog(UC)におけるエンベロープ暗号化](https://medium.com/databricks-platform-sme/envelope-encryption-with-unity-catalog-b5329666d0b6)を導入し、データの機密性を保証するための複数レイヤーのセキュリティを提供します。このアプローチでは、データ暗号化キーとデータ複合化キーの組み合わせを提供し、機密情報に対する堅牢な保護を提供します。
- **ユーザー固有のデータアクセス:** `session_user()`関数を活用することで、システムは行レベルのセキュリティを強制します。このメカニズムは認証されたユーザーIDに基づいて動的にデータをフィルタリングし、個人が自身のロールや権限に適した情報にのみアクセスすることを確実にします。
- **最小権限原則のアクセスコントロール:** このフレームワークでは、最小権限の原則を実装するために、Unity Catalogのきめ細かい権限システムを活用します。これによって、ユーザーのカタログ、スキーマ、テーブル、ビュー、関数に対するアクセスに対する正確なコントロールを可能とし、不必要なアクセスを制限することで潜在的なセキュリティリスクを最小化します。
- **セキュアなシークレット管理:** データへのアクセスに使用されるキーのような機密情報は、*Databricksが管理するシークレットスコープ*を用いてセキュアに格納、管理されます。このアプローチによって、重要な資格情報が保護され、承認されたプロセスやユーザーによって安全にアクセスできるようになります。
- **データの暗号化とコントロールされた復号化:** デフォルトでは、格納されるすべてのデータはテーブルレベルで暗号化されます。ビューを通じてデータにアクセスした際、現在のユーザーの権限に基づいて選択的に復号化されます。これによって、ストレージレベルで許可されないアクセスが発生したとしても、復号化はコントロールされたアクセスポイントを通じて許可されたユーザーに対してのみ行われるので、データが保護され続けることを確実にします。

本書では、システムにおけるライフサイクルを通じて機密情報を保護するために、暗号化、アクセスコントロール、セキュアな資格情報管理を組み合わせたデータセキュリティに対する包括的なアプローチを提供します。

# デザインアプローチ

デフォルトでデータはテーブルで暗号化され、データにアクセスユーザーに対してフィルタリングが行われるのように、その上のビューにデータを復号化する関数を埋め込みます。

ビュー***Payroll_decrypted***を作成するために、テーブル***Payroll_Encrypted***、***Employee_hierarchy***、***Employee_UPN***を使います。***Employee_UPN***テーブルが、***current_user***関数を使う鍵となります。

![](https://community.databricks.com/t5/image/serverpage/image-id/15855i44614554420E3CF6/image-size/large?v=v2&px=999)

# 権限のサマリー

1. Domain.cryptにあるオブジェクト(`key_vault`テーブル、`decrypt`および`unwrap_key`関数)は、管理ユーザーあるいは特定の分離されたチームによってのみアクセスされます。
1. *payroll_decrypted*ビューには、クエリーに対して許可されたマネージャ(階層)やユーザー、グループに制限されたSELECT権限が付与されます。同じグループに対して、このビューが定義されたカタログ、スキーマに対するUSE CATALOG、USE SCHEMA権限が必要です。このグループに対するシークレットスコープの読み取り権限も必要です。
1. *payroll_decrypted*ビューのオーナーは、`decrypt`関数に対する実行権限と、シークレットスコープの読み取り権限が必要です。これは、管理者チームや選別されたチームになることでしょう。

# 実装のウォークスルー

一般的な利用においては、従業員の階層とUPN情報が使われます。ベースとなるpayrollテーブル/データは暗号化されます。`current_user()`を用いて一般的なアクセスをする際に、`decrypt`関数とこれら3つのテーブルを組み合わせてビューを作成します。

## ステップ0

入力としてのキーにアクセスするためのシークレットスコープ、キー暗号化のキー名、ユーザー/グループ。

```py
dbutils.widgets.text(name="secret_scope", defaultValue="piiscope", label="The secret scope to use for DEKs")
dbutils.widgets.text(name="kek_name", defaultValue="piikeyname", label="The name to use for our KEK")
dbutils.widgets.text(name="keyvault_user", defaultValue="payroll_managers", label="The username to grant unprivileged access to decrypt the data")
```

## ステップ1

サンプルの従業員、マネージャテーブルを準備。

```sql
%sql
CREATE OR REPLACE TABLE consume.catalog.employee_hierarchy AS (SELECT * FROM read_files(
 '/Volumes/consume/catalog/synthetic_data/employee_hierarchy.csv',
 format => 'csv',
 header => true,
 inferSchema => true))
```

```sql
%sql
CREATE OR REPLACE TABLE consume.catalog.employee_upn AS (SELECT * FROM read_files(
 '/Volumes/consume/catalog/synthetic_data/employee_upn.csv',
 format => 'csv',
 header => true,
 inferSchema => true));
```

## ステップ2

キー暗号化のキー(KEK)を生成し、それを専用のカタログとスキーマに格納するために`key_vault`テーブルを作成。

```py
from base64 import b64encode
from os import urandom
kek = b64encode(urandom(24)).decode('utf-8')
```

```sql
%sql
CREATE OR REPLACE TABLE sys.crypto.key_vault (
 id BIGINT GENERATED BY DEFAULT AS IDENTITY,
 created_date DATE,
 created_time TIMESTAMP,
 last_modified_time TIMESTAMP,
 created_by STRING,
 managed_by STRING,
 key_name STRING,
 key_version INT,
 key_enabled BOOLEAN,
 key_type STRING,
 key STRING);
```

```py
kek_name = dbutils.widgets.get("kek_name")
sql(f"""
   INSERT INTO sys.crypto.key_vault (created_date, created_time, last_modified_time, created_by, managed_by, key_name, key_version, key_enabled, key_type, key)
   VALUES (current_date(), current_timestamp(), current_timestamp(), session_user(), session_user(), '{kek_name}', 1, True, 'KEK', '{kek}')""")
```

## ステップ3

データ暗号化キー(DEL)を暗号化するためにKEKを使用し、暗号化されたDEKをシークレットとして格納します。

```py
import string
import random
 
dek = b64encode(urandom(24)).decode('utf-8')
iv = ''.join(random.choices(string.ascii_uppercase + string.digits, k=12))
aad = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
 
encrypted_dek = sql(f"SELECT base64(aes_encrypt('{dek}', '{kek}', 'GCM', 'DEFAULT'))").first()[0]
encrypted_iv = sql(f"SELECT base64(aes_encrypt('{iv}', '{kek}', 'GCM', 'DEFAULT'))").first()[0]
encrypted_aad = sql(f"SELECT base64(aes_encrypt('{aad}', '{kek}', 'GCM', 'DEFAULT'))").first()[0]
```

```py
from databricks.sdk import WorkspaceClient
w = WorkspaceClient()
secret_scope = dbutils.widgets.get("secret_scope")
try:
   w.secrets.create_scope(scope=secret_scope)
except Exception as e:
   print(e)
w.secrets.put_secret(scope=secret_scope, key='dek', string_value=encrypted_dek)
w.secrets.put_secret(scope=secret_scope, key='iv', string_value=encrypted_iv)
w.secrets.put_secret(scope=secret_scope, key='aad', string_value=encrypted_aad)
from databricks.sdk.service import workspace
w.secrets.put_acl(scope=secret_scope, permission=workspace.AclPermission.READ, principal=dbutils.widgets.get("keyvault_user"))
```

## ステップ4

我々のキーをアンラップし、データを暗号化するための`unwrap_key`関数を作成します。

```sql
%sql
CREATE OR REPLACE FUNCTION sys.crypto.unwrap_key(key_to_unwrap STRING, key_to_use STRING)
RETURNS STRING
RETURN aes_decrypt(unbase64(key_to_unwrap), (SELECT key FROM sys.crypto.key_vault WHERE key_enabled AND key_name = key_to_use ORDER BY created_date DESC  LIMIT 1), 'GCM', 'DEFAULT')
```

```py
kek_name = dbutils.widgets.get("kek_name")
sql(f"""CREATE OR REPLACE FUNCTION sys.crypto.encrypt(col STRING)
RETURNS STRING
RETURN
   base64(aes_encrypt(col,
   sys.crypto.unwrap_key(secret('{secret_scope}', 'dek'), '{kek_name}'),
   'GCM',
   'DEFAULT',
   sys.crypto.unwrap_key(secret('{secret_scope}', 'iv'), '{kek_name}'),
   sys.crypto.unwrap_key(secret('{secret_scope}', 'aad'), '{kek_name}')
   ))""")
```

## ステップ5

暗号化されたサラリー情報で`payroll_encrypted`テーブルを作成します。

```sql
%sql
CREATE OR REPLACE TABLE consume.catalog.payroll_encrypted AS 
(SELECT
employee_id,
first_name,
last_name,
sys.crypto.encrypt(salary) AS salary
FROM consume.catalog.employee_hierarchy)
```

## ステップ6

データを復号化するための`decrypt`関数を作成します。

```py
sql(f"""CREATE OR REPLACE FUNCTION sys.crypto.decrypt(col STRING)
RETURNS STRING
RETURN
   nvl(CAST(try_aes_decrypt(unbase64(col),
   sys.crypto.unwrap_key(secret('{secret_scope}', 'dek'), '{kek_name}'),
   'GCM',
   'DEFAULT',
   sys.crypto.unwrap_key(secret('{secret_scope}', 'aad'), '{kek_name}')) AS STRING),
   col)
   """)
```

## ステップ7

マネージャが自身の従業位のデータのみを参照できるようにするためのビューを作成するために`decrypt`関数を適用します。

```sql
%sql
create or replace view consume.catalog.payroll_decrypted as
select e.employee_id, e.first_name, e.last_name, m.manager_id, m.manager_email,
sys.crypto.decrypt(e.salary) as salary
from consume.catalog.payroll_encrypted e join consume.catalog.employee_upn m on e.employee_id = m.employee_id
where m.manager_email = current_user()
```

## ステップ8

データをクエリーし、期待した通りにデータが復号化されることを確認します。

```sql
%sql
select * from consume.catalog.payroll_decrypted;
```

# まとめ

Databricksでデータを保護する方法には複数の選択肢がありますが、要件に応じて適切なオプションを選択し、カスタマイズすることが重要です。この記事では、そのような要件の一つとそれを解決し、スケールさせるための詳細なアプローチをようやくしました

# リファレンス

1. [Git Hub](https://github.com/pponnaDB/Securing-Sensitive-Data/tree/main)のサンプルコード
1. [Envelope Encryption with Unity Catalog](https://medium.com/databricks-platform-sme/envelope-encryption-with-unity-catalog-b5329666d0b6)
1. [Databricksにおけるシークレット管理](https://docs.databricks.com/aws/ja/security/secrets)

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)