External Secretとは
External SecretとはExternal Secret Operatorによって提供される機能で、Kubernetes外にあるSecret(例:AWS Secret Manager, Vault等)を読み取ってKubernetesのSecretとしてクラスタにSecretを挿入してくれる。
提供されるカスタムリソースとしては、ExternalSecretとSecretStoreがあり、ExternalSecretは外部Secretとクラスタ内Secretの関係性を定義し、SecretStoreは外部Secretからどのように取得するかを指定する。

※画像はこちらから引用
通常EKSでExternalSecretを使う場合はSecretManagerから値を引っ張ることが多いが、今回はVaultから引っ張った際の検証メモ。
External Secret Operatorのインストール
オフィシャルサイトのGetting Startedに従い、External Secret Operatorをインストールする。
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
--set installCRDs=true
Vaultのインストール
Vaultもインストールする。
詳細はオフィシャル手順にお任せして、ここではやった手順をメモ程度に残しておく。
helm repo add hashicorp https://helm.releases.hashicorp.com
helm show values hashicorp/vault > vault-values.yaml
helm upgrade -i -f vault-values.yaml vault -n vault --create-namespace hashicorp/vault
vault-values.yamlはIngressを使うよう書き換えている。
Vault起動後にunseal作業を行う。unseal作業を実施しないと、Podが以下のエラーを吐き続けて起動が完了しない。
2022-08-04T03:28:40.124Z [INFO] core: seal configuration missing, not initialized
2022-08-04T03:28:45.090Z [INFO] core: security barrier not initialized
unsealのためのkeyやTokenをvault operator initで取得する。
kubectl exec -ti vault-0 -n vault -- vault operator init
この時のInitial Root Tokenは色々使うので残しておくこと。
unseal keyを使ってunsealを行う。
kubectl exec -ti vault-0 -n vault -- vault operator unseal
3回unseal作業を行うと、以下のような出力が得られてVaultが使えるようになる。
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 1.10.3
Storage Type file
Cluster Name vault-cluster-44f5b753
Cluster ID d4b134d3-6888-37a1-dba4-3b7063a61a80
HA Enabled false
Vaultの設定
ここではVault内にサンプルデータを投入し、またアクセスのためのポリシーを作成する。
ここの記述は基本的には以下の情報を参考にした。
- https://www.vaultproject.io/docs/auth/kubernetes
- https://learn.hashicorp.com/tutorials/vault/agent-kubernetes
ここからはHelmのVaultのIngressに対してローカルPCからvaultコマンドでアクセスする。
export VAULT_ADDR=https://vault.mydomain.info
vault login
デフォルトではKeyValueエンジンが有効化されていなかったので、有効化する。
vault secrets enable kv
KeyとValueのサンプルデータを投入する。
vault kv put kv/mysecret password=himitsu
確認する。
$ vault kv get kv/mysecret
====== Data ======
Key Value
--- -----
password himitsu
次にポリシーを作成する。
cat <<EOF > /tmp/vault_sample_policy.hcl
path "*" {
capabilities = ["read", "list"]
}
EOF
ポリシーを割り当てる。ポリシーの名前はTutorialに従ってmyapp-kv-roとした。
vault policy write myapp-kv-ro /tmp/vault_sample_policy.hcl
Kubernetes内のリソースを触るServiceAccountを作成する。今回はTutorialに従ってdefaultのNamespaceに作成した。権限周りでトラシュすることがあったのでcluster-adminにしているが、もっと絞れるとは思う。
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: default
EOF
Kubernetesの認証を有効化し、認証情報を追加する。
vault auth enable kubernetes
export SA_SECRET_NAME=$(kubectl get secrets --output=json \
| jq -r '.items[].metadata | select(.name|startswith("vault-auth-")).name')
export SA_JWT_TOKEN=$(kubectl get secret $SA_SECRET_NAME \
--output 'go-template={{ .data.token }}' | base64 --decode)
export SA_CA_CRT=$(kubectl config view --raw --minify --flatten \
--output 'jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)
export K8S_HOST=$(kubectl config view --raw --minify --flatten \
--output 'jsonpath={.clusters[].cluster.server}')
vault write auth/kubernetes/config \
token_reviewer_jwt="$SA_JWT_TOKEN" \
kubernetes_host="$K8S_HOST" \
kubernetes_ca_cert="$SA_CA_CRT" \
issuer="https://kubernetes.default.svc.cluster.local"
問題なければ、vault write auth/kubernetes/configの結果として以下のメッセージが得られるはずだ。
Success! Data written to: auth/kubernetes/config
次にロールを作成する。ロール名はhogeとした。
vault write auth/kubernetes/role/hoge \
bound_service_account_names=vault-auth \
bound_service_account_namespaces=default \
policies=myapp-kv-ro \
ttl=24h
なお、ここで出てくるServiceAccountの名前やNamespace、ポリシー名を変えている場合は適宜変更して実行すること。
vault write auth/kubernetes/login role=hoge jwt=$SA_JWT_TOKEN iss=https://kubernetes.default.svc.cluster.local
VaultのSecretをEKSに展開する
Vaultとの接続にはSecretStoreリソースを利用する。
cat << EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.mydomain.info"
path: "kv"
version: "v1"
namespace: "default"
auth:
kubernetes:
mountPath: "kubernetes"
role: "hoge"
serviceAccountRef:
name: "vault-auth"
EOF
なお、versionを"v2"にすると、ExternalSecretを作ると以下のようなエラーが出てSecretが作成されないことがある。自分も遭遇して少しハマった。
"cannot read secret data from Vault: Error making API request.\n\nNamespace: default\nURL: GET https://vault.mydomain.info/v1/kv/data/mysecret\nCode: 404.
そのため、こちらの記事を参考に、v1に変更して回避している。
※追記:
KVをGUIから作成するとv2となって、CLIから作成するとv2となる模様。GUIで作成した人はv2もしくは指定なしにする必要あり。
状態を確認する。
$ kubectl get secretstore
NAME AGE STATUS
vault-backend 8s Valid
なお、設定ミス等があると、以下のようになる。
$ kubectl get secretstore.external-secrets.io/vault-backend
NAME AGE STATUS
vault-backend 9m50s InvalidProviderConfig
この場合、例えばServiceAccountを設定し忘れた場合はexternal-secretsのPod内にCode: 500. Errors:\n\n* service account name not authorized
のようなエラーが出ているため、エラーを見て何が起きているか確認すること。
次にExternalSecretで実際にKubernetesのSecretリソースとして取り込む。
cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: vault-example
spec:
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: vault-example-secret
data:
- secretKey: fuga
remoteRef:
key: mysecret
property: password
EOF
spec.target.nameは作成されるSecret名、spec.data.secretKeyは作成されるSecret内のKeyでspec.data.remoteRefでVault側のパス(key部分、SecretStoreで指定したpathを取り除いたもの)とKey-Valueのkey(property)を設定する。
上記リソース作成後、statusがSecretSyncedになっていればOK.
$ kubectl get externalsecret
NAME STORE REFRESH INTERVAL STATUS
vault-example vault-backend 1h SecretSynced
Secretが作成されて、Vaultから値が引っ張ってこれていることが分かる。
$ kubectl get secret vault-example-secret -o jsonpath={.data.fuga} | base64 -d
himitsu