Skip to main content

Command Palette

Search for a command to run...

Kubernetes 的小秘密——从 Secret 到 Bank Vault

Updated
4 min read

Kubernetes 提供了 Secret 对象用于承载少量的机密/敏感数据,在实际使用中,有几种常规或者非常规的方式能够获取到 Secret 的内容:

  1. Pod 加载(自己的或者不是自己的)Secret 为环境变量或者文件

  2. 使用 Kubernetes API(或者 kubectl)获取 Secret 对象内容

  3. 连接 ETCD 读取其中保存的 Secret 明文

  4. 在 CICD 工具中截获含有明文的 Secret 对象 YAML

  5. 在加载了 Secret 的容器中直接读取环境变量或者机密文件

上述泄露途径有几个方式可以进行消减:

  1. 制定细粒度的 RBAC 策略,防止未授权的 Secret 访问以及 Exec 访问

  2. API Server 使用加密参数(EncryptionConfiguration),在 ETCD 中存储密文

  3. 使用 Scratch 等超精简基础镜像,杜绝无用访问

  4. 使用策略引擎,防止不当的加载行为

    • 只有特定的 Pod/容器可以加载特定的 Secret

    • 禁止随意加载主机卷,防止 Kubernetes 组件的身份证书被冒用

除了上述的原生方案之外,还有一些补充手段也是有帮助的,例如:

  • Bitnami 的 Sealed Secret 工具,使用密钥对机密信息进行加密,只有在进入集群之后才会还原为目标 Secret,防止在供应链中泄露信息。

  • Vault 提供了一个 Sidecar,能把 Vault 中存储的机密信息,直接在 Pod 中生成相应的敏感信息文件

  • Secrets Store CSI Driver 项目,能从 Vault、Azure 等设施获取信息,注入 Pod 或者生成 Secret。

Bank Vault

Bank Vault 是个 Vault 周边项目,它大大的降低了 Vault 的落地难度,通过 Webhook 注入,Sidecar 等方式,为 Kubernetes 集群中的工作负载提供了方便的 Vault 接入手段。下图表示了它和原生 Vault 的相对优势:

部署

Bank Vault 提供了一个 Operator,能够非常方便的部署 Vault 服务极其相关的 Webhook。所以首先从 Helm 安装 Operator 开始。

$ helm upgrade --install --wait vault-operator \
oci://ghcr.io/bank-vaults/helm-charts/vault-operator

Release "vault-operator" does not exist. Installing it now.
Pulled: ghcr.io/bank-vaults/helm-charts/vault-operator:1.22.1
Digest: sha256:f9d976c39f96942ae52b26a3ab923f173109de64a87c3161fed2470f7bcfa86f
NAME: vault-operator
LAST DEPLOYED: Sat Apr  6 13:54:32 2024
...

接下来使用 Kustomize 生成 Vault 所需的 RBAC 对象:

$ kubectl kustomize https://github.com/bank-vaults/vault-operator/deploy/rbac | kubectl apply -f -
serviceaccount/vault created
role.rbac.authorization.k8s.io/vault created
role.rbac.authorization.k8s.io/leader-election-role created
rolebinding.rbac.authorization.k8s.io/leader-election-rolebinding created
rolebinding.rbac.authorization.k8s.io/vault created
clusterrolebinding.rbac.authorization.k8s.io/vault-auth-delegator created

最后创建 Vault 实例:

$ kubectl apply -f https://raw.githubusercontent.com/bank-vaults/vault-operator/v1.21.0/deploy/examples/cr-raft.yaml
vault.vault.banzaicloud.com/vault created

创建结束后,会出现几个 Pod,分别是 vault-operatorvault-configurer 以及三个有状态 vault 实例。

连接到 Vault

首先是新开一个终端窗口,使用端口转发方式暴露 Vault 服务:

$ kubectl port-forward vault-0 8200 &
...
Forwarding from 127.0.0.1:8200 -> 8200
Forwarding from [::1]:8200 -> 8200

然后是给 Vault 客户端准备接入端点和 CA:

# 端点就是 kubectl 转发的端口
$ export VAULT_ADDR=https://127.0.0.1:8200
# 导出证书,并记录到环境变量里
$ kubectl get secret vault-tls -o jsonpath="{.data.ca\.crt}" | base64 --decode > $PWD/vault-ca.crt
export VAULT_CACERT=$PWD/vault-ca.crt

检查一下 vault 的连接:

$ vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
...

用环境变量保存凭据:

export VAULT_TOKEN=$(kubectl get secrets vault-unseal-keys -o jsonpath={.data.vault-root} | base64 --decode)

部署 Webhook

Vault 服务启动并连接之后,就可以开始着手部署功能部分了,前面提到过,Bank Vault 是用 Webhook 实现功能的,所以接下来部署的就是 Webhook 了:

$ kubectl create namespace vault-infra
$ kubectl label namespace vault-infra name=vault-infra

