Help us understand the problem. What is going on with this article?

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

はじめに

みなさん、KubernetesのSecretはどのように管理されていますか?
よく聞くOSSツールといえば、Kubesec、Sealed Secrets、External Secretsなどがあります。
それらとAWSのSecrets Manager等のクラウドマネージドサービスを連携して使う方法もありますね。
今回は、データストレージにHashiCorp Consulを使用するHashiCorp VaultクラスタをEKS上に作成して、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にあるHelmチャートを用いる方法でデプロイを行います。

参考アーキテクチャ

Vaultの公式ドキュメントVault on Kubernetes Reference Architectureで紹介されているアーキテクチャの参考を簡単にまとめます。

  • 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://learn.hashicorp.com/tutorials/vault/kubernetes-reference-architecture?in=vault/kubernetes#infrastructure-design)

今回のアーキテクチャ

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

スクリーンショット 2021-01-14 11.34.50.png

Vaultのデータ永続化用のConsulをデプロイする

Consulの公式ドキュメントを参考にしながらデプロイを行います。

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

Helmリポジトリの追加

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

$ 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にパイプで渡しています。

$ 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を生成しておきます。

$ 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を指定
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はデフォルトでデプロイされているgp2StorageClassを使用すれば可能です。

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

$ helm template consul-0.28.0.tgz | less

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

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

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

Consulのデプロイ

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

$ 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用のpersistentvolumeclaimspersistentvolumesが生成され(少し時間がかかります)、consul-consul-...というPodが正常にRunningしていれば問題なくデプロイできています。

Vaultをデプロイする

1. 名前空間の作成

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

namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: vault
$ kubectl apply -f namespace.yml

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

Helmリポジトリの追加

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

$ 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にパイプで渡しています。

$ 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)としての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行近くあります。

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

values.ymlの更新

公式HelmチャートではありますがデフォルトではHA構成になっていなかったりするので、設定を書き換えていきます。
各ディレクティブの説明は公式ドキュメントを参照してください。

HAモードの有効化

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

Before
server:
  ha:
    enabled: false
    replicas: 3
After
server:
  ha:
    enabled: true
    replicas: 3

Vault Server Podのリソース制限

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

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になっていることを確認します。

server:
  dataStorage:
    enabled: true
  auditStorage:
    enabled: false

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

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
$ 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を調べて記載します。

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を作成します。

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を実行します。

$ 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が作成されていれば問題ありません。

$ 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)が生成されていることも確認できます。

$ 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を取得します(以降も大切な情報ですので安全な場所で保管します)。

$ 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のうち任意のひとつを入力します。

$ 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は全て別のものを使用してください

$ 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

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

スクリーンショット 2021-01-14 18.52.01.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

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

スクリーンショット 2021-01-14 18.54.40.png

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

スクリーンショット 2021-01-14 18.55.17.png

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

スクリーンショット 2021-01-14 18.56.26.png

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

スクリーンショット 2021-01-14 18.57.29.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

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

  • Kubernetes host
    • ここで得られるIPを$IPとするとhttps://$IP:443の形式で入力してください
$ kubectl -n vault exec -it vault-0 -- sh -c 'echo $KUBERNETES_PORT_443_TCP_ADDR'
  • Kubernetes CA Certificate
    • 入力時はEnter as textを有効にしてから入力してください
$ kubectl -n vault exec -it vault-0 -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
  • Token Reviewer JWT
    • このJWTはVault起動時に作成されるvaultというServiceAccountに紐づくSecretとしてデプロイされています
$ 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

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

スクリーンショット 2021-01-14 19.02.33.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

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

スクリーンショット 2021-01-14 12.53.03.png

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

サンプルアプリケーション構築

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

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/2STATUSRunningになっていればOKです。

$ 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コンテナにパスワードが埋め込まれていることを次のように確認できます。

$ 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

更新情報

  • 2021/01/15
    • 説明不足の箇所や文脈がおかしいところの文章を修正しています
    • 構築内容に変更はありません
ttksm
主にバックエンド開発、AWSが守備範囲のエンジニアです。
https://booklive.jp
booklive
株式会社BookLiveは書籍、マンガ、雑誌、写真集等の人気作品を取り扱う総合電子書籍ストアを運営しています。
https://booklive.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away