路线图

30-密钥管理

星辉 2026-07-02 阅读 4 min 680 字 路线图
30-密钥管理 封面

密钥管理是安全的地基。再花哨的安全工具,只要有一个长期密码躺在 YAML 文件里,都等于零——那就是免费的绕过路径。本章覆盖 HashiCorp Vault 的动态密钥与租约模型、云 KMS 方案、External Secrets Operator、密钥轮换策略。

为什么不能把 Secret 存进 Git#

K8s 的 Secret 只是 base64 编码,不是加密:

yaml
# 别这样做
apiVersion: v1
kind: Secret
data:
  password: bXlwYXNzd29yZDEyMw==   # echo bXlwYXNzd29yZDEyMw== | base64 -d → "mypassword123"

更大的风险来自 Git 历史——即使下一个 commit 删掉了,git log 依然能翻出历史版本。GitHub secret scanning 每天都在扫描公开仓库里的 AWS Key、数据库密码。

我们真正需要的是:

  • Secret 不出现在代码仓库(无论明文还是 base64)
  • 不同环境使用不同凭证
  • 凭证有生命周期,定期自动轮换
  • 访问凭证有审计日志

核心概念:静态 vs 动态密钥#

  • 静态密钥(static secret):一次生成、多次使用、长期有效。如 MySQL root 密码、API key、TLS 证书。
  • 动态密钥(dynamic secret):按需生成、用完即弃、短生命周期。如"给这个微服务临时生成一个数据库账号,1 小时后自动删除"。
  • 轮换(rotation):周期性更换密钥。静态密钥通过 rotation 变得"不那么静态";动态密钥天生"自动过期"。

零信任的理想状态:用动态密钥替代一切静态密钥。现实做不到(遗留系统只认静态密钥),所以真实方案是"能动态就动态,动态不了就自动轮换"。

HashiCorp Vault#

Vault 在"动态密钥"这件事上依然没有对手。

Secret Engine#

Vault 把不同类型的 Secret 管理能力抽象成"引擎":

  • KV(Key-Value):最常用,KV v2 支持版本历史方便回滚
  • Database:动态生成临时数据库账号,用完即销毁
  • PKI:让 Vault 变成内部 CA,自动签发/吊销 TLS 证书
  • Transit:加密即服务,应用不接触密钥本身

Database 动态密钥#

bash
# 配置连接
vault write database/config/my-postgres \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb" \
    username="vault-admin" \
    password="admin-pass"

# 定义 role(动态账号模板)
vault write database/roles/app-role \
    db_name=my-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

应用请求时 Vault 现场创建临时账号:

bash
vault read database/creds/app-role
# lease_id: database/creds/app-role/lKxjbVyBdRBqUSGRy9DJJfQh
# lease_duration: 1h
# username: v-token-app-role-xxxxx
# password: A1a-xxxxxxxxxxxx

结果:没有任何一个长期数据库密码存在于任何地方。即便 Pod 被入侵,攻击者拿到的也只是一个 1 小时有效期的账号。

K8s 集成:Vault Agent Injector#

通过 annotation 自动注入 secret,应用无感知:

yaml
metadata:
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "orders-api"
    vault.hashicorp.com/agent-inject-secret-db.conf: "database/creds/readonly"
    vault.hashicorp.com/agent-inject-template-db.conf: |
      {{- with secret "database/creds/readonly" -}}
      DB_USER={{ .Data.username }}
      DB_PASS={{ .Data.password }}
      {{- end -}}

Vault Agent sidecar 通过 K8s SA token 向 Vault 认证,获取动态凭据,渲染模板写到 /vault/secrets/db.conf,TTL 到期前自动续期。

生产部署要点#

  • HA 用 Raft integrated storage,三副本跨 AZ
  • Unseal 用 auto-unseal(AWS KMS / 阿里云 KMS),否则半夜 Pod 重启要手动 unseal
  • Audit log 必开,记录每次请求的身份、路径、响应时间
  • Snapshot 定时备份:vault operator raft snapshot save
  • Root token 只用于 bootstrap,之后立刻 revoke

云 KMS 方案#

阿里云 KMS#

  • 密钥管理服务,支持对称/非对称加密
  • 与 RAM 深度集成,权限通过 RAM policy 控制
  • 可作为 Vault 的 auto-unseal 后端

腾讯云 KMS#

  • 类似阿里云 KMS,密钥生命周期管理
  • 与 CAM 集成

AWS Secrets Manager#

  • 零运维,HA 内置
  • 原生集成 RDS——打勾就能开启轮换
  • 双用户模式:预先创建 app_user_a 和 app_user_b,轮换时交替改密,应用永远有一个刚改过的账号和一个当前用的账号,不会断连
bash
aws secretsmanager rotate-secret \
    --secret-id prod/rds/orders-db \
    --rotation-lambda-arn arn:aws:lambda:us-west-2:xxx:function:RotationMultiUser \
    --rotation-rules '{"ScheduleExpression":"rate(7 days)"}'

