gcloudコマンドで試すGCP Secret Manager

[Secret Manager](https://cloud.google.com/secret-manager)は、GCP上でパスワードやAPIキーなどの秘密情報を安全に管理するためのサービスです。[2020年3月にGA](https://cloud.google.com/secret-manager/docs/release-notes)しました。

似たカテゴリのサービスである[Cloud Key Management Service](https://cloud.google.com/kms)（KMS）は、秘密情報そのものではなく、それらを暗号化・複合するための鍵を管理します。

以前はKMSと独自の暗号化・複合ソリューション、その他[Berglas](https://github.com/GoogleCloudPlatform/berglas)や[HashiCorp Vault](https://www.vaultproject.io/)を利用していたケースの一部もSercert Manager単独で賄えるはずです。

# 概念

SecretとVersionという単位を理解する必要があります。

Secretは各プロジェクトのグローバルに作成されます。Secretに対しては削除、Versionの追加、ラベルの付与などの操作が可能です。

SecretはVersionを持ちます。個々のVersionは秘密情報や有効、無効、削除済みのステータスを持ちます。

たとえば、「Service AのパスワードをSecret Managerで管理し、アプリケーションから利用したい」とします。これを実現するには、`service-a-password`という名前でSecretを作成し、パスワードを指定してVersionを作成します。このパスワードをアプリケーションから利用する際は、`service-a-password` SecretのVersion 1を指定してSecret Managerから取得します。

パスワードをローテーションする際は、`service-a-password` SecretにVersionを追加します。

GCPのコンソールから確認するとつぎのようになります。

<img width="709" alt="service-a-password.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46547/ba362c93-c2a5-1a22-fdf7-e07812f89853.png">


# コマンド概要

gcloudコマンドではSecretと、locationsとversionsに対する操作が可能です。[]は適当に読み替えてください。

```shell
# GCPプロジェクト内ではじめて利用する場合はAPIを有効化します
$ gcloud services enable --project [PROJECT_ID] secretmanager.googleapis.com

$ gcloud secrets
ERROR: (gcloud.secrets) Command name argument expected.

Available groups for gcloud secrets:

      locations               Manage locations of users' secrets.
      versions                Manage secret versions.

Available commands for gcloud secrets:

      add-iam-policy-binding     Add IAM policy binding to a secret.
      create                     Create a new secret.
      delete                     Delete a secret.
      describe                   Describe a secret's metadata.
      get-iam-policy             Get the IAM policy for the secret.
      list                       List all secret names.
      remove-iam-policy-binding  Remove IAM policy binding for a secret.
      set-iam-policy             Set the IAM policy binding for a secret.
      update                     Update a secret's metadata.
...

$ gcloud secrets locations
ERROR: (gcloud.secrets.locations) Command name argument expected.

Available commands for gcloud secrets locations:

      describe                Describe a location.
      list                    List all available locations.
...

$ gcloud secrets versions
ERROR: (gcloud.secrets.versions) Command name argument expected.

Available commands for gcloud secrets versions:

      access                  Access a secret version's data.
      add                     Create a new version of an existing secret.
      describe                Describe metadata about the secret version.
      destroy                 Destroy a secret version's metadata and secret
                              data.
      disable                 Disable the version of the provided secret.
      enable                  Enable the version of the provided secret.
      list                    List all versions for a secret.
...
```

listは利用可能なロケーションのリストです。

```shell
$ gcloud secrets locations list
NAME                  LOCATION
asia-east1            Taiwan
asia-southeast1       Singapore
australia-southeast1  Sydney
europe-north1         Finland
europe-west1          Belgium
europe-west4          Netherlands
us-central1           Iowa
us-east1              South Carolina
us-east4              Northern Virginia
us-west1              Oregon
us-west2              Los Angeles
```

gcloudコマンドからSecretを作成するときは、`--locations`オプションで指定します。指定しない場合はGCPがよしなに選択します。後から変更できません。

# 準備

gcloudコマンドで挙動を確認するにあたり、つぎのコマンドで権限を付与します。

```shell
$ gcloud projects add-iam-policy-binding [PROJECT_ID] --member="serviceAccount:[SERVICE_ACCOUNT]" --role="roles/secretmanager.admin"
```

[利用できるロール](https://cloud.google.com/secret-manager/docs/access-control)はつぎの3つです。

* roles/secretmanager.admin
* roles/secretmanager.secretAccessor
* roles/secretmanager.viewer

今回はSecret自体を作成する必要があるため、`roles/secretmanager.admin`を付与しています。

# Secret・Versionの作成・変更

`test`という名前のSecretを作成し、秘密情報を保存する方法を見ていきます。

```shell
$ gcloud secrets create test
Created secret [test].

$ gcloud secrets list
NAME  CREATED              REPLICATION_POLICY  LOCATIONS
test  2020-07-14T12:44:01  automatic

# Secret自体を消そうとすると、含むVersionも一緒に消します。
$ gcloud secrets delete test
You are about to destroy the secret [test] and its [0] version(s).
This action cannot be reversed.

Do you want to continue (Y/n)?  n

ERROR: (gcloud.beta.secrets.delete) Aborted by user.

$ gcloud beta secrets describe test
createTime: '2020-07-14T12:44:01.379968Z'
name: projects/xxxxxxxxxxxx/secrets/test
replication:
  automatic: {}

# Secret自体にできる操作はラベル関連です
$ gcloud secrets update test -h
Usage: gcloud secrets update SECRET [optional flags]
  optional flags may be  --clear-labels | --help | --remove-labels |
                         --update-labels

For detailed information on this command and its flags, run:
  gcloud secrets update --help

# testtestという秘密情報でVersionを追加します
$ echo "test test" > secret.txt
$ gcloud secrets versions add test --data-file ./secret.txt
Created version [1] of the secret [test].

$ gcloud secrets versions list test
NAME  STATE    CREATED              DESTROYED
1     enabled  2020-07-14T13:16:36  -

# describeはデータを確認するものではありません
$ gcloud secrets versions describe 1 --secret test
createTime: '2020-07-14T13:16:36.616572Z'
name: projects/xxxxxxxxxxxx/secrets/test/versions/1
state: ENABLED

# Versionのデータを読むのは'access'です
$ gcloud secrets versions access 1 --secret test
test test

# Version毎に無効にできます
$ gcloud secrets versions disable 1 --secret test
Disabled version [1] of the secret [test].

$ gcloud secrets versions list test
NAME  STATE     CREATED              DESTROYED
1     disabled  2020-07-14T13:16:36  -

# 無効にしたVersionにアクセスするとエラーになります
$ gcloud secrets versions access 1 --secret test
ERROR: (gcloud.secrets.versions.access) FAILED_PRECONDITION: Secret Version [projects/xxxxxxxxxxxx/secrets/test/versions/1] is in DISABLED state.

# Versionを削除する前にdisableすることが推奨されています
$ gcloud secrets versions destroy 1 --secret test
You are about to destroy version [1] of the secret [test]. This action
 cannot be reversed.

Do you want to continue (Y/n)?  Y

Destroyed version [1] of the secret [test].

# 削除したVersionにアクセスしてもエラーになりますが、状態は区別されています
$ gcloud secrets versions access 1 --secret test
ERROR: (gcloud.secrets.versions.access) FAILED_PRECONDITION: Secret Version [projects/xxxxxxxxxxxx/secrets/test/versions/1] is in DESTROYED state.

$ gcloud secrets versions list test
NAME  STATE      CREATED              DESTROYED
1     destroyed  2020-07-14T13:16:36  2020-07-14T13:27:25

# Versionを増やします
$ gcloud secrets versions add test --data-file ./secret.txt
Created version [2] of the secret [test].

$ gcloud secrets versions list test
NAME  STATE      CREATED              DESTROYED
2     enabled    2020-07-14T13:28:46  -
1     destroyed  2020-07-14T13:16:36  2020-07-14T13:27:25
```

# Secretの権限管理

`test` Secretにアクセスできるサービスを設定します。`system-a`というService AccountをもつサービスがこのSecretを利用するとしましょう。

```shell
# system-a Service Accountを用意します
$ gcloud iam service-accounts create system-a
Created service account [system-a].

# gcloud secretsコマンドで、system-aにSecret Manager関連のポリシーをバインディングできます
# roles/secretmanager.secretAccessor はデータアクセス専用です
$ gcloud secrets add-iam-policy-binding test --member=serviceAccount:system-a@yyyyyyyyyyyyy.iam.gserviceaccount.com --role=roles/secretmanager.secretAccessor
Updated IAM policy for secret [test].
bindings:
- members:
  - serviceAccount:system-a@yyyyyyyyyyyyy.iam.gserviceaccount.com
  role: roles/secretmanager.secretAccessor
etag: BwWqZxB39ss=
version: 1

# gcloud secretsコマンドでroles/appengine.appAdminを付与しようとするとエラーになります
$ gcloud secrets add-iam-policy-binding test --member=serviceAccount:system-a@yyyyyyyyyyyyy.iam.gserviceaccount.com --role=roles/appengine.appAdmin
ERROR: Policy modification failed. For a binding with condition, run "gcloud alpha iam policies lint-condition" to identify issues in condition.
ERROR: (gcloud.secrets.add-iam-policy-binding) INVALID_ARGUMENT: Role roles/appengine.appAdmin is not supported for this resource.

$ gcloud secrets get-iam-policy test
bindings:
- members:
  - serviceAccount:system-a@yyyyyyyyyyyyy.iam.gserviceaccount.com
  role: roles/secretmanager.secretAccessor
etag: BwWqZxB39ss=
version: 1 
```

# Secretの利用

`system-a`というService Accountからアクセスしてみましょう。

今回はgcloudコマンドからアクセスしますが、コンピューティング系のサービスのService Accountを設定し、GCPクライアントライブラリからアクセスするのも要領は同じです。

```shell
# system-aのService Accountのクレデンシャルをローカルに…
$ gcloud auth activate-service-account --key-file system-a-service-account.json
Activated service account credentials for: [system-a@yyyyyyyyyyyyy.iam.gserviceaccount.com]

# test Secretのversion 2にアクセス
$ gcloud secrets versions access 2 --secret test
test test

# SecretのVersionにアクセスする以外の操作はエラーになります
$ gcloud secrets versions list test
ERROR: (gcloud.secrets.versions.list) PERMISSION_DENIED: Permission 'secretmanager.versions.list' denied for resource 'projects/yyyyyyyyyyyy/secrets/test' (or it may not exist).

$ gcloud secrets locations list
ERROR: (gcloud.secrets.locations.list) User [system-a@yyyyyyyyyyyy.iam.gserviceaccount.com] does not have permission to access project [yyyyyyyyyyyy] (or it may not exist): Permission 'secretmanager.locations.list' denied for resource 'projects/yyyyyyyyyyyy' (or it may not exist).
```

# その他

[Berglas](https://github.com/GoogleCloudPlatform/berglas)はSecret Managerにも対応しているようです。

Secret管理用のCLIとしても、アプリケーション用のインテグレーション（ライブラリや環境変数管理）としても利用できそうなので随時追記します。

# 参考

* [Secret Manager](https://cloud.google.com/secret-manager)
* [Cloud Key Management Service](https://cloud.google.com/kms)
* [秘密情報管理全般](https://cloud.google.com/solutions/secrets-management)
* [gcloud secrets](https://cloud.google.com/sdk/gcloud/reference/secrets)
* [利用できるロール](https://cloud.google.com/secret-manager/docs/access-control)
* [Improving your security posture with centralized secrets management](https://cloud.google.com/blog/products/identity-security/improving-your-security-posture-with-centralized-secrets-management)
* [GoogleCloudPlatform/berglas](https://github.com/GoogleCloudPlatform/berglas)
* [HashiCorp Vault](https://www.vaultproject.io/)