namespace/vault-infra created
namespace/vault-infra labeled
$ helm upgrade --install --wait vault-secrets-webhook \
  oci://ghcr.io/bank-vaults/helm-charts/vault-secrets-webhook \
  --namespace vault-infra
...
LAST DEPLOYED: Sat Apr  6 14:45:05 2024
NAMESPACE: vault-infra
STATUS: deployed

部署完成之后发现生成了两个 Webhook。查看代码,可以看到:

  • pods.vault-secrets-webhook

    • 会被 Pod 的创建事件触发

    • 跳过 kube-system 和刚创建的 vault-infra 两个命名空间

    • 跳过 security.banzaicloud.io/mutate 标签为 skip 的 Pod

  • secrets.vault-secrets-webhook

    • 会被 Secret 的创建和更新事件触发

    • 跳过 kube-system 和刚创建的 vault-infra 两个命名空间

    • 跳过 security.banzaicloud.io/mutate 标签为 skip 的 Secret

写入测试数据

向 Vault 写入一个密钥:

vault kv put secret/demosecret/aws AWS_SECRET_ACCESS_KEY=s3cr3t

======= Secret Path =======
secret/data/demosecret/aws

======= Metadata =======
Key                Value
---                -----
created_time       2024-04-06T07:12:27.042649134Z
...

用环境变量读取 Vault 内容

创建一个 Pod,看看 Webhook 会对他做什么。

apiVersion: v1
kind: Pod
metadata:
  name: vault-test-pod
  labels:
    app.kubernetes.io/name: vault
  annotations:
    vault.security.banzaicloud.io/vault-addr: "https://vault:8200"
    vault.security.banzaicloud.io/vault-role: "default"
    vault.security.banzaicloud.io/vault-skip-verify: "false"
    vault.security.banzaicloud.io/vault-tls-secret: "vault-tls"
    vault.security.banzaicloud.io/vault-agent: "false"
    vault.security.banzaicloud.io/vault-path: "kubernetes"
spec:
  serviceAccountName: default
  containers:
  - name: alpine
    image: alpine
    command: ["sh", "-c", "echo $AWS_SECRET_ACCESS_KEY && echo going to sleep... && sleep 10000"]
    env:
    - name: AWS_SECRET_ACCESS_KEY
      value: vault:secret/data/demosecret/aws#AWS_SECRET_ACCESS_KEY

创建成功之后,看看 Pod 的日志:

$ kubectl logs -f vault-test-pod
Defaulted container "alpine" out of: alpine, copy-vault-env (init)
...
s3cr3t
going to sleep...

这里输出了我们之前写入 Vault 的密钥值,然而回头看看,我们的 Pod 定义里,并没有引用 Secret,只是定义了一个值为 vault:secret/data/demosecret/aws#AWS_SECRET_ACCESS_KEY 的环境变量,command 节中的命令行直接输出这个环境变量,就能够输出保存在 Vault 中的内容了。但是进入 Pod 的 Shell,会发现环境变量没有变化:

$ kubectl exec -it vault-test-pod -- env | grep -i aws
Defaulted container "alpine" out of: alpine, copy-vault-env (init)
AWS_SECRET_ACCESS_KEY=vault:secret/data/demosecret/aws#AWS_SECRET_ACCESS_KEY

所以 Pod 中被注入了什么呢?

  • 首先是注入了一个初始化容器,在临时卷里面复制了一个 vault-env 命令

  • 用卷加载了 Configmap,其中包含了访问 Vault 所需的 CA

  • 加载了

  • 根据我们前面的注解,生成了一系列的 VAULT* 环境变量

  • 最重要的,它劫持了原有的启动命令,在前面加入了一个 /vault/vault-env,启动命令就变成了:

        - args:
          - sh
          - -c
          - echo $AWS_SECRET_ACCESS_KEY && echo going to sleep... && sleep 10000
          command:
          - /vault/vault-env
    

所以可以推测——/vault/vault-env 充当了 sh 的父进程,在其中根据环境变量 AWS_SECRET_ACCESS_KEY 的值获取了保存在 Vault 中的机密内容。

用机密数据渲染配置文件

看看下面的 Configmap

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/name: my-app
    my-app.kubernetes.io/name: my-app-vault-agent
    branches: "true"
  name: my-app-vault-agent
data:
  config.hcl: |
    vault {
      // This is needed until https://github.com/hashicorp/vault/issues/7889
      // gets fixed, otherwise it is automated by the webhook.
      ca_cert = "/vault/tls/ca.crt"
    }
    auto_auth {
      method "kubernetes" {
        mount_path = "auth/kubernetes"
        config = {
          role = "default"
        }
      }
      sink "file" {
        config = {
          path = "/vault/.vault-token"
        }
      }
    }
    template {
      contents = <<EOH
        {{- with secret "secret/data/demosecret/aws" }}
        token: {{ .Data.data.AWS_SECRET_ACCESS_KEY }}
        {{ end }}
      EOH
      destination = "/tmp/config"
      // command     = "/bin/sh -c \"kill -HUP $(pidof sleep) || true\""
    }

