EKSにHashiCorp Vault with ConsulをHA構成でデプロイしてSecret管理させてみる

# はじめに
みなさん、KubernetesのSecretはどのように管理されていますか？
よく聞くOSSツールといえば、Kubesec、Sealed Secrets、External Secretsなどがあります。
それらとAWSのSecrets Manager等のクラウドマネージドサービスを連携して使う方法もありますね。
今回は、データストレージに[HashiCorp Consul](https://www.consul.io/)を使用する[HashiCorp Vaultクラスタ](https://www.vaultproject.io/)をEKS上に作成して、[hashicorp/vault-k8s](https://hub.docker.com/r/hashicorp/vault-k8s)を用いたサイドカー構成でSecret管理をさせてみたいと思います。
また、VaultはOSS版であってもWeb UIが提供されているのでそちらも合わせて構築したいと思います。

## やりたいこと

- UIで秘匿情報を管理できる
- アプリケーションのデプロイ時にその秘匿情報を自動で埋め込んで欲しい
- 秘匿情報はひとつのVaultサーバに登録するだけで、複数クラスタ間で共有できる
    - 今回はこれは確認しません

## 注意事項
- EKSの構築については触れません
- 構築の際はIAMの知識が必要です
- ConsulとVaultの原理や仕様は本筋から逸れるため本記事では説明しません
- Kubernetesクラスタ上にどのように構築するかということに重きを置いています
- 本文が非常に長くなってしまいました

## 使用した環境
- Amazon EKS
    - Clusterバージョン 1.17
- HashiCorp Vault v1.6.1
- HashiCorp Consul v1.9.1

# 基本方針
Vaultの公式ドキュメント[Vault on Kubernetes Deployment Guide](https://learn.hashicorp.com/tutorials/vault/kubernetes-raft-deployment-guide?in=vault/kubernetes)にあるHelmチャートを用いる方法でデプロイを行います。

## 参考アーキテクチャ
Vaultの公式ドキュメント[Vault on Kubernetes Reference Architecture](https://learn.hashicorp.com/tutorials/vault/kubernetes-reference-architecture?in=vault/kubernetes)で紹介されているアーキテクチャの参考を簡単にまとめます。

- VaultのストレージとしてConsulを使用する
    - つまり、Vault Server PodとConsul Server Podが必要
    - また、Consul Client Podをデプロイし、Vault Server Podはそれを介してConsul Serverを検索する
    - 運用簡素化のため、Consul Client PodはDaemonSetでデプロイすることが望ましい
- 可能であれば、Vault/Consul専用のクラスタを作成する
    - セキュリティ担保や高可用性のため
    - 難しい場合であっても(マルチテナントクラスタ上にデプロイする場合であっても)、専用のノードを作成するのが望ましい
- PersistentVolumesおよびPersistentVolumeClaimsを使用したストレージ永続化
- ノード要件
    - Consul
        - 2-4 CPU / 8-16 GB RAM / 50GB Disk / e.g. m5.large, m5.xlarge
        - 8-16 CPU / 32-64 GB RAM / 100GB Disk / e.g. m5.2xlarge, m5.4xlarge
    - Vault
        - 2 CPU / 4-8 GB RAM / 25GB Disk / e.g. m5.large
        - 4-8 CPU / 16-32GB RAM / 50GB Disk / e.g. m5.xlarge, m5.2xlarge
- Consul Server PodとVault Server Podはアベイラビリティゾーン間で均質に分散すること
    - 5つのConsul Server Pod (StatefulSet)

![Nodes-with-Pods.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/c5ec4832-8166-0a3d-8db0-c02477c5aecc.png)
(画像引用：https://learn.hashicorp.com/tutorials/vault/kubernetes-reference-architecture?in=vault/kubernetes#infrastructure-design)

## 今回のアーキテクチャ
今回は複数のVault Server Pod (StatefulSet)によるHA構成、Consulを用いたデータ永続化、Web UI有効化を行います。
データ永続化については、他にも[様々なストレージの選択肢](https://www.vaultproject.io/docs/configuration/storage)があるので必要に応じて使い分けてください。
また、VaultとConsulのそれぞれのWeb管理画面へのServiceとしてInternal NLBを自動生成するように設定します。
最終的に同一クラスタ内のアプリケーションにVaultを介して秘匿情報を埋め込むことにチャレンジします。
以下はイメージ図です(色々省いてます)。

![スクリーンショット 2021-01-14 11.34.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/73381f2a-b99d-b077-6088-ec0bb0e32821.png)

# Vaultのデータ永続化用のConsulをデプロイする
[Consulの公式ドキュメント](https://learn.hashicorp.com/tutorials/consul/kubernetes-eks-aws?utm_source=consul.io&utm_medium=docs)を参考にしながらデプロイを行います。

## Helmリポジトリのセットアップ

### Helmリポジトリの追加
公式Helmリポジトリが提供されているのでそれを追加します。
追加したらチャートを検索しましょう。
今回は記事を書いている時点で最新のチャートバージョン0.28.0を使用することにします。

```bash
$ helm repo add hashicorp https://helm.releases.hashicorp.com

$ helm search repo hashicorp/consul
NAME                    CHART VERSION   APP VERSION     DESCRIPTION                    
hashicorp/consul        0.28.0          1.9.1           Official HashiCorp Consul Chart
```

### Helmチャートの内容確認
リポジトリを追加したらチャートの中身を確認してみましょう。
helm fetchでチャートをダウンロードしてhelm templateで内容を標準出力に出します。
今回は標準出力をlessにパイプで渡しています。

```bash
$ helm fetch hashicorp/consul --version 0.28.0

$ helm template consul-0.28.0.tgz | less
```

中身を確認すると、大まかにServiceAccount系リソース、ConfigMap系リソース、Service系リソース、Pod系リソース、PodDisruptionBudgetリソースを確認できます。

- Pod系リソース
    - Consul Server PodとしてのStatefulSet
    - Consul Client PodとしてのDaemonSet
    - テスト用のConsul Pod
- Service系リソース
    - Consul Server PodへのClusterIP
        - Web UI用
    - Consul Server PodへのHeadless Service
    - Consul Server PodおよびConsul Client Podへの53番ポートClusterIP
- ServiceAccount系リソース
    - Consul Server用のServiceAccount (serverと呼ぶことにする)
    - ServiceAccount server用のRoleとそれを付与するRoleBinding
    - Consul Client用のServiceAccount (clientと呼ぶことにする)
    - ServiceAccount client用のRoleとそれを付与するRoleBinding
- ConfigMap系リソース
    - Consul Server Pod用のConfigMap
    - Consul Client Pod用のConfigMap
- PodDisruptionBudget
    - `app: consul`ラベルが付与されたPodに対してのPodDisruptionBudget

## values.ymlの更新

### ファイル生成
変更可能なパラメータ確認のためオリジナルのvalues.ymlを生成しておきます。

```bash
$ helm show values hashicorp/consul --version 0.28.0 > values-origin.yml
```

### 更新内容
今回は必要なディレクティブのみ抜き出してyamlを作成します。
先に生成したvalues-origin.ymlを更新してもOKです。

- `global.datacenter`: Consul datacenterとしての名前 (好きに指定)
- `server.storageClass`: EBSのStorageClassを指定 (後述)
- `ui.service`: Web UI用の内部NLBを生成するように指定
- `syncCatalog.enabled`: Kubernetesのリソースを同期できるようにtrueを指定

```yaml:values.yml
global:
  datacenter: booklive-dc1
server:
  storageClass: gp2
ui:
  service:
    type: 'LoadBalancer'
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-type: nlb
      service.beta.kubernetes.io/aws-load-balancer-internal: "true"
syncCatalog:
  enabled: true
```

## デプロイ

### EBSのPersistentVolumeClaimはDynamic Provisioningで生成する
公式Helmチャートを使用してConsulをKubernetesにデプロイするには、Dynamic Provisioningが可能なStorageClassがクラスタにデプロイされている必要があります。
2021年1月現在、EFSはDynamic Provisioningに対応していませんので、今回はEBSを利用したいと思います。
EBSのDynamic Provisioningはデフォルトでデプロイされている`gp2`StorageClassを使用すれば可能です。

Consul Server Podの実際のmanifestを覗いてどのようにPersistentVolumeをコンテナにマウントするのかを念のため確認しておきます。
先ほどfetch済みのmanifestで確認します。
次のコマンドを実行後、`kind: StatefulSet`の`metadata.name: RELEASE-NAME-consul-server`を探してください。

```bash
$ helm template consul-0.28.0.tgz | less
```

すると、そのmanifest内に次のような内容が見つかると同時に、外部PersistentVolumeClaimのマウントは見つからないことが分かります。

```yaml
  volumeClaimTemplates:
    - metadata:
        name: data-default
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
```

つまり、Consul Server PodはDynamic Provisioningを利用して動的にPersistentVolumeClaimを作成してくれるので、別途作成する必要がないということです。

### Consulのデプロイ
先に紹介したvalues.ymlを使用してデプロイします。

```bash
$ helm install -f values.yml consul hashicorp/consul --version 0.28.0
NAME: consul
LAST DEPLOYED: Thu Jan 14 11:38:22 2021
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Consul!

Now that you have deployed Consul, you should look over the docs on using
Consul with Kubernetes available here:

https://www.consul.io/docs/platform/k8s/index.html


Your release is named consul.

To learn more about the release if you are using Helm 2, run:

  $ helm status consul
  $ helm get consul

To learn more about the release if you are using Helm 3, run:

  $ helm status consul
  $ helm get all consul
```

`default` namespaceにconsul用の`persistentvolumeclaims`と`persistentvolumes`が生成され(少し時間がかかります)、`consul-consul-...`というPodが正常にRunningしていれば問題なくデプロイできています。

# Vaultをデプロイする

## 1. 名前空間の作成
default名前空間を使用するのは推奨されませんので、新規に作成します。
今回はvaultという名前空間を作成して、Vaultリソースは全てこの名前空間にapplyします。

```yaml:namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: vault
```

```bash
$ kubectl apply -f namespace.yml
```

## 2. Helmリポジトリのセットアップ

### Helmリポジトリの追加
公式Helmリポジトリが提供されているのでそれを追加します。
追加したらチャートを検索しましょう。
今回は記事を書いている時点で最新のチャートバージョン0.9.0を使用することにします。

```bash
$ helm repo add hashicorp https://helm.releases.hashicorp.com

$ helm search repo hashicorp/vault
NAME            CHART VERSION   APP VERSION     DESCRIPTION                               
hashicorp/vault 0.9.0           1.6.1           Official HashiCorp Vault Chart            
```

### Helmチャートの内容確認
リポジトリを追加したらチャートの中身を確認してみましょう。
`helm fetch`でチャートをダウンロードして`helm template`で内容を標準出力に出します。
今回は標準出力をlessにパイプで渡しています。

```bash
$ helm fetch hashicorp/vault --version 0.9.0

$ helm template vault-0.9.0.tgz | less
```

中身を確認すると、大まかにServiceAccount系リソース、Service系リソース、Pod系リソース、MutatingWebhookConfigurationリソースを確認できます。

- Pod系リソース
    - Vault Server PodとしてのStatefulSet
    - vault-agent-injector Pod ([hashicorp/vault-k8s](https://hub.docker.com/r/hashicorp/vault-k8s))としてのDeployment
- Service系リソース
    - Vault Server PodへのClusterIP
    - Vault Server PodへのHeadless Service
    - vault-agent-injectorへのClusterIP (vault-agent-injector-svc)
- MutatingWebhookConfigurationリソース
    - vault-agent-injector-svcへのMutating Admission Webhook
        - Pod等のリソース作成時・更新時にvault-agent-injectorが呼ばれるようになる
        - vault-agent-injectorが特定の`spec.template.metadata.annotations`に反応できるようになる
- ServiceAccount系リソース
    - Vault Server Pod用のServiceAccount (vaultと呼ぶことにする)
    - ServiceAccount vaultに`system:auth-delegator`のClusterRoleを割り当てるClusterRoleBinding
    - MutatingWebhookConfigurationのget/list/watch/patchができるClusterRole (injector-roleと呼ぶことにする)
    - vault-agent-injector用のServiceAccount (injectorと呼ぶことにする)
    - ServiceAccount injectorにClusterRole injector-roleを割り当てるClusterRoleBinding

### values.ymlの生成
パラメータはなるべくコード化しておきたいので、この時点でvalues.ymlを生成しておきます。
ただし、600行近くあります。

```bash
$ helm show values hashicorp/vault --version 0.9.0 > values.yml
```

### values.ymlの更新
公式HelmチャートではありますがデフォルトではHA構成になっていなかったりするので、設定を書き換えていきます。
各ディレクティブの説明は[公式ドキュメント](https://www.vaultproject.io/docs/platform/k8s/helm/configuration)を参照してください。

#### HAモードの有効化
HAモードはデフォルトで無効になっています。
HAモードを有効化するとVault Server Pod (StatefulSet)はreplicasに指定した数だけデプロイされます。
`ha:`で検索したらすぐに見つかります。

```yaml:Before
server:
  ha:
    enabled: false
    replicas: 3
```

```yaml:After
server:
  ha:
    enabled: true
    replicas: 3
```

#### Vault Server Podのリソース制限
injector (vault-k8s)とVault本体のリソース制限をそれぞれできます。
本番で稼働させるには、参考アーキテクチャで紹介されているスペックが必要になるかもしれません。
今回は特に指定しません。

```yaml:Default
injector:
  resources: {}
  # resources:
  #   requests:
  #     memory: 256Mi
  #     cpu: 250m
  #   limits:
  #     memory: 256Mi
  #     cpu: 250m
server:
  resources: {}
  # resources:
  #   requests:
  #     memory: 256Mi
  #     cpu: 250m
  #   limits:
  #     memory: 256Mi
  #     cpu: 250m
```

#### StatefulSetとしてのデータ永続化
今回はVaultのデータのみ永続化するので、`server.dataStorage.enabled`がtrueになっていることを確認します。

```yaml
server:
  dataStorage:
    enabled: true
  auditStorage:
    enabled: false
```

また、HA構成にする場合は、ストレージスタンザの設定を必要に応じて書き変えます。
デフォルトではConsulが有効になっています。
`HOST_IP`の箇所だけ、ConsulServerへ名前解決できるService名に書き換えておきます。

```yaml
server:
  ha:
    config: |
      ui = true

      listener "tcp" {
        tls_disable = 1
        address = "[::]:8200"
        cluster_address = "[::]:8201"
      }
      storage "consul" {
        path = "vault"
        address = "consul-consul-server.default.svc.cluster.local:8500"
      }

      service_registration "kubernetes" {}
```

#### Auto Unseal
今回はこの設定は行いませんが、AWSの場合、credentialsとKMSを使用してVault ServerのAuto Unsealを実現できます。
紹介までに方法を記載しておきます。
まず、credentialsをvalues.ymlに渡すためのsecretを作成します。
このとき、次のIAMポリシー権限を持つIAMユーザーのキーを使用するようにしてください。

- `kms:Encrypt`
- `kms:Decrypt`
- `kms:DescribeKey`

```bash
$ kubectl -n vault create secret generic vault-aws-key \
> --from-literal=AWS_ACCESS_KEY_ID=AAAAAAAAAAAAAAAA \
> --from-literal=AWS_SECRET_ACCESS_KEY=BBBBBBBBBBBBBBBBB
```

作成が終えたら、secretをvalues.ymlから読み込むように設定します。
`seal "awskms"`内の`kms_key_id`にはAWS KMSのKey IDを調べて記載します。

```yaml
server:
  extraSecretEnvironmentVars:
  - envName: AWS_ACCESS_KEY_ID
    secretName: vault-aws-key
    secretKey: AWS_ACCESS_KEY_ID
  - envName: AWS_SECRET_ACCESS_KEY
    secretName: vault-aws-key
    secretKey: AWS_SECRET_ACCESS_KEY

  ha:
    config: |
      seal "awskms" {
          region = "ap-northeast-1"
          kms_key_id = "aaa-bbb-ccc-ddd-eee-111"
      }
```

#### Web UIの有効化
デフォルトではWeb UIは無効化されているので有効化し、Vault Server Podへの疎通のためにLoadBalancer Serviceを作成するように書き換えます。
EKSを使用している場合、`type: LoadBalancer`を指定すればCLBまたはNLBを自動生成できるので、それでリソースの作成を行います。
今回は内部NLBを作成します。

```yaml
ui:
  enabled: true
  publishNotReadyAddresses: true
  activeVaultPodOnly: true
  serviceType: "LoadBalancer"
  serviceNodePort: null
  externalPort: 80

  # loadBalancerSourceRanges:
  #   - 10.0.0.0/16
  #   - 1.78.23.3/32

  # loadBalancerIP:

  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"

```

## 3. デプロイ
設定したvalues.ymlを使用してhelm installを実行します。

```bash
$ helm install vault hashicorp/vault --namespace vault -f values.yml --version 0.9.0
NAME: vault
LAST DEPLOYED: Thu Jan 14 12:27:55 2021
NAMESPACE: vault
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://www.vaultproject.io/docs/


Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault
```

StatefulSetのreplicasを3に設定したので、vault-{number}が3つとvault-agent-injectorという合計4つのPodが作成されていれば問題ありません。

```bash
$ kubectl -n vault get pod
NAME                                   READY   STATUS    RESTARTS   AGE
vault-0                                0/1     Running   0          12s
vault-1                                0/1     Running   0          12s
vault-2                                0/1     Running   0          12s
vault-agent-injector-b55d65869-mlxtt   1/1     Running   0          12s
```

Web UIに疎通可能なELB (NLB)が生成されていることも確認できます。

```bash
$ kubectl -n vault get service vault-ui
NAME       TYPE           CLUSTER-IP      EXTERNAL-IP                                                                          PORT(S)          AGE
vault-ui   LoadBalancer   172.20.223.44   aaaaabbbbb-cccccddddd.elb.ap-northeast-1.amazonaws.com   8200:32040/TCP   5m10s
```

## 4. Vaultの初期設定
HA構成の場合、コマンドラインでVaultをUnsealにする必要があります。
`kubectl exec`でVault Server Podのどれかひとつにinitコマンドを実行します。
このとき、Unseal KeyとInitial Tokenを取得します(以降も大切な情報ですので安全な場所で保管します)。

```bash
$ kubectl -n vault exec -it vault-0 -- vault operator init
Unseal Key 1: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Unseal Key 2: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Unseal Key 3: cccccccccccccccccccccccccccccccccccccccccccc
Unseal Key 4: dddddddddddddddddddddddddddddddddddddddddddd
Unseal Key 5: eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee

Initial Root Token: s.fffffffffffffffffffffff

Success! Vault is initialized

Recovery key initialized with 5 key shares and a key threshold of 3. Please
securely distribute the key shares printed above.
```

AWS KMSによるAuto Unsealを有効にしていれば、Unseal Keyを入力せずにこのタイミングで使える状態になります。
ただし、今回はマニュアルでUnsealを行います。
次のコマンドをすると対話形式でUnseal Keyを聞かれるので、`operator init`で取得した5つのUnseal Keyのうち任意のひとつを入力します。

```bash
$ kubectl -n vault exec -it vault-0 -- vault operator unseal
Unseal Key (will be hidden):
```

これを全部で3回繰り返すとVaultはUnseal状態になり使用可能になります。
3回目にSealed Keyがfalseになったことを確認できればOKです。
※ 3回入力するUnseal Keyは全て別のものを使用してください

```bash
$ kubectl -n vault exec -it vault-0 -- vault operator unseal
Unseal Key (will be hidden):
Key                    Value
---                    -----
Seal Type              shamir
Initialized            true
Sealed                 false
Total Shares           5
Threshold              3
Version                1.6.1
Storage Type           consul
Cluster Name           vault-cluster-****
Cluster ID             
HA Enabled             true
HA Cluster             n/a
HA Mode                standby
Active Node Address    <none>

$ kubectl -n vault get pod                                
NAME                                    READY   STATUS    RESTARTS   AGE
vault-0                                 1/1     Running   0          2m38s
vault-1                                 1/1     Running   0          2m38s
vault-2                                 1/1     Running   0          2m38s
vault-agent-injector-6fcf464c66-9k8ch   1/1     Running   0          2m38s
```

ここまで終えると`NLBのDNS名`にブラウザからアクセスしてみましょう。
するとログイン画面が表示されるので、`kubectl exec`で取得した`Initial Root Token`を入力し、Sign Inを実行します。

![スクリーンショット 2021-01-14 18.50.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/2f547fb4-8707-7288-5b98-dfb7c4c15860.png)

実行後、次のような画面が表示されれば初期設定完了です。

![スクリーンショット 2021-01-14 18.52.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/090900d9-02eb-142f-1ca4-47bb68a5a8ac.png)

## 5. ロール/認証情報の設定

### ログイン認証
Vaultの長所は細かく権限管理できるところです。
これは管理画面のログイン認証においても例外ではなく、ログインユーザーが持つポリシーによって可能な作業が制御されます。
先ほど初期設定のためにRoot Tokenを使用しましたが、このTokenにはrootポリシーが付与されていて何でもできるため、安全に管理する必要があります。
そのため、必要な権限に応じてポリシーを作成していく必要があります。
デフォルトではrootポリシーとdefaultポリシーがありますが、rootポリシーは使い回すことができず、一方でdefaultポリシーはできることが少な過ぎます。
そこで、今回は強めの権限を持ったadminというポリシーを作成しておきます。
まず、`Top > Policies`に遷移して`Create ACL policy`をクリックします。
Nameに`admin`を入力し、Policyに次の設定をコピーし、`Create policy`を実行します。

```
# Configure auth methods
path "sys/auth" {
  capabilities = [ "read", "list" ]
}

# Configure auth methods
path "sys/auth/*" {
  capabilities = [ "create", "update", "read", "delete", "list", "sudo" ]
}

# Manage auth methods
path "auth/*" {
  capabilities = [ "create", "update", "read", "delete", "list", "sudo" ]
}

# Display the Policies tab in UI
path "sys/policies" {
  capabilities = [ "read", "list" ]
}

# Create and manage ACL policies from UI
path "sys/policies/acl/*" {
  capabilities = [ "create", "read", "update", "delete", "list" ]
}

# Create and manage policies
path "sys/policies/acl" {
  capabilities = [ "read", "list" ]
}

# Create and manage policies
path "sys/policies/acl/*" {
  capabilities = [ "create", "read", "update", "delete", "list" ]
}

# List available secrets engines to retrieve accessor ID
path "sys/mounts" {
  capabilities = ["read"]
}

# Create and manage entities and groups
path "identity/*" {
  capabilities = [ "create", "read", "update", "delete", "list" ]
}

# List, create, update, and delete key/value secrets
path "kv/*" {
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# Manage secrets engine
path "sys/mounts/*" {
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
```

次にユーザー・パスワード認証を有効化します。
`Top > Access > Auth Methods > Enable new method`を押します。
今回は`Username & Password`を設定してみます。

![スクリーンショット 2021-01-14 18.54.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/070225f5-0db4-08b6-823a-007ad38e98f9.png)

Nextを押すと詳細設定ができますが今回はデフォルトのままEnable Methodを押します。

![スクリーンショット 2021-01-14 18.54.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/90dd5b9c-4727-31ff-54d8-52ac2de368ee.png)

Methodの作成が完了したら、次の画面に移動してCreate userを押します。
ユーザー作成画面では、今回は`Username=admin,Password=adminpassword`を入力し、隠れている詳細設定のアコーディオンを開くためにTokensをクリックします。

![スクリーンショット 2021-01-14 18.55.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/d9505e8c-b309-4240-974e-5b34e4dea942.png)

Generated Token's Policiesに先ほど作成したポリシーの`admin`を入力し`Add`をクリックします。
ここまで完了したらSaveをクリックしてユーザー作成完了です。

![スクリーンショット 2021-01-14 18.56.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/53e04b8c-a0e3-4dd1-55f4-568de22e2409.png)

以後、adminユーザーで作業を行うため、一度ログアウトしてログインし直します。

![スクリーンショット 2021-01-14 18.57.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/33e6e666-3eaf-7e1b-877d-45f3cc7be777.png)

### 読み取り専用アプリケーションポリシー
アプリケーションからはSecretの読み取りのみ許可したいことがほとんどだと思われます。
そのため、Read Onlyなポリシーも作成しておきます。
今回は、`readonly-secret`という名前で登録しました。

```
path "kv/*" {
  capabilities = ["read"]
}
```

### Kubernetes認証メソッドの有効化とロールの作成
KubernetesのServiceAccountとVaultのポリシーを関連づけるための設定です。
まず、`Access > Auth Methods`からEnable new methodを押します。
`Infra > Kubernetes`を選択し`Next`を押します。
デフォルトのままEnable Methodを押し、Kubernetes Authentication Methodを有効化します。
有効化が終わったら、Configure Kubernetesという画面に遷移するので必要情報を入力します。

![スクリーンショット 2021-01-14 18.59.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/6d76b2da-1e17-3a2a-0323-7e7171b7ee06.png)

それぞれのキーに対して、次のコマンドを実行して得られた値を入力します。

- **Kubernetes host**
    - ここで得られるIPを`$IP`とすると`https://$IP:443`の形式で入力してください

```bash
$ kubectl -n vault exec -it vault-0 -- sh -c 'echo $KUBERNETES_PORT_443_TCP_ADDR'
```

- **Kubernetes CA Certificate**
    - 入力時は`Enter as text`を有効にしてから入力してください

```bash
$ kubectl -n vault exec -it vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
```

- **Token Reviewer JWT**
    - このJWTはVault起動時に作成されるvaultというServiceAccountに紐づくSecretとしてデプロイされています

```bash
$ kubectl -n vault exec -it vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
```

入力が済んだらSaveを押して保存します。
ここまで終えたら、次の画面まで移動して`Create role`を押します。

![スクリーンショット 2021-01-14 19.01.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/feecdb73-f815-6bf7-2dc7-25b93ca95fb0.png)

今回は、sample-app-roleという名前で全てのServiceAccountと全てのnamespaceを許可します。
また、Generated Token's Policiesには`readonly-secret`ポリシーを追加しておきます。

![スクリーンショット 2021-01-14 19.02.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/772316a8-c683-24a5-1250-e1d1199106bc.png)


## 6. Secret情報の登録

今回はKey-Value形式のSecretを有効にします。
まずは、`Top > Secrets > Enable new engine > Generic/KV > Next > (特に何もせずに) Enable Engine`を実行します。
すると、Key-Value形式のSecretを登録できるようになるので、Create secretからSecretを登録します。

![スクリーンショット 2021-01-14 13.21.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/a4b2a604-2049-ffdf-00b3-3e52c3946230.png)

今回は、`secret/test-secret`というSecretに`Key=username,Value=testuser`と`Key=password,Value=password`を登録してみます。
`readonly-secret`のポリシーに`kv/*`のreadを許可しているのは、今作成した`kv`配下の読み込み権限を与えるためです。

![スクリーンショット 2021-01-14 12.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/73ff7cd1-12a3-5d7a-961f-f17bf9f2bbfa.png)

# コンシューマアプリケーションからSecret情報を取得してみる

## サンプルアプリケーション構築
サンプルアプリケーションのマニフェストは次の通りです。
Nginxコンテナの `/vault/secrets/test-secret`というファイルに秘匿情報が埋め込まれます。

```yaml:sample-app.yml
---
apiVersion: v1
kind: Namespace
metadata:
  name: sample-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: sample-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
      annotations:
        # Enables the Vault Agent Injector service
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/agent-inject-status: update
        # Vault Kubernetes authentication role
        vault.hashicorp.com/role: sample-app-role
        # agent-inject-secret-FILEPATH prefixes the path of the file, written to the /vault/secrets directory.
        # The value is the path to the secret defined in Vault.
        vault.hashicorp.com/agent-inject-secret-test-secret: kv/secret/test-secret
        vault.hashicorp.com/agent-inject-template-test-secret: |
          {{- with secret "kv/secret/test-secret" -}}
          USERNAME={{ .Data.data.username }}
          PASSWORD={{ .Data.data.password }}
          {{- end -}}
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.19
```

## デプロイと確認
ファイルを作成したらデプロイします。
デプロイ後に`READY2/2`と`STATUS`が`Running`になっていればOKです。

```bash
$ kubectl apply -f sample-app.yml

$ kubectl -n sample-app get pod
NAME                          READY   STATUS    RESTARTS   AGE
sample-app-6f4f9b46fd-l8wgd   2/2     Running   1          60s
```

なぜ2/2かというとvault-agentコンテナが埋め込まれているためです。
sample-app.ymlに`vault.hashicorp.com/agent-inject: "true"`アノテーションがあるので、Vaultクラスタと一緒にデプロイしておいたvault-agent-injectorによってMutating Admission Webhookが発火し、Vaultと通信できるAgentがsample-app Podのサイドカーとして埋め込まれる仕組みです。

最終的にNginxコンテナにパスワードが埋め込まれていることを次のように確認できます。

```bash
$ kubectl -n sample-app exec -it sample-app-6f4f9b46fd-l8wgd -c nginx-container -- cat /vault/secrets/test-secret
USERNAME=testuser
PASSWORD=password
```

ローカル開発でも同じ場所に同じ形式でファイルを置いておけば、アプリケーションに秘匿情報を埋め込まず、かつビルド時に秘匿情報を渡すこともなく、.envのような使い方で実装を進められます。

# まとめ
やりたかったことのうち次の2つを確認できました。

- UIで秘匿情報を管理できる
- アプリケーションのデプロイ時にその秘匿情報を自動で埋め込んで欲しい

3つ目の複数クラスタでVaultの情報を共有するためにはVault専用クラスタを作成するのが一番いいと考えられます。
その場合、Vault Helmのvalues.ymlにて「Vault Server Podは起動せず、外部Vault Serverを使用する」というような設定が可能なので、それを利用します。
そうすると、Ingector Agentのみを起動することができます。

そして、良くも悪くも柔軟な権限管理や様々な秘匿情報の管理に対応しているので、様々なケースに対応できると思います。
高機能であるが故に覚えないといけないことも多いというのも事実です。
まだまだ分かっていない点も多いので本番運用に向けて色々調べていく所存です。

### おまけ

ついでに、データストレージとして使用しているConsulサーバにアクセスするとKey/Valueの項目にvaultのデータが格納されていることも確認できます。

![スクリーンショット 2021-01-14 19.03.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555899/7ed210d3-eefe-c6a6-bf55-20dcebd1ddba.png)

# 更新情報

- 2021/01/15
    - 説明不足の箇所や文脈がおかしいところの文章を修正しています
    - 構築内容に変更はありません