Cloud Data Loss Prevention

# 概要
Cloud Data Loss prevention(以降、DLP）は、メールアドレスやパスワード、口座番号などの機密情報がテキストや画像に含まれていないかどうかをスキャンできるサービス。info検出器というものを使用して、検出したい情報を設定しておくことで、検出された情報を難読化したり削除したりできる。ジョブトリガーを作成することで、スキャンをスケジューリング（定期実行）することができる。

# info検出器
組み込みでinfo検出器として用意されているものは以下のドキュメントに記載されている。
https://cloud.google.com/dlp/docs/infotypes-reference?hl=ja

これらは元々用意されている検出器なのに対して、カスタムinfo検出器を作成することもできる。これにより、「特定のリストの値に一致した場合、匿名化する」や「正規表現にマッチした場合に匿名化する」などの設定が可能になる。
https://cloud.google.com/dlp/docs/creating-custom-infotypes?hl=ja

組み込みのinfo検出器かカスタムのinfo検出器かにかかわらず、特定のワードを除外したり、特定のキーワードが近くに配置されていた場合などに検出する（ホットワードルール）などの柔軟な設定が行える。

### 除外ルール
特定のルールにマッチした文字列は除外するように設定することができる。例えば以下のようなもの。結構柔軟に制御できる。
https://cloud.google.com/dlp/docs/creating-custom-infotypes-rules?hl=ja#exclusion_rules_api_overview

- 正規表現にマッチした場合にその値を除外する
- 特定のワードを含んでいた場合にその値を除外する
- 組み込みinfo検出器から、カスタムinfo検出器で検出される値を除外する
- 特定のinfo検出器ともう一つのinfo検出器どちらにもマッチした時に一方を優先する

### ホットワードルール
ホットワードルールは、特定の文字列がある範囲内に存在している場合にマッチするように設定すること。少しわかりにくいと思うので、ドキュメントに乗っていた例を拝借する。

> たとえば、医療データベースで患者名をスキャンするとします。Cloud DLP に組み込まれている PERSON_NAME infoType 検出器を使用できますが、その場合、患者名だけでなくすべての人の名前が一致してしまいます。これを修正するには、起動ワードルールを正規表現のカスタム infoType の形式で組み込んで、一致候補の最初の文字から特定の文字の近接性の範囲内で単語「患者」を探します。このパターンに一致した結果は特殊な基準を満たしているので、可能性として「very likely」を割り当てることができます。

つまり、「田中太郎」は反応しないが、「患者名　田中太郎」のように書かれている場合はマッチする。可能性に関して、Cloud DLPのスキャンを作成する際に、可能性の閾値を設定することができる。例えば、「LIKELY」以上にマッチしたものだけを匿名化するようにできる。


マッチした場合の可能性は以下の値から設定できる。
![Screen Shot 2021-08-09 at 14.44.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642884/44700710-c2bd-f9db-e4a3-4eb29ee91f4b.png)


# 匿名化
テキストや画像からマッチした値を匿名化することができる。具体的に次のような処理が行える。

- `*`や`#`で文字列の一部、もしくは全てを置換する
- サロゲートとトークンで置換する
- ランダムで生成された鍵、もしくは事前に用意した鍵で暗号化する

画像の場合は、不透明な長方形でマッチしている文字列を見えなくする。
![Screen Shot 2021-08-09 at 15.04.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642884/f4658597-e5cf-3614-605c-cfc833ecd8d3.png)
# アクション
匿名化のほかに取れるアクションとしては以下のようなものがある。

- 検出結果をBigQueryに送信する
- PubSubに通知
- Security Command Centerに検出結果を送信する
- 検出結果をCloud Monitoringに送信する
- メールで通知する
- Cloud DataCatalogに検出結果を送信する（メタデータ一元管理サービス）


# ストレージ・データベースに対するスキャン
Cloud Storage、Cloud Datastore、Big Queryに保存されているデータをスキャンして、機密情報を検出することができる。また、ハイブリッドジョブを使用することで、任意のデータソースをスキャンすることもできる。DLPがネイティブでサポートしているのはGCSとDatastoreとBigQueryだけだが、自前でデータをDLPに送信する処理を記述することで実質全てのデータソースをスキャンさせることができるようになる。

# リスク分析
機密情報が正しく匿名化されていない場合、攻撃者は機密情報を取得、再識別することができる。リスク分析ジョブを使用することで、これらのリスクをいくつかの指標に基づいて数値化することができる。

指標については、少し複雑そうだったので別の記事であげる。
https://cloud.google.com/dlp/docs/concepts-risk-analysis?hl=ja

# 暗号鍵を使用して秘匿化ってみた
以下のドキュメントの流れに沿って、暗号鍵を使用して秘匿化して復号するまでの流れをやってみようと思う。
https://cloud.google.com/dlp/docs/quickstart-deid-reid?hl=ja

このチュートリアルでは、以下のような流れで進んでいく。
1. Cloud KMSを使用して、データを暗号化するための鍵を暗号化する鍵(Key Encryption Key）を生成する。（エンペローブ暗号化）
2. データを暗号化するための暗号鍵(DEK = Data Encryption Key)をAES256で作成する
3. Cloud KMSで作成したKEK（Key Encryption Key）を使用して、DEKを暗号化する
4. KEKとDEKを使用して、Cloud DLPにテキストをDEKを使用して暗号化するリクエストを送信する
5. 再識別リクエストをCloud DLPに送信して、受け取ったデータを復号化する

### 1. Cloud KMSを使用して、データを暗号化するための鍵を暗号化する鍵を生成する。（エンペローブ暗号化）
Cloud DLPとCloud KMSのAPIを有効化し、ローカルでDLPとKMSにリクエストを送る際に使用するサービスアカウントを作成する。作成したサービスアカウントの鍵をローカルにダウンロードする。ダウンロードした鍵ファイルを、環境変数`GOOGLE_APPLICATION_CREDENTIALS`で指定する。

ここで指定したサービスアカウントはAPIリクエスト時に、Authoizationヘッダーの値として使用される。

KMSでキーリングを作成する。キーリングはKMSで管理される鍵のグループ。

```
$ gcloud kms keyrings create "dlp-keyring" --location "global"
```

先ほど作ったキーリングに紐づいたKEKを作成する。

```
$ gcloud kms keys create "dlp-key" --location "global" --keyring "dlp-keyring" --purpose "encryption"
```

### 2. データを暗号化するための暗号鍵(DEK = Data Encryption Key)をAES256で作成する
256ビットのAES鍵を作成する

```
$ openssl rand -out "./aes_key.bin" 32
$ base64 -i ./aes_key.bin
SRNLB************R7SSi8w=　(これがDEK)
```

### 3. Cloud KMSで作成したKEK（Key Encryption Key）を使用して、DEKを暗号化する
KMSで作成されたKEKはKMSの外に出ることはないので、KMSにリクエストを送信することで暗号化してもらう必要がある。また、KMSはエンペローブ暗号化（鍵を暗号化するための鍵）のためのプロダクトなので、64KiB以上のデータは暗号化できないようになっている。

```
$ curl "https://cloudkms.googleapis.com/v1/projects/[project-id]/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key:encrypt" \
        --request "POST" \
        --header "Authorization:Bearer $(gcloud auth application-default print-access-token)" \
        --header "content-type: application/json" \
        --data "{\"plaintext\": \"[base64でエンコーディングしたDEK]\"}"

{
  "name": "projects/[project-id]/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key/cryptoKeyVersions/1",
  "ciphertext": "[KEKによって暗号化されたDEK]",
  "ciphertextCrc32c": "875343496",
  "protectionLevel": "SOFTWARE"
}
``` 

### 4. KEKで暗号化したDEKを使用して、Cloud DLPにテキストをDEKを使用して暗号化するリクエストを送信する

以下のようなJSONファイルを作成する。ファイル名は`dlp.json`にする。このJSONファイルには、以下のような情報が含まれている。

- スキャン対象のテキスト
- 組み込みのinfo検出器（EMAIL_ADDRESS)
- 検出したワードを暗号化する設定(cryptoDeterministicConfig）

※ ここでは、KEKで暗号化したDEKは、`wrappedKey`のように呼ばれている。

```dlp.json
{
  "item": {
    "value": "My name is Alicia Abernathy, and my email address is aabernathy@example.com."
  },
  "deidentifyConfig": {
    "infoTypeTransformations": {
      "transformations": [
        {
          "infoTypes": [
            {
              "name": "EMAIL_ADDRESS"
            }
          ],
          "primitiveTransformation": {
            "cryptoDeterministicConfig": {
              "cryptoKey": {
                "kmsWrapped": {
                  "cryptoKeyName": "projects/PROJECT_ID/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key",
                  "wrappedKey": "WRAPPED_KEY"
                }
              },
              "surrogateInfoType": {
                "name": "EMAIL_ADDRESS_TOKEN"
              }
            }
          }
        }
      ]
    }
  },
  "inspectConfig": {
    "infoTypes": [
      {
        "name": "EMAIL_ADDRESS"
      }
    ]
  }
}
```

DLPに暗号鍵を指定した匿名化リクエストを送信すると、`aabernathy@example.com`というメールアドレスだった部分が、`EMAIL_ADDRESS_TOKEN(52):AdEnOdXI2IzNQBvPzEJ7J5AXyELoYlDCi5RwTrdp73quHmdQTxxR`のように、サロゲートとトークンに置換されて返ってきた。

```
$ curl -s \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token" \
  -H "Content-Type: application/json" \
  https://dlp.googleapis.com/v2/projects/[project-id]locations/global/content:deidentify \
  -d @dlp.json

{
  "item": {
    "value": "My name is Alicia Abernathy, and my email address is EMAIL_ADDRESS_TOKEN(52):AdEnOdXI2IzNQBvPzEJ7J5AXyELoYlDCi5RwTrdp73quHmdQTxxR."
  },
  "overview": {
    "transformedBytes": "22",
    "transformationSummaries": [
      {
        "infoType": {
          "name": "EMAIL_ADDRESS"
        },
        "transformation": {
          "cryptoDeterministicConfig": {
            "cryptoKey": {
              "kmsWrapped": {
                "wrappedKey": "[wrapped-key]",
                "cryptoKeyName": "projects/[project-id]/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key"
              }
            },
            "surrogateInfoType": {
              "name": "EMAIL_ADDRESS_TOKEN"
            }
          }
        },
        "results": [
          {
            "count": "1",
            "code": "SUCCESS"
          }
        ],
        "transformedBytes": "22"
      }
    ]
  }
}
```

### 5. 再識別リクエストをCloud DLPに送信して、受け取ったデータを復号化する
再識別リクエストのためのJSONファイルを作成する。ファイル名は、`reidentity.json`にする。

```reidentity.json
{
  "reidentifyConfig":{
    "infoTypeTransformations":{
      "transformations":[
        {
          "infoTypes":[
            {
              "name":"EMAIL_ADDRESS_TOKEN"
            }
          ],
          "primitiveTransformation":{
            "cryptoDeterministicConfig":{
              "cryptoKey":{
              "kmsWrapped": {
                "cryptoKeyName": "projects/[PROJECT_ID]/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key",
                "wrappedKey": "[WRAPPED_KEY]"
              }
            },
              "surrogateInfoType":{
                "name":"EMAIL_ADDRESS_TOKEN"
              }
            }
          }
        }
      ]
    }
  },
  "inspectConfig":{
    "customInfoTypes":[
      {
        "infoType":{
          "name":"EMAIL_ADDRESS_TOKEN"
        },
        "surrogateType":{

        }
      }
    ]
  },
  "item":{
    "value": "My name is Alicia Abernathy, and my email address is [TOKEN]."
  }
}
```

DLPに再識別リクエストを送信する。

```
curl -s \
-H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
-H "Content-Type: application/json" \
https://dlp.googleapis.com/v2/projects/PROJECT_ID/locations/global/content:reidentify \
-d @reidentify-request.json

{
 "item": {
   "value": "My name is Alicia Abernathy, and my email address is aabernathy@example.com."
 },
 "overview": {
   "transformedBytes": "70",
   "transformationSummaries": [
     {
       "infoType": {
         "name": "EMAIL_ADDRESS"
       },
       "transformation": {
         "cryptoDeterministicConfig": {
           "cryptoKey": {
             "kmsWrapped": {
               "wrappedKey": "CiQAYuuIGo5DVaqdE0YLioWxEhC8LbTmq7Uy2G3qOJlZB7WXBw0SSQAjdwP8ZusZJ3Kr8GD9W0vaFPMDksmHEo6nTDaW/j5sSYpHa1ym2JHk+lUgkC3Zw5bXhfCNOkpXUdHGZKou1893O8BDby/82HY=",
               "cryptoKeyName": "projects/PROJECT_ID/locations/global/keyRings/dlp-keyring/cryptoKeys/dlp-key"
             }
           },
           "surrogateInfoType": {
             "name": "EMAIL_ADDRESS_TOKEN"
           }
         }
       },
       "results": [
         {
           "count": "1",
           "code": "SUCCESS"
         }
       ],
       "transformedBytes": "70"
     }
   ]
 }
}
```

このようにトークンが復号化されて返ってきた。今回は対象鍵（暗号化と復号化の鍵が同じ）を使用したが、非対称鍵でもできるらしい。ちなみにちょくちょく出てくる`gcloud auth default-application print-access-token` コマンドは、序盤で作成したサービスアカウントの鍵ファイルの認証情報をJWTにして表示してくれるコマンド。`GOOGLE_APPLICATION_CREDENTIALS`にサービスアカウントの鍵ファイルを指定することで、APIリクエストの際に認証情報として使用できるJWTトークンを取得できる。[完]

# まとめ
今回はRESTを使用して暗号鍵を使用した匿名化を行なったんですが、JSONで設定を書くのは個人的にはあんまり好きじゃないので、疲れました。実際に使用するときはクライアントライブラリ一択ですね。

Cloud KMSは秘密鍵を管理するストレージのようなサービスだと思ってたのですが今回で違うってことがわかりました。エンペローブ暗号化という概念で使用されるKEKを保管するためのストレージで、KEKによって暗号化されるDEKは各自保管する必要がありそうです。おそらくこの鍵は、Secret Managerで保管するのがいいんかな、、、セキュリティ詳しい人アドバイスください！！！