选型:如果不需要动态凭据、主要是 RDS 场景,AWS SM 完全够用,不用强行上 Vault。

External Secrets Operator(ESO)#

手动在 Pod 里调 Vault API 太繁琐。ESO 以 K8s 原生方式把 Vault/AWS SM/云 KMS 的 Secret 同步成 K8s Secret 对象,应用无感知。

SecretStore#

定义"去哪里取 Secret"——连接配置和认证方式:

yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "http://vault.vault.svc:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "myapp"
          serviceAccountRef:
            name: "myapp-sa"

ExternalSecret#

定义"取哪些 key,同步成什么 K8s Secret":

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-secrets
spec:
  refreshInterval: "15m"     # 每 15 分钟同步一次
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: myapp-secret
    creationPolicy: Owner
    template:
      data:
        DATABASE_URL: "postgresql://{{ .db_user }}:{{ .db_password }}@postgres:5432/mydb"
  data:
    - secretKey: db_user
      remoteRef:
        key: myapp/prod
        property: db_user
    - secretKey: db_password
      remoteRef:
        key: myapp/prod
        property: db_password

Pod 通过普通 env.valueFrom.secretKeyRef 使用,不需要接触 Vault API。

密钥轮换策略#

三条技术路线#

方案适用轮换方式
Vault Dynamic SecretsDB 凭据、IAM、证书按需生成临时凭据,TTL 自动过期
云 SM 原生 RotationRDS、Kafka定时 Lambda 改密 + 双用户交替
SOPS + Git第三方 API key、license手动轮换 + Git 可审计

SOPS:静态密钥的安全版本控制#

SOPS 用 KMS/age 密钥加密 YAML 的 value 部分,key 保留明文便于 diff:

yaml
# secrets.yaml (加密后)
stringData:
  STRIPE_KEY: ENC[AES256_GCM,data:xxxxx,iv:yyyy,tag:zzzz]
sops:
  age:
    - recipient: age1xxxxxxxxxxxx

加密后的文件可以安全地放进 Git 仓库。Flux 原生支持 SOPS 解密,Argo CD 通过插件支持。

轮换工作流#

动态密钥:无需显式轮换,TTL 到期自动失效,应用通过 Vault Agent/ESO 自动获取新凭据。

云 SM 轮换:定时触发,Lambda 改密 + 更新 secret 版本,ESO 同步到 K8s Secret,配合 Reloader 触发 Pod 滚动重启。

SOPS 手动轮换:

  1. 在第三方 dashboard 生成新 key
  2. sops secrets.yaml,替换 value,保存
  3. git commit + push
  4. Flux/Argo 同步到集群,应用 reload
  5. 在 dashboard 禁用旧 key

整个过程有 Git 历史,可审计。可写定时 job 检查每个 secret 的 sops.lastmodified,超 90 天发告警推动轮换。

整体架构#

text
开发者 → 写代码(不涉及 Secret)
  ↓
GitOps 仓库 → 只存 ExternalSecret/SecretStore CRD(无敏感值)
  ↓
ArgoCD 同步 → 在 K8s 创建 ExternalSecret 对象
  ↓
ESO Controller → 读 SecretStore 配置 → 调用 Vault/SM API
  ↓
Vault/SM → 验证身份 → 检查 Policy → 返回 Secret
  ↓
ESO → 创建/更新 K8s Secret
  ↓
Pod → 通过 envFrom/volumeMount 使用 Secret

Git 仓库里永远不会出现敏感值,审计日志记录每次 Secret 访问,凭证有 TTL 自动过期。

常见坑#

Vault Seal/Unseal:Pod 重启后 Vault 进入 sealed 状态,所有请求 503。务必配 Auto Unseal(KMS),否则半夜 Pod 被驱逐要爬起来手动 unseal。

ESO 同步延迟:refreshInterval 只是"最多等多久",实际同步还要看 controller 健康状况。要监控 external_secrets_sync_calls_total{status="error"}。

Secret 轮换不触发重启:ESO 更新了 K8s Secret 后,如果 Pod 通过 env 引用,更新不会自动触发重启。用 Reloader 监听 Secret 变化自动重启 Pod:

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

Rotation 风暴:几百个 Pod 的 Vault lease 同一分钟到期,同一秒向 Vault 请求新凭据,Vault 又同一秒向 PostgreSQL 打几百个 CREATE ROLE,DDL 锁被打满。解决:lease TTL 加随机抖动,TTL 拉长到 4-8 小时。

小结#

密钥管理的核心原则:能动态就动态(Vault Dynamic Secrets),动态不了就自动轮换(云 SM Rotation),静态密钥用 SOPS 加密进 Git 可审计。ESO 是 K8s 密钥注入的标准方案——Git 里只存 ExternalSecret CRD,不存敏感值。Vault 生产必配 Auto Unseal(KMS)+ Audit Log + Snapshot 备份。轮换要防"rotation 风暴"——TTL 加随机抖动。落地建议:不要一上来就搞 Vault,从"凭据清点"开始,优先处理生产 DB 密码/云 root key/支付 API key 这三类"血泪级"密钥。