Cloud Key Management Service (KMS) チュートリアル

# お題
[前回](https://qiita.com/sky0621/items/e4ec222e50615107b405)、GCPのCloud SQLに接続するDBやユーザー、パスワードといった機密情報を`app.yaml`とは別の`secret.yaml`に分ける方法を書いた。
https://qiita.com/sky0621/items/e4ec222e50615107b405#機密情報を記載したファイルsecretyamlを用意してapp-engineデプロイ用のファイルappyamlからインクルード
ただ、このやり方だと、`secret.yaml`をどうやって管理するかという問題が生じる。
今回は、この問題をGCPの[Cloud Key Management Service（略してKMS）](https://cloud.google.com/kms/?hl=ja)を使って解消してみる。
要するに、`secret.yaml`も平文でなく暗号化してGitHub管理してしまう。暗号化・復号用の鍵はGCPで管理するということ。

# 前提
- [GCP](https://cloud.google.com/?hl=ja)は知っている。

以下は済んだ上での作業。

- GCPプロジェクトの作成
- [Cloud SDK](https://cloud.google.com/sdk/docs/)のインストールと初期化・認証

# 開発環境
### # OS
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
```

### # Cloud SDK
```
$ gcloud version
Google Cloud SDK 241.0.0
```

# 実践

## そもそもKMSって

公式ドキュメントやQiitaの記事もふんだんにあるので、説明は↓を見た方がいいと思う。

### 公式ドキュメント

- [Cloud Key Management Service のドキュメント ](https://cloud.google.com/kms/docs/?hl=ja)
- [Cloud KMS を使い暗号化キーの管理をクラウドで ](https://cloudplatform-jp.googleblog.com/2017/01/managing-encryption-keys-in-the-cloud-introducing-Google-Cloud-Key-Management-Service.html)

### その他ドキュメント

- [Cloud KMS Introduction](https://github.com/gcpug/nouhau/tree/master/cloudkms/poem/introduction)
- [Cloud KMSを使ってみる](https://qiita.com/t-yotsu/items/5b419d83787f535da2a2)
- [Cloud KMS (Cloud Key Management Service) について調べたことのまとめ](https://qiita.com/ao_log/items/673e3f37d3e5069d0d03)
- [Cloud KMSでID/Passwordを暗号化し、Cloud Buildで利用する](https://medium.com/google-cloud-jp/cloud-kms%E3%81%A7id-password%E3%82%92%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%97-cloud-builder%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B-e783a23f0a0)
- [Cloud KMS を gcloud コマンドで試す](https://qiita.com/takasp/items/99270cf3bf0546f7e55e)

## 準備

### 「鍵リング」作成

↓のコマンド。任意に当たる部分は「`env`」くらいかな。これは「鍵リング」の名前。

```
$ gcloud kms keyrings create env --location global
ERROR: (gcloud.kms.keyrings.create) FAILED_PRECONDITION: Google Cloud KMS API has not been used in this project before, or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudkms.googleapis.com/overview?project=194057495083 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
```

失敗。KMS APIが使えるようになってないとのこと。指定されたリンクを開くと、↓の画面（たぶん、対象のGoogleアカウント（自分のGCPプロジェクトと紐付いているやつ）でログインしてないと出ないかも？）が表示されたので「有効にする」ボタン押下。

![screenshot-console.developers.google.com-2019.04.08-09-47-58.png](https://qiita-image-store.s3.amazonaws.com/0/180599/2aa020f8-f5d8-f0e9-b7b3-fab56ee101ba.png)

こんな画面が表示される。

![screenshot-console.developers.google.com-2019.04.09-08-32-44.png](https://qiita-image-store.s3.amazonaws.com/0/180599/0cbf5a4c-a4a1-0c88-4bcd-d0a2d011dda6.png)

リトライ。

```
$ gcloud kms keyrings create env --location global
ERROR: (gcloud.kms.keyrings.create) FAILED_PRECONDITION: Google Cloud KMS API has not been used in this project before, or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudkms.googleapis.com/overview?project=194057495083 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
```

まだ失敗。なぜ？
・・・
あ、

```
$ gcloud config list
[compute]
region = asia-northeast1
zone = asia-northeast1-a
[core]
account = 【今回使うGCPプロジェクト用ではないプロジェクトのアカウント】
disable_usage_reporting = False
project = 【今回使うGCPプロジェクト用ではないプロジェクト】

Your active configuration is: [【今回使うGCPプロジェクト用ではない設定の名前】]
```

使うGCPプロジェクトを複数持っている場合、設定を切り替えて使うのだけど、常々確認する癖をつけておかないと、
自分が意図していないGCPプロジェクトに対して、コマンド発行してしまう。。。
※GCPプロジェクトの設定切り替えについては下記参照。
[gcloud configで複数の設定を持って切り替える](https://qiita.com/sky0621/items/597d4de7ed9ba7e31f6d)

というわけで、今回使うGCPプロジェクトの設定に切り替えてから再実行。

```
$ gcloud config configurations activate default
Activated [default].
$
$ gcloud config list
[compute]
region = asia-northeast1
zone = asia-northeast1-c
[core]
account = 【今回使うGCPプロジェクト用アカウント】@gmail.com
disable_usage_reporting = False
project = 【今回使うGCPプロジェクト】

Your active configuration is: [default]
$
$ gcloud kms keyrings create env --location global
$
```

エラーが出なくなった。これでKeyRing作られたってこと？
確認してみる。

![screenshot-console.cloud.google.com-2019.04.09-08-54-52.png](https://qiita-image-store.s3.amazonaws.com/0/180599/fac87153-7b60-15b4-2f4c-41807fbb9074.png)

![screenshot-console.cloud.google.com-2019.04.09-08-56-50.png](https://qiita-image-store.s3.amazonaws.com/0/180599/d14aea5e-edf2-6af6-b9fd-e28bb90a4b8f.png)

出来ているもよう。

### 「鍵」作成

↓のコマンド。任意に当たる部分は「`cloudsql-connect`」と「`env`」くらいかな。
「`cloudsql-connect`」は「鍵」の名前で「`env`」は「鍵リング」の名前だね。

```
$ gcloud kms keys create cloudsql-connect --location global --keyring env --purpose encryption
$
```

例によってエラーなく終わったので、たぶん出来ているんだろう。

![screenshot-console.cloud.google.com-2019.04.09-09-04-27.png](https://qiita-image-store.s3.amazonaws.com/0/180599/90159a96-6c4a-7751-491f-9d0f780c34f7.png)

おー、できてる。
コマンドでも確認できるらしい。

```
$ gcloud kms list
ERROR: (gcloud.kms) Invalid choice: 'list'.
Maybe you meant:
  gcloud kms keyrings list
  gcloud kms keys list
  gcloud kms locations list
  gcloud kms keyrings add-iam-policy-binding
  gcloud kms keyrings create
  gcloud kms keyrings describe
  gcloud kms keyrings get-iam-policy
  gcloud kms keyrings remove-iam-policy-binding
  gcloud kms keyrings set-iam-policy

To search the help text of gcloud commands, run:
  gcloud help -- SEARCH_TERMS
```

間違えた。適当にコマンド打っても、ちゃんと候補らしいものを出してくれるの嬉しい。

```
$ gcloud kms keys list
ERROR: (gcloud.kms.keys.list) argument --location: Must be specified.
```

あ、ロケーションの指定が必要らしい。

```
$ gcloud kms keys list --location global
ERROR: (gcloud.kms.keys.list) value for field [keyRingsId] in collection [cloudkms.projects.locations.keyRings] is required but was not provided
```

あれ？「keyRingsId」が何か変？
あ、どの「鍵リング」の鍵の情報が見たいかを指定しないとダメなのかな。

```
$ gcloud kms keys list --location global --keyring env
NAME                                                                             PURPOSE          LABELS  PRIMARY_ID  PRIMARY_STATE
projects/【今回使うプロジェクト】/locations/global/keyRings/env/cryptoKeys/cloudsql-connect  ENCRYPT_DECRYPT          1           ENABLED
```

そうらしい。

## 暗号化

### 暗号化対象ファイル

Cloud SQLに接続するためのDB名やユーザー名、パスワードといった情報は下記の`secret.yaml`に記載している。
https://github.com/sky0621/go-webapi-for-gae-study

```
$ pwd
/home/sky0621/work/src/go111/src/github.com/sky0621/go-webapi-for-gae-study
$
$ ls -l
合計 28
-rw-r--r-- 1 sky0621 sky0621   26  3月 31 22:15 README.md
-rw-r--r-- 1 sky0621 sky0621   43  4月  1 09:02 app.yaml
-rw-r--r-- 1 sky0621 sky0621  224  3月 31 18:36 docker-compose.yml
-rw-r--r-- 1 sky0621 sky0621  114  3月 31 23:47 go.mod
-rw-r--r-- 1 sky0621 sky0621  342  3月 31 23:47 go.sum
-rw-r--r-- 1 sky0621 sky0621 3075  4月  3 02:32 main.go
-rw-r--r-- 1 sky0621 sky0621  277  3月 31 23:15 secret.yaml
$
$ cat secret.yaml 
env_variables:
  # For Cloud SQL 2nd generation instances, this should be in the form of "project:region:instance".
  CLOUDSQL_CONNECTION_NAME: 【今回使うGCPプロジェクト】:asia-northeast1:【インスタンス名】
  CLOUDSQL_USER: 【ユーザー名】
  CLOUDSQL_PASSWORD: 【パスワード】
  CLOUDSQL_DATABASE: 【DB名】
```

※生で載せるわけにいかないので【〜〜】の部分は濁してる。

### 暗号化実行

`secret.yaml.encrypted`という名前で`secret.yaml`を暗号化したファイルを作る。

```
$ gcloud kms encrypt --location global --keyring env --key cloudsql-connect --plaintext-file secret.yaml --ciphertext-file secret.yaml.encrypted
$
$ ls -l
合計 32
-rw-r--r-- 1 sky0621 sky0621   26  3月 31 22:15 README.md
-rw-r--r-- 1 sky0621 sky0621   43  4月  1 09:02 app.yaml
-rw-r--r-- 1 sky0621 sky0621  224  3月 31 18:36 docker-compose.yml
-rw-r--r-- 1 sky0621 sky0621  114  3月 31 23:47 go.mod
-rw-r--r-- 1 sky0621 sky0621  342  3月 31 23:47 go.sum
-rw-r--r-- 1 sky0621 sky0621 3075  4月  3 02:32 main.go
-rw-r--r-- 1 sky0621 sky0621  277  3月 31 23:15 secret.yaml
-rw-r--r-- 1 sky0621 sky0621  360  4月  9 09:32 secret.yaml.encrypted
$
$ cat secret.yaml.encrypted 

$�!s|��XU{/����(��m^�4�v��8����ʧ���
                                       �\T�R��ݶ�\��
G��jVb��*n��y9XW�
�N|a�XM�feG�
            ��<p��C1
bQls6��z�t|�QWqz�E�|e��z4?OXlFT������ �/٭�A1wЖ��,������d�h�YD���'٣C����w��bp�4�v���9t�2,�cm쫋b���>�
                                                                                                           �6�0�-0�n�AD,����?�g����o�Kw��@����m�4��iz}��XBOa��Q�P����$�_�#�.��l0id��}o����\�q�	 �ΰ~?�J�k�_���W�N/_�Kĕ�#{�nH�-�9��=�Y>*�
```

ふむ、出来てる。

## 復号

復号できなきゃ意味ないので。

いったん、平文の`secret.yaml`を別名に変えておいてから、復号を試す。

```
$ mv secret.yaml secret.yaml.bk
$ ls -l
合計 32
-rw-r--r-- 1 sky0621 sky0621   26  3月 31 22:15 README.md
-rw-r--r-- 1 sky0621 sky0621   43  4月  1 09:02 app.yaml
-rw-r--r-- 1 sky0621 sky0621  224  3月 31 18:36 docker-compose.yml
-rw-r--r-- 1 sky0621 sky0621  114  3月 31 23:47 go.mod
-rw-r--r-- 1 sky0621 sky0621  342  3月 31 23:47 go.sum
-rw-r--r-- 1 sky0621 sky0621 3075  4月  3 02:32 main.go
-rw-r--r-- 1 sky0621 sky0621  277  3月 31 23:15 secret.yaml.bk
-rw-r--r-- 1 sky0621 sky0621  360  4月  9 09:32 secret.yaml.encrypted
$
$ gcloud kms decrypt --location global --keyring env --key cloudsql-connect --ciphertext-file secret.yaml.encrypted --plaintext-file secret.yaml
$
$ ls -l
合計 36
-rw-r--r-- 1 sky0621 sky0621   26  3月 31 22:15 README.md
-rw-r--r-- 1 sky0621 sky0621   43  4月  1 09:02 app.yaml
-rw-r--r-- 1 sky0621 sky0621  224  3月 31 18:36 docker-compose.yml
-rw-r--r-- 1 sky0621 sky0621  114  3月 31 23:47 go.mod
-rw-r--r-- 1 sky0621 sky0621  342  3月 31 23:47 go.sum
-rw-r--r-- 1 sky0621 sky0621 3075  4月  3 02:32 main.go
-rw-r--r-- 1 sky0621 sky0621  277  4月  9 09:41 secret.yaml
-rw-r--r-- 1 sky0621 sky0621  277  3月 31 23:15 secret.yaml.bk
-rw-r--r-- 1 sky0621 sky0621  360  4月  9 09:32 secret.yaml.encrypted
$
$ cat secret.yaml 
env_variables:
  # For Cloud SQL 2nd generation instances, this should be in the form of "project:region:instance".
  CLOUDSQL_CONNECTION_NAME: 【今回使うGCPプロジェクト】:asia-northeast1:【インスタンス名】
  CLOUDSQL_USER: 【ユーザー名】
  CLOUDSQL_PASSWORD: 【パスワード】
  CLOUDSQL_DATABASE: 【DB名】
```

※生で載せるわけにいかないので【〜〜】の部分は濁してる。

うん、OKだね。

# まとめ
ひとまず機密情報の暗号化と復号の確認はできた。これで、環境別の機密情報も暗号化してGitHubに載せられる。
ただ、CI/CDのこと考えたら、このままじゃダメなので、次は、Cloud Buildと絡めたり、Terraformと絡めたりの方法を調べてみようか。