Vault使用external Secret管理kubernetes Secret

今天是2025年8月15日,大噩耗啊:

external-secrets / external-secrets

Link

⭐ 5416 | 🔀 1032 | Go 97.6%

External Secrets Operator 项目暂停发布

由于维护团队规模过小,External Secrets Operator 决定暂停所有官方版本发布,直到重建足够的维护团队。

Key Takeaways

  • 项目暂停所有官方版本发布,包括新功能、补丁和容器镜像。
  • 将继续审核和合并社区的 PR,但不会提供 GitHub Discussions、Slack 或问题评论的支持。
  • 需要至少五名长期维护者来确保项目的可持续发展。
  • 鼓励依赖该项目的公司或团队积极参与贡献。
  • 贡献者可通过填写表单或查看治理文档加入项目。

居然暂停发布了,那就抓紧时间把Vault跟它的集成记录下来:

一、首先必须在集群里安装Vault,然后安装external -secrets,都用helm装即可

1helm repo add hashicorp https://helm.releases.hashicorp.com
2helm repo update
3helm install vault hashicorp/vault --set "server.dev.enabled=true" --set "injector.enabled=true" --namespace default
4
5
6helm repo add external-secrets https://charts.external-secrets.io
7helm repo update
8helm install external-secrets external-secrets/external-secrets --namespace external-secrets --create-namespace --set installCRDs=true

二、k8s授权,k8的授权体系就是 sa -> role -> rolebinding

那kubernetes会把pod中sa的Token注入到文件:/var/run/secrets/kubernetes.io/serviceaccount/token

那调用k8s api中的token reviewer是可以对Token令牌进行验证的,我们先建立一系列k82资源

 1---
 2apiVersion: v1
 3kind: ServiceAccount
 4metadata:
 5  name: external-secrets-sa
 6  namespace: external-secrets
 7
 8---
 9apiVersion: rbac.authorization.k8s.io/v1
10kind: ClusterRole
11metadata:
12  name: external-secrets-clusterrole
13rules:
14  - apiGroups: [""]
15    resources: ["secrets"]
16    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
17  - apiGroups: ["authorization.k8s.io"]
18    resources: ["selfsubjectrulesreviews"]
19    verbs: ["create"]
20    
21---
22apiVersion: rbac.authorization.k8s.io/v1
23kind: ClusterRoleBinding
24metadata:
25  name: external-secrets-clusterrolebinding
26subjects:
27  - kind: ServiceAccount
28    name: external-secrets-sa
29    namespace: external-secrets
30roleRef:
31  kind: ClusterRole
32  name: external-secrets-clusterrole
33  apiGroup: rbac.authorization.k8s.io

如上,我们建立了一个sa,然后有api group的权限,同时授予了整个集群级别的权限

三、登录vault,配置vault使用k8s的认证方式进行身份认证

那 enable kubernetes 其实设置了 vault 可以使用kubernetes的认证方式来进行vault-role角色的身份认证

 1# 进入容器
 2kubectl exec -it vault-0 -- /bin/sh
 3
 4# 登录,要输入的token可以从日志里看到
 5vault login root
 6
 7# 开启kubernetes认证方式
 8vault auth enable kubernetes
 9
10# 每个pod里面都有sa,k8s会将这个sa的token注入到文件/var/run/secrets/kubernetes.io/serviceaccount/token
11# 那就用这个sa的token,调用k8s token_reviewer进行身份认证
12# 认证通过vault就会颁发一个对应vault-role的vault-token令牌出来
13# 用vault-token就可以去vault里读取到vault-secret的内容了
14vault write auth/kubernetes/config \
15    token_reviewer_jwt=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
16    kubernetes_host="https://kubernetes.default.svc:443" \
17    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
18    
19# 拿token的另一种方法:
20# kubectl get secret $(kubectl get serviceaccount external-secrets-sa -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decode
21
22
23# vault的认证体系跟AWS一样
24# 先建policy,后建role
25# 所以先建一个读secret的策略
26vault policy write external-secrets-policy - <<EOF
27path "secret/data/*" {
28  capabilities = ["read", "list"]  
29}
30EOF   
31
32# 然后建立一个vault的role,将这个vault-role绑定到k8s的sa账号上
33# 那k8s token review认证通过就可以获得相应vault-role的权限
34vault write auth/kubernetes/role/external-secrets-operator \
35  bound_service_account_names=external-secrets-sa \
36  bound_service_account_namespaces=external-secrets \
37  policies=external-secrets-policy \
38  ttl=24h
39  
40# 放数据进去vault
41vault kv put secret/my-app MY_SECRET_NAME="Fuck Dingdang"
42vault kv get secret/my-app

四、external-secret同步的设定

