Amazon Redshift で ダイナミックデータマスキング 続編：プレビュー版ビルトイン機能を試す

追記：2023/4/20にGAになりました
https://aws.amazon.com/about-aws/whats-new/2023/04/amazon-redshift-availability-dynamic-data-masking/

# ダイナミックデータマスキング（DDM)とは？
- 権限のないユーザに対してのみ、機密データをマスクした状態でしか見せないようにすること。
- 具体的には、ポリシーに従って電話番号やメールアドレスなど個人情報を＊＊＊とマスクして表示させるなどのイメージ。
- 例えば、所属Aのユーザには生データを見せるが、所属Bのユーザには *** でマスクするなど

## 何が嬉しい？

- 表示内容を変えたい場合は、権限別にビューを作成して個別にアクセス権限設定をする必要があった
- DDMを利用すると、テーブルは1つで、ビューなども個別に準備する必要なく、ポリシー設定だけでRBACに基づく表示方法制御ができるので管理が効率化できる
- そもそも見せたくない列や行ごと固定的に非表示にするなら列レベル/行レベルセキュリティが利用できるが、完全に非表示にせず、集計用に一部マスクして表示したい要件（例えばメールアドレスのドメイン部分だけ表示、ハッシュ化して一意性を維持して集計、など）では便利

# Redshift のダイナミックデータマスキング機能
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/283df5c2-fcf9-b1cd-4362-00dccd6f25cb.png)
参考：AWS re:Invent 2022 - What’s new with Amazon Redshift (ANT201)　より

https://www.youtube.com/watch?v=pnPHEOlRcHQ&list=PLBdoS3SSnaglLCP7B4bVXYC1CpAZlwVpe&index=26


- Python UDFを利用した実装方法が公式GitHubに公開されていたが、ビルトイン機能が発表された（2022/12）
- 元々公開されていた方法はこちら
    - https://qiita.com/suzukihi724/items/f7f8a1b53c36a82e4659
- 新たに公開されたビルトイン機能がこちら
    - https://aws.amazon.com/jp/about-aws/whats-new/2022/11/amazon-redshift-support-dynamic-data-masking-preview/
- 2022年12月時点でまだプレビュー中

# プレビュー版ビルトイン機能を試す

- 公式ドキュメントがこちら
    - https://docs.aws.amazon.com/redshift/latest/dg/t_ddm.html
- プレビュー版なのであくまで検証用途で、今後仕様変更になる可能性は考慮が必要
- 公式ドキュメントの設定例で実現するのは以下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/27ab2962-e4bb-679e-e682-cd02e1c24536.png)
- シナリオ概要
    - 通常ユーザと分析ユーザを準備
    - ユーザ識別IDとクレジット番号カード列を含むテーブルを作成
    - クレジットカード番号を「全てマスクするポリシー」と、「部分的にマスクするポリシー」を作成
    - 部分的にマスクする処理内容はPythonUDFで定義して「部分的にマスクするポリシー」から呼び出して使う
    - 基本的には「全てマスクするポリシー」でクレジットカード番号は 000000XXXX0000 に変換されて表示される
    - analytics_roleに属するユーザのみ「部分的にマスクするポリシー」が優先適用されて、一部分だけマスクされて表示される
    - 表示方法が切り替わるだけで、テーブルは1つ

## [1] プレビュー用クラスタ作成
- プレビューを試す場合は、新規にプレビュー用のProvisionedクラスタまたはサーバーレスワークグループを作成する必要あり
- 作成はコンソールの上の青色の「Create preview cluster」から
- Preview_trackは`preview_2022`
- 対応リージョンは
    - US East (Ohio) – us-east-2
    - US East (N. Virginia) – us-east-1
    - US West (Oregon) – us-west-2
    - Asia Pacific (Tokyo) – ap-northeast-1
    - Europe (Ireland) – eu-west-1
    - Europe (Stockholm) – eu-north-1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/65416b61-5997-143d-99b3-83f252504797.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/5689018b-405a-2e8e-f0e2-7f8eca1afbc7.png)

## [2] 事前準備：テーブル、IAM ROLE、db userなど作成

