概要
Kubernetes環境において、CI/CDによる自動化を行おうと試みた時など、パスワードやアクセストークンなどの機密情報をどうやって保存しようかを悩んだことがある人は多いのではないでしょうか。パスワードを書いたManifestをGitHubなどにPushするのは、抵抗があるかと思います。今回はHashiCorp社が提供している機密情報を管理するためのソフトウェアVaultに格納された機密情報をKuberntes上のストレージとしてマウントするsecrets-store-csi-driverの動作検証を行います。
なお、secrets-store-csi-driverはKubeCon 2019 EUにてHashiCorp社とMS社のメンバによって発表されています。その時の資料はこちらです。
検証環境
- Kubernetes v1.14.1
- secrets-store-csi-driver v0.0.3
事前準備
事前準備として、動作検証で利用するVaultのサーバを立ち上げます。
ダウンロード
GitHubのリポジトリからsecrets-store-csi-driverをダウンロードします。
$ git clone git@github.com:deislabs/secrets-store-csi-driver.git
$ cd secrets-store-csi-driver
Vaultコマンドもダウンロードサイトからダウンロードし、パスの通ったディレクトリにコピーします。
検証用Vaultのデプロイ
次に、検証用に利用するVaultをKubernetes上にデプロイします。
このVaultは、データを永続していないため、Podが削除されるとともに格納したデータは全て削除されます。あくまで検証用として利用してください。
$ kubectl apply -f pkg/providers/vault/examples/vault.yaml
デプロイすると、以下のようにVaultのPodが起動します。
$ kubectl get pod -l app=vault
NAME READY STATUS RESTARTS AGE
vault-f585568d9-9sbp2 1/1 Running 1 3m17s
$ kubectl get service -l app=vault
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
vault NodePort 10.101.58.66 <none> 8200:31899/TCP 67m
VaultをKubernetesクラスタの外部からアクセスできるようにport forwardを行います。
$ kubectl port-forward vault-f585568d9-9sbp2 8200:8200 &
実際に、Vaultにアクセスし、起動していることを確認を行います。
$ export VAULT_ADDR="http://127.0.0.1:8200"
$ export VAULT_TOKEN="root"
i$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.1.1
Cluster Name vault-cluster-692252b2
Cluster ID 60c527f1-2e28-70c5-88fc-55afcc01ec43
HA Enabled false
これで検証に利用するVault(サーバ)のセットアップが終わりました。
動作検証
VaultにKubernetesからのアクセスできるように認証設定したのち、secrets-store-csi-driverをデプロイしていきます。なお、今回の検証では、Kubernetesのdefault
ネームスペースを利用します。
Kubernetesでのサービスアカウントの作成
まず、Kubernetes上にVaultへのアクセス用のサービスアカウントを作成します。
$ kubectl create serviceaccount vault-auth
$ kubectl apply -f - <<EOH
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: default
EOH
VaultにKubernetesの認証情報を設定
次に、Vaultに、Kubernetesの認証情報を設定します。
まず初めに、必要な証明書やトークンなどの情報をKubernetesの各種リソースから取得します。
$ CLUSTER_NAME="$(kubectl config view -o json | jq -r .clusters[].name)"
$ SECRET_NAME="$(kubectl get serviceaccount vault-auth \
-o go-template='{{ (index .secrets 0).name }}')"
$ TR_ACCOUNT_TOKEN="$(kubectl get secret ${SECRET_NAME} \
-o go-template='{{ .data.token }}' | base64 --decode)"
$ K8S_HOST="$(kubectl config view --raw \
-o go-template="{{ range .clusters }}{{ if eq .name \"${CLUSTER_NAME}\" }}{{ index .cluster \"server\" }}{{ end }}{{ end }}")"
$ K8S_CACERT="$(kubectl config view --raw \
-o go-template="{{ range .clusters }}{{ if eq .name \"${CLUSTER_NAME}\" }}{{ index .cluster \"certificate-authority-data\" }}{{ end }}{{ end }}" | base64 --decode)"
なお、幾つかの値は、KuberntesのConfig情報から取得しています。
Config情報に複数のKubernetesクラスタが登録している場合は、正しいKuberntesクラスタの情報が取得されているかを確認してください。
次に、Vaultの認証でkubernetes
を有効にします。
$ vault auth enable kubernetes
続いて、kubernetes
の認証情報として、先に取得した証明書やトークンの情報を設定します。
$ vault write auth/kubernetes/config \
kubernetes_host="${K8S_HOST}" \
kubernetes_ca_cert="${K8S_CACERT}" \
token_reviewer_jwt="${TR_ACCOUNT_TOKEN}"
続いて、今回の動作検証で利用する秘密情報のアクセスポリシーをVaultに設定します。
$ echo 'path "secret/data/foo" {
capabilities = ["read", "list"]
}
path "sys/renew/*" {
capabilities = ["update"]
}' | vault policy write example-readonly -
$ vault write auth/kubernetes/role/example-role \
bound_service_account_names=csi-driver-registrar \
bound_service_account_namespaces=default \
policies=default,example-readonly \
ttl=20m
これでVaultの認証情報の設定は終わりです。
次に、動作検証で利用するサンプルの機密情報をfoo
に格納します。
$ vault kv put secret/foo bar=hello
secrets-store-csi-driverのデプロイ
secrets-store-csi-driverをデプロイします。
$ kubectl apply -f deploy/crd-csi-driver-registry.yaml
$ kubectl apply -f deploy/rbac-csi-driver-registrar.yaml
$ kubectl apply -f deploy/rbac-csi-attacher.yaml
$ kubectl apply -f deploy/csi-secrets-store-attacher.yaml
$ kubectl apply -f pkg/providers/vault/examples/secrets-store-csi-driver.yaml
default
ネームスペース以外のNamespaceにデプロイする場合は,Manifestを編集する必要があります。
secrets-store-csi-driverがデプロイされたことを確認します。
$ kubectl get sts,ds
NAME READY AGE
statefulset.apps/csi-secrets-store-attacher 1/1 6m42s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.extensions/csi-secrets-store 2 2 2 2 2 <none> 112s
Vaultに格納された機密情報をPV,PVCでマウント
Vaultに格納された機密情報をマウントするためのPVのManifest(pv-vault-csi.yaml
)を作成します。
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vault
spec:
capacity:
storage: 1Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: secrets-store.csi.k8s.com
readOnly: true
volumeHandle: kv
volumeAttributes:
providerName: "vault"
roleName: "example-role"
vaultAddress: "http://10.101.58.66:8200" # VaultのURL
vaultSkipTLSVerify: "true"
objects: |
array:
- |
objectPath: "/foo" # Vaultのセットアップにて格納したSecretのパス名
objectName: "bar" # 上記Secretに格納したKey名
objectVersion: ""
続いて、PV(pv-vault
)と対になるPVCのManifest(pvc-vault-csi-static.yaml
)を作成します。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-vault
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 1Gi
volumeName: pv-vault
storageClassName: ""
定義したPVとPVCをデプロイします。
$ kubectl apply -f pv-vault-csi.yaml
$ kubectl apply -f pvc-vault-csi-static.yaml
PVCとPVがBound
されたのを確認します。
$ kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/pvc-vault Bound pv-vault 1Gi ROX 84s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pv-vault 1Gi ROX Retain Bound default/pvc-vault 2m34s
次に、デプロイしたPVC,PVをマウントするPodのManifestを作成します。
kind: Pod
apiVersion: v1
metadata:
name: nginx-vault
spec:
containers:
- image: nginx
name: nginx-vault
envFrom:
- configMapRef:
name: vault-cm
volumeMounts:
- name: vault01
mountPath: "/mnt/vault" # Vault mount point.
readOnly: true
volumes:
- name: vault01
persistentVolumeClaim:
claimName: pvc-vault
Podをデプロイします。
$ kubectl apply -f nginx-pod-vault.yaml
続いて、PodがVaultに格納された機密情報をマウントできているかを確認します。
Podにアタッチしたのち、マウントしたディレクトリ/mnt/vault
を確認します。
$ kubectl exec -ti nginx-vault /bin/bash
root@nginx-vault:/# ls /mnt/vault/
foo
root@nginx-vault:/# cat /mnt/vault/foo
hello
結果、Vaultの機密情報が格納されたパスfoo
の値が/mnt/vault/foo
に格納されています。
このように、Vaultに格納された機密情報をPVC,PVを通じて参照することが可能となります。
感想
今回の動作検証では、機密情報を格納するためのリポジトリであるVaultに格納したデータを、Kubernetesで永続ストレージ(PVC,PV)としてマウントする方法を動作検証しました。これにより、容易にVaultに格納されたパスワードやアクセストークンをKubernetes上で読み出すことが可能となります。
ついつい利便性を優先し、アプリケーションのパスワードやアクセストークンなどの機密情報の管理が甘くなりがちな人もいるかもしれません。今回検証したVaultとKubernetsの連携のハードルは高くないので、ぜひ選択肢のひとつとしてご検討ください。
おまけ(Tips)
環境変数として利用したい場合は以下のように設定できます。
$ export FOO=`cat /mnt/vault/foo`