上面的配置文件指示了如何对接 Vault,从 secret/data/demosecret/aws 拉取 AWS_SECRET_ACCESS_KEY 中的值,渲染到 template 一节中的模板里面。只要在 Pod 的注解中加入 vault.security.banzaicloud.io/vault-agent-configmap: "my-app-vault-agent"。就可以在这个容器中加入 Sidecar,使用 Sidecar 在 destination 字段指定的配置文件里保存渲染结果。如果 command 有赋值,还可以发出命令,通知业务应用刷新配置。

加入该注解的 Pod 运行后,可以在这个 Pod 的指定文件中看到渲染结果,例如:

$ kubectl get pods | grep vault-agent-pod
vault-agent-pod                     2/2     Running   0              9m8s
$ kubectl exec -it vault-agent-pod -- cat /tmp/config
Defaulted container "vault-agent" out of: vault-agent, alpine

    token: s3cr3t

后记

Bank Valut 这个项目虽然已经有 2000 Star 了,不过文档还弱的很,甚至 Blog 全挂了也没人理。但是这个思路还是有点意思。虽说有点像屠龙技,不过被安全同学卡脖子的时候,这种使用父进程遮盖环境变量,或者用轮转方式刷新配置文件的玩法,都算是个可行的解法。

More from this blog

龙虾恐慌:AIOps 又要改名了?

ChatGPT 开始,把 AI 拉近到普罗大众的面前,让无数人感受到 AI 的亲民魅力。而龙虾,则把大模型驱动的自动化能力,突然间变得水灵灵、活泼泼地走进千家万户。它不只是“风口上的猪”,而是风口本身。热度高到让 Mac mini 一度断货,不知道这在不在库克的预料之内。 每代人都有每代人的鸡蛋,春节期间,我就领了我的鸡蛋。翻出古老的 MacBook Air M1,充值各种大模型。当然了,这个工具

Mar 9, 20261 min read

再见 2025

我猜不少人以为这个号废了吧?并没有,只是今年变化有点大,一直有种抄起键盘,无从说起的感觉,所以一直偷懒到今天,2025 的最后一天。 今年是我的第四个本命年,去年末一期播客里,大内说本命年不是灾年,是变化年,有危也有机。可是讲真啊,只看到危,没看到机。 各种因缘际会,从鹅厂跳槽到前东家,已经接近四年,第一个合同期已经进入尾声。除了前两年还在云原生领域嗷嗷叫,后两年基本都是些鸡零狗碎的东西了,用老东家的术语说是——偏离主航道,可谓是前景暗淡了。 一旦确定要滚蛋,反倒心思轻松起来,每天骑着我的小红车...

Jan 5, 20261 min read

辅助编程?dora 说:我知道你很急可是请你别急

从 OpenGPT 把大模型的火烧旺了之后,这三年来,相信很多组织或摩拳擦掌、或躬身入局,希望借助聪明能干的大模型,或想偿还技术宅,或想降本增效,或想弯道超车。一时间,沉寂许久的 AIxx 又活过来了,LLM Ops、Vibe Coding、中医大模型、GPT 算命等等,全都老树发新芽,焕发了勃勃生机。那么视角拉回从业者最关注的饭碗相关的领域之一——AI 辅助开发,产生了什么触动,应该如何拥抱呢? DORA 的年度报告中给出了很有意思的结论——强者恒强。 执行摘要部分总结了几个有趣的点: 问题...

Oct 6, 20251 min read

[译]dora:ai 辅助软件开发状态报告

执行摘要 在 2025 年,科技领导者面临的核心问题已不再是“是否要采用 AI”,而是“如何实现其价值”。 DORA 的研究基于超过 100 小时的定性访谈和来自全球近 5,000 名技术专业人士的问卷调查。研究揭示了一个关键事实:AI 在软件开发中的主要角色是“放大器”。它会放大高效能组织的优势,也会凸显组织的缺陷。 关键结论:AI 是放大器 AI 投资的最大回报并非来自工具本身,而是来自组织底层系统的战略性建设: 高质量的内部平台 清晰的工作流 团队的协同能力 缺少这些基础,AI ...

Oct 2, 202514 min read

僭越了,有人在用 Rust 写 Kubernetes

一个新语言问世,最爱做的事情之一,就是重写存量软件了。 云原生喝酒 SIG 重点扶持项目——rk8s(https://github.com/rk8s-dev/rk8s) 也可以归在这个范畴里,只不过这个项目重写的东西比较大,是 Kubernetes。 从 2025 年 1 月第一个 Commit 开始,到现在有了 200 多次 Commit,十几万行代码。当然距离 Kubernetes 的几百万行代码还差得远——老马就是喜欢整这种大无畏项目。 另外该项目也是国内第一个脱离 Cargo 转向使用 ...

Sep 27, 20253 min read

【伪】架构师

342 posts