```SQL
--デーブル作成        
CREATE TABLE credit_cards (
  customer_id INT,
  credit_card TEXT
);

--作成したテーブルにデータ投入
INSERT INTO credit_cards
VALUES
  (100, '4532993817514842'),
  (100, '4716002041425888'),
  (102, '5243112427642649'),
  (102, '6011720771834675'),
  (102, '6011378662059710'),
  (103, '373611968625635')
;

--作成したテーブルに参照権限付与
GRANT SELECT ON credit_cards TO PUBLIC;

--ユーザを作成
CREATE USER regular_user WITH PASSWORD '1234Test!';
CREATE USER analytics_user WITH PASSWORD '1234Test!';

--分析ユーザーroleを作成して、analytics_userに紐付け
CREATE ROLE analytics_role;
GRANT ROLE analytics_role TO analytics_user;
      
```

## [3] ポリシーとUDFを作成

```SQL
--全てマスクするポリシーを作成
CREATE MASKING POLICY mask_credit_card_full
WITH (credit_card VARCHAR(256))
USING ('000000XXXX0000');

--部分的にマスクするポリシーで利用するマスク処理内容をUDFで定義
CREATE FUNCTION REDACT_CREDIT_CARD (credit_card TEXT)
RETURNS TEXT IMMUTABLE
AS $$
    import re
    --正規表現で変換
    regexp = re.compile("^([0-9]{6})[0-9]{5,6}([0-9]{4})")
 
    match = regexp.search(credit_card)
    if match != None:
        first = match.group(1)
        last = match.group(2)
    else:
        first = "000000"
        last = "0000"
    
    return "{}XXXXX{}".format(first, last)
$$ LANGUAGE plpythonu;

--部分的にマスクするポリシーを作成
CREATE MASKING POLICY mask_credit_card_partial
WITH (credit_card VARCHAR(256))
USING (REDACT_CREDIT_CARD(credit_card));
```
## [4] 作成したポリシーをアタッチ

```SQL
--全てマスクするポリシーを、credit_cardsテーブルのcredit_cardカラムに適用するために、PUBLICにアタッチ
ATTACH MASKING POLICY mask_credit_card_full
ON credit_cards(credit_card)
TO PUBLIC;

--部分的にマスクするポリシーを、credit_cardsテーブルのcredit_cardカラムに適用するために、analytics_roleにアタッチ
--優先度10を設定。同一対象のポリシーがあった場合はPRIORITY値が大きい方が優先される（つまり全てをマスクするポリシーより優先される）
ATTACH MASKING POLICY mask_credit_card_partial
ON credit_cards(credit_card)
TO ROLE analytics_role
PRIORITY 10;
```
## [5] 表示内容確認

### ① 通常ユーザの見え方
```SQL
SET SESSION AUTHORIZATION regular_user;
SELECT * FROM credit_cards;
RESET SESSION AUTHORIZATION;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/cef5ac5f-6fe0-9777-7f7d-b52ef9ed6deb.png)
全部マスクされる

### ② 分析ユーザの見え方
```SQL
SET SESSION AUTHORIZATION analytics_user;
SELECT * FROM credit_cards;
RESET SESSION AUTHORIZATION;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/134ad90d-6aad-2859-23e8-c815b6fbce49.png)
一部だけマスクされる
 
## [6] 設定状況確認

### 1. ポリシー定義の確認
`SELECT * FROM svv_masking_policy;`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/277f9671-4fd1-2bd9-9a0d-d67d3e990f9b.png)


### 2. ポリシーのアタッチ状況確認
`SELECT * FROM svv_attached_masking_policy; `
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112537/e3a59462-e4b4-5a4e-5d5b-1e844129adba.png)


## [7] ポリシーのデタッチと削除

```SQL
--detach both masking policies from the credit_cards table
DETACH MASKING POLICY mask_credit_card_full 
ON credit_cards(credit_card) 
FROM PUBLIC;

DETACH MASKING POLICY mask_credit_card_partial 
ON credit_cards(credit_card) 
FROM ROLE analytics_role;

--drop both masking policies
DROP MASKING POLICY mask_credit_card_full;

DROP MASKING POLICY mask_credit_card_partial;      
```

# まとめ

- まだプレビューだが、RBACと組み合わせてきめ細かいダイナミックデータマスキング制御ができることがわかる
- 実際のマスキング処理もPython UDFで柔軟に拡張できる
- 特にDDM利用の追加料金はない

# 参考

- あくまでプレビューなので今後変更になる可能性あると思いますが、現状作成済みポリシーの変更はできない（作り直しが必要）、RLS(行レベルセキュリティ）との併用の場合はRLS適用後にDDMが適応される、など制限事項もある
    - https://docs.aws.amazon.com/redshift/latest/dg/t_ddm.html