今天是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装即可

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install vault hashicorp/vault --set "server.dev.enabled=true" --set "injector.enabled=true" --namespace default


helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm 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资源

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets-sa
  namespace: external-secrets

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-secrets-clusterrole
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["authorization.k8s.io"]
    resources: ["selfsubjectrulesreviews"]
    verbs: ["create"]
    
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: external-secrets-clusterrolebinding
subjects:
  - kind: ServiceAccount
    name: external-secrets-sa
    namespace: external-secrets
roleRef:
  kind: ClusterRole
  name: external-secrets-clusterrole
  apiGroup: rbac.authorization.k8s.io

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

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

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

# 进入容器
kubectl exec -it vault-0 -- /bin/sh

# 登录,要输入的token可以从日志里看到
vault login root

# 开启kubernetes认证方式
vault auth enable kubernetes

# 每个pod里面都有sa,k8s会将这个sa的token注入到文件/var/run/secrets/kubernetes.io/serviceaccount/token
# 那就用这个sa的token,调用k8s token_reviewer进行身份认证
# 认证通过vault就会颁发一个对应vault-role的vault-token令牌出来
# 用vault-token就可以去vault里读取到vault-secret的内容了
vault write auth/kubernetes/config \
    token_reviewer_jwt=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    kubernetes_host="https://kubernetes.default.svc:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    
# 拿token的另一种方法:
# kubectl get secret $(kubectl get serviceaccount external-secrets-sa -o jsonpath="{.secrets[0].name}") -o jsonpath="{.data.token}" | base64 --decode


# vault的认证体系跟AWS一样
# 先建policy,后建role
# 所以先建一个读secret的策略
vault policy write external-secrets-policy - <<EOF
path "secret/data/*" {
  capabilities = ["read", "list"]  
}
EOF   

# 然后建立一个vault的role,将这个vault-role绑定到k8s的sa账号上
# 那k8s token review认证通过就可以获得相应vault-role的权限
vault write auth/kubernetes/role/external-secrets-operator \
  bound_service_account_names=external-secrets-sa \
  bound_service_account_namespaces=external-secrets \
  policies=external-secrets-policy \
  ttl=24h
  
# 放数据进去vault
vault kv put secret/my-app MY_SECRET_NAME="Fuck Dingdang"
vault kv get secret/my-app

四、external-secret同步的设定

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

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

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

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

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

---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: demo-external-secret
  namespace: default
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-cluster-secretstore
    kind: ClusterSecretStore
  target:
    name: demo-secret
    creationPolicy: Owner
  data:
    - secretKey: MY_SECRET_NAME
      remoteRef:
        key: my-app
        property: MY_SECRET_NAME       # Vault中具体的键名

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

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

那我们随便起个deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
  namespace: default # 使用目标命名空间,可以修改为你的实际命名空间
  labels:
    app: demo
spec:
  replicas: 1 # 副本数量,可以根据需要调整
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: demo-container
        image: nginx:latest
        imagePullPolicy: IfNotPresent # 如果本地已经存在镜像则不拉取
        ports:
        - containerPort: 8080 # 容器暴露的端口
        env:
        - name: MY_SECRET_NAME
          valueFrom:
            secretKeyRef:
              name: demo-secret # 引用的 Secret 名称
              key: MY_SECRET_NAME # Secret 中的键名
              
---
apiVersion: v1
kind: Secret
metadata:
  name: demo-secret
  namespace: default # 使用目标命名空间,可以修改为你的实际命名空间
type: Opaque
data:
  MY_SECRET_NAME: AABBCC

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

MY_SECRET_NAME="Fuck Dingdang"

但这没有结束呢。

六、安装配置reloader自动轮换

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

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

在deploy中container的部分新增annotations:

metadata:
  annotations:
    reloader.stakater.com/auto: "true"

如下:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
  namespace: default 
  annotations:
    reloader.stakater.com/auto: "true"  
  labels:
    app: demo
spec:
  replicas: 1 # 副本数量,可以根据需要调整
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: demo-container
        image: nginx:latest
        imagePullPolicy: IfNotPresent # 如果本地已经存在镜像则不拉取
        ports:
        - containerPort: 80
        env:
        - name: MY_SECRET_NAME
          valueFrom:
            secretKeyRef:
              name: demo-secret # 引用的 Secret 名称
              key: MY_SECRET_NAME # Secret 中的键名

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

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

Deployment新增annotations:

metadata:
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "demo-role"
    vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
    vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
      {{- with secret "secret/data/demo" -}}
      export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
      {{- end -}}

完整如下:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
  namespace: default
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "demo-role"
    vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
    vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
      {{- with secret "secret/data/demo" -}}
      export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
      {{- end }}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "demo-role"
        vault.hashicorp.com/agent-inject-secret-MY_SECRET_NAME: "secret/data/demo"
        vault.hashicorp.com/agent-inject-template-MY_SECRET_NAME: |
          {{- with secret "secret/data/demo" -}}
          export MY_SECRET_NAME="{{ .Data.data.MY_SECRET_NAME }}"
          {{- end }}
    spec:
      serviceAccountName: demo-sa
      containers:
      - name: demo-container
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

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

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

# 先建立一个SA
apiVersion: v1
kind: ServiceAccount
metadata:
  name: demo-sa
  namespace: default 
  
# 然后定义一个Deployment,在spec附上这个SA
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: default
spec:
  serviceAccountName: demo-sa
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - env:
            - name: VAULT_ADDR
              value: "http://vault.default.svc.cluster.local:8200"  # 替换为 Vault 的服务地址
          image: nginx:latest
          imagePullPolicy: IfNotPresent
          name: nginx

# 登录vault服务(开发模式,登录用户是root)
vault login root
 
# 启用 Kubernetes Auth 模式
vault auth enable kubernetes
 
# vault 使用 sa Token进行身份识别
vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    kubernetes_host="https://kubernetes.default.svc:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    
# 创建一个policy
vault policy write demo-policy - <<EOF
path "secret/data/*" {
  capabilities = ["read", "list"]
}
EOF
 
# 创建一个vault-role,绑定到sa
vault write auth/kubernetes/role/demo-role \
    bound_service_account_names=demo-sa \
    bound_service_account_namespaces=default \
    policies=demo-policy \

# 存放测试kv数据  
vault kv put secret/demo MY_SECRET_NAME="BBCCDDEE"
vault kv get secret/demo

# 启动一个deployment
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: default
spec:
  serviceAccountName: demo-sa
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - env:
            - name: VAULT_ADDR
              value: "http://vault.default.svc.cluster.local:8200"  # 替换为 Vault 的服务地址
          image: nginx:latest
          imagePullPolicy: IfNotPresent
          name: nginx
      
# 进入deployment的pod中
kubectl exec -it nginx -- /bin/sh

# 拿到本地sa的Token
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# 注意pod里有环境变量VAULT_ADDR,如果没有要定义一下
# 然后用curl拿vault-token
curl -X POST "${VAULT_ADDR}/v1/auth/kubernetes/login" \
    -H "Content-Type: application/json" \
    -d '{
      "role": "demo-role",
      "jwt": "'$TOKEN'"
    }'
    
# curl 返回字符串中的 client_token 字段    
export VAULT_TOKEN="hvs.CAESaaaBBBcccDDDEEEfffGGG"   

# 用vault_token获得kv的数据
curl -X GET "${VAULT_ADDR}/v1/secret/data/demo" -H "X-Vault-Token: $VAULT_TOKEN"

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

好了,就到这里吧!