external-secret相当于中间人,他有很多自定义的资源,来自动进行vault-secret和k8s-secret之间的同步

ClusterSecretStore指定了vault server、vault-role、k8s sa的关系

 1---
 2apiVersion: external-secrets.io/v1
 3kind: ClusterSecretStore
 4metadata:
 5  name: vault-cluster-secretstore
 6spec:
 7  provider:
 8    vault:
 9      server: "http://vault.default.svc.cluster.local:8200"  # 请根据你的vault服务地址调整
10      path: "secret"       # KV引擎挂载路径,与你vault kv put命令中路径对应
11      version: "v2"        # KV版本为v2
12      auth:
13        kubernetes:
14          mountPath: "kubernetes"   # 你启用的k8s auth路径
15          role: "external-secrets-operator"  # 你在vault中创建的role名称
16          serviceAccountRef:
17            name: external-secrets-sa       # 你为ESO创建的ServiceAccount名称
18            namespace: external-secrets      # ServiceAccount所在命名空间

ExternalSecret 指定了利用vault-role获得了vault-secret,放置到最终要用到的k8s-secret中

注意有刷新,每小时会刷新一次的

 1---
 2apiVersion: external-secrets.io/v1
 3kind: ExternalSecret
 4metadata:
 5  name: demo-external-secret
 6  namespace: default
 7spec:
 8  refreshInterval: 1h
 9  secretStoreRef:
10    name: vault-cluster-secretstore
11    kind: ClusterSecretStore
12  target:
13    name: demo-secret
14    creationPolicy: Owner
15  data:
16    - secretKey: MY_SECRET_NAME
17      remoteRef:
18        key: my-app
19        property: MY_SECRET_NAME       # Vault中具体的键名

通过以上两个资源,那vault里存放的secret就被同步到k8s的secret中了

五、实际部署一个应用看看

那我们随便起个deployment

 1---
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: demo-deployment
 6  namespace: default # 使用目标命名空间,可以修改为你的实际命名空间
 7  labels:
 8    app: demo
 9spec:
10  replicas: 1 # 副本数量,可以根据需要调整
11  selector:
12    matchLabels:
13      app: my-app
14  template:
15    metadata:
16      labels:
17        app: my-app
18    spec:
19      containers:
20      - name: demo-container
21        image: nginx:latest
22        imagePullPolicy: IfNotPresent # 如果本地已经存在镜像则不拉取
23        ports:
24        - containerPort: 8080 # 容器暴露的端口
25        env:
26        - name: MY_SECRET_NAME
27          valueFrom:
28            secretKeyRef:
29              name: demo-secret # 引用的 Secret 名称
30              key: MY_SECRET_NAME # Secret 中的键名
31              
32---
33apiVersion: v1
34kind: Secret
35metadata:
36  name: demo-secret
37  namespace: default # 使用目标命名空间,可以修改为你的实际命名空间
38type: Opaque
39data:
40  MY_SECRET_NAME: AABBCC

实在太复杂痛苦了,最终的环境变量应该如下:

1MY_SECRET_NAME="Fuck Dingdang"

但这没有结束呢。

六、安装配置reloader自动轮换

如果secret被改了,如AK的Secret自动轮换了,那pod也必须跟着自动重启以加载新的变量

1# 安装reloader
2kubectl apply -f https://raw.githubusercontent.com/stakater/Reloader/master/deployments/kubernetes/reloader.yaml

在deploy中container的部分新增annotations:

1metadata:
2  annotations:
3    reloader.stakater.com/auto: "true"

如下:

 1---
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: demo-deployment
 6  namespace: default 
 7  annotations:
 8    reloader.stakater.com/auto: "true"  
 9  labels:
10    app: demo
11spec:
12  replicas: 1 # 副本数量,可以根据需要调整
13  selector:
14    matchLabels:
15      app: my-app
16  template:
17    metadata:
18      labels:
19        app: my-app
20    spec:
21      containers:
22      - name: demo-container
23        image: nginx:latest
24        imagePullPolicy: IfNotPresent # 如果本地已经存在镜像则不拉取
25        ports:
26        - containerPort: 80
27        env:
28        - name: MY_SECRET_NAME
29          valueFrom:
30            secretKeyRef:
31              name: demo-secret # 引用的 Secret 名称
32              key: MY_SECRET_NAME # Secret 中的键名

这样ENV环境变量改变的时候就可以自动重起pod

七、另一种注入式的做法vault injector

Deployment新增annotations:

1metadata:
2  annotations:
3    vault.hashicorp.com/agent-inject: "true"
4    vault.hashicorp.com/role: "demo-role"
5    vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
6    vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
7      {{- with secret "secret/data/demo" -}}
8      export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
9      {{- end -}}

完整如下:

 1---
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: demo-deployment
 6  namespace: default
 7  annotations:
 8    vault.hashicorp.com/agent-inject: "true"
 9    vault.hashicorp.com/role: "demo-role"
10    vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
11    vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
12      {{- with secret "secret/data/demo" -}}
13      export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
14      {{- end }}
15spec:
16  replicas: 1
17  selector:
18    matchLabels:
19      app: my-app
20  template:
21    metadata:
22      labels:
23        app: my-app
24      annotations:
25        vault.hashicorp.com/agent-inject: "true"
26        vault.hashicorp.com/role: "demo-role"
27        vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
28        vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
29          {{- with secret "secret/data/demo" -}}
30          export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
31          {{- end }}
32    spec:
33      serviceAccountName: demo-sa
34      containers:
35      - name: demo-container
36        image: nginx:latest
37        imagePullPolicy: IfNotPresent
38        ports:
39        - containerPort: 80

如上也可以做到secret有改动,就自动重启

八、补全一下用k8s Token凭据验证vault角色的过程

  1# 先建立一个SA
  2apiVersion: v1
  3kind: ServiceAccount
  4metadata:
  5  name: demo-sa
  6  namespace: default 
  7  
  8# 然后定义一个Deployment,在spec附上这个SA
  9---
 10apiVersion: apps/v1
 11kind: Deployment
 12metadata:
 13  labels:
 14    app: nginx
 15  name: nginx
 16  namespace: default
 17spec:
 18  serviceAccountName: demo-sa
 19  replicas: 1
 20  selector:
 21    matchLabels:
 22      app: nginx
 23  template:
 24    metadata:
 25      labels:
 26        app: nginx
 27    spec:
 28      containers:
 29        - env:
 30            - name: VAULT_ADDR
 31              value: "http://vault.default.svc.cluster.local:8200"  # 替换为 Vault 的服务地址
 32          image: nginx:latest
 33          imagePullPolicy: IfNotPresent
 34          name: nginx
 35
 36# 登录vault服务(开发模式,登录用户是root)
 37vault login root
 38 
 39# 启用 Kubernetes Auth 模式
 40vault auth enable kubernetes
 41 
 42# vault 使用 sa Token进行身份识别
 43vault write auth/kubernetes/config \
 44    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
 45    kubernetes_host="https://kubernetes.default.svc:443" \
 46    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
 47    
 48# 创建一个policy
 49vault policy write demo-policy - <<EOF
 50path "secret/data/*" {
 51  capabilities = ["read", "list"]
 52}
 53EOF
 54 
 55# 创建一个vault-role,绑定到sa
 56vault write auth/kubernetes/role/demo-role \
 57    bound_service_account_names=demo-sa \
 58    bound_service_account_namespaces=default \
 59    policies=demo-policy \
 60
 61# 存放测试kv数据  
 62vault kv put secret/demo MY_SECRET_NAME="BBCCDDEE"
 63vault kv get secret/demo
 64
 65# 启动一个deployment
 66---
 67apiVersion: apps/v1
 68kind: Deployment
 69metadata:
 70  labels:
 71    app: nginx
 72  name: nginx
 73  namespace: default
 74spec:
 75  serviceAccountName: demo-sa
 76  replicas: 1
 77  selector:
 78    matchLabels:
 79      app: nginx
 80  template:
 81    metadata:
 82      labels:
 83        app: nginx
 84    spec:
 85      containers:
 86        - env:
 87            - name: VAULT_ADDR
 88              value: "http://vault.default.svc.cluster.local:8200"  # 替换为 Vault 的服务地址
 89          image: nginx:latest
 90          imagePullPolicy: IfNotPresent
 91          name: nginx
 92      
 93# 进入deployment的pod中
 94kubectl exec -it nginx -- /bin/sh
 95
 96# 拿到本地sa的Token
 97export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
 98
 99# 注意pod里有环境变量VAULT_ADDR,如果没有要定义一下
100# 然后用curl拿vault-token
101curl -X POST "${VAULT_ADDR}/v1/auth/kubernetes/login" \
102    -H "Content-Type: application/json" \
103    -d '{
104      "role": "demo-role",
105      "jwt": "'$TOKEN'"
106    }'
107    
108# curl 返回字符串中的 client_token 字段    
109export VAULT_TOKEN="hvs.CAESaaaBBBcccDDDEEEfffGGG"   
110
111# 用vault_token获得kv的数据
112curl -X GET "${VAULT_ADDR}/v1/secret/data/demo" -H "X-Vault-Token: $VAULT_TOKEN"

所以,其实vault有api接口,可以直接调用curl来认证并获取数据,这就极大的扩展了vault的接口范围。

好了,就到这里吧!


K3s的安装
Vault使用AWS的federation_token进行交互
comments powered by Disqus