饮墨

子安饮墨馀三斗,留与卿儿作赋来

用 SOPS + age 加密 GitOps 密钥:3 步告别明文 Secret 提交

痛点

GitOps 工作流要求「一切皆代码、一切进 Git」,但 Kubernetes Secret、数据库密码、API Token 这类敏感信息怎么办?直接明文提交是安全事故的温床;用 Vault 全托管又引入额外基础设施和运维成本。

现实中最常见的反模式:

  • .env 文件写入 .gitignore,部署时手动拷贝 —— 无法审计变更历史
  • Secret 经 CI 变量注入,YAML 里留占位符 —— 本地调试困难、CI 平台成为单点
  • 直接把 base64 编码(≠加密)后的 Secret 推进仓库 —— 等于裸奔

SOPS(Secrets OPerationS)+ age 组合恰好解决这个问题:在 Git 中存储加密后的密钥文件,解密权限通过 age 私钥控制,零外部依赖、纯文件级操作。

方案概览

组件 作用 特点
SOPS 加密/解密引擎 仅加密 value 不加密 key,保留 YAML/JSON 结构可读性
age 密钥体系 现代替代 PGP,无需 keyserver,单文件密钥,X25519 算法
Flux/ArgoCD GitOps 控制器 原生支持 SOPS 解密,部署时自动还原明文

整体流程:开发者用 age 公钥加密 → 密文推入 Git → GitOps 控制器用私钥解密 → 注入 Kubernetes 集群。

实操步骤

第 1 步:安装 SOPS + age 并生成密钥对

# 安装 age(macOS / Linux)
# macOS
brew install age sops
# Linux amd64
curl -LO https://github.com/FiloSottile/age/releases/download/v1.2.0/age-v1.2.0-linux-amd64.tar.gz
tar xf age-v1.2.0-linux-amd64.tar.gz && sudo mv age/age* /usr/local/bin/

# 安装 SOPS
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
chmod +x sops-v3.9.4.linux.amd64 && sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops

# 生成 age 密钥对
age-keygen -o ~/.config/sops/age/keys.txt
# 输出示例:
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

记录公钥(age1...),私钥文件保存在本地,绝不提交到 Git

第 2 步:配置 .sops.yaml 并加密 Secret

在仓库根目录创建 .sops.yaml,声明加密规则:

# .sops.yaml
creation_rules:
  # 匹配路径中含 secret 的文件
  - path_regex: .*secret.*\.yaml$
    age: >-
      age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
      age1second-recipient-public-key-for-ci-server
  # 可为不同环境指定不同密钥
  - path_regex: envs/prod/.*\.yaml$
    age: age1prod-only-key...

加密 Kubernetes Secret 文件:

# 原始明文文件 db-secret.yaml
cat <<EOF > db-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
stringData:
  username: admin
  password: "S3cur3P@ss!"
  connection_string: "postgresql://admin:S3cur3P@ss!@db.internal:5432/app"
EOF

# 加密(仅 stringData 下的 value 被加密,key 保留可读)
sops --encrypt --in-place db-secret.yaml

加密后文件结构(注意 key 名称仍然可读):

apiVersion: v1
kind: Secret
metadata:
    name: db-credentials
    namespace: production
type: Opaque
stringData:
    username: ENC[AES256_GCM,data:kDj2fw==,iv:...,tag:...]
    password: ENC[AES256_GCM,data:r8K2mNqz...,iv:...,tag:...]
    connection_string: ENC[AES256_GCM,data:bH7xY2...,iv:...,tag:...]
sops:
    age:
        - recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...

现在可以安全地 git add db-secret.yaml && git push

第 3 步:GitOps 控制器集成解密

Flux 方案(推荐):

# 将 age 私钥作为 Secret 导入集群
cat ~/.config/sops/age/keys.txt | kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey=/dev/stdin

# 在 Flux Kustomization 中启用解密
cat <<EOF | kubectl apply -f -
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: app-secrets
  namespace: flux-system
spec:
  interval: 5m
  path: ./k8s/secrets
  prune: true
  sourceRef:
    kind: GitRepository
    name: main-repo
  decryption:
    provider: sops
    secretRef:
      name: sops-age
EOF

Flux 会自动解密 Git 中的 SOPS 加密文件,将明文 Secret 应用到集群。

避坑指南

坑 1:age 私钥丢失 = 所有密文永久不可恢复

解决: 私钥备份至少两个独立安全位置(如硬件密钥 + 密码管理器)。生产环境建议配置多 recipient(多把公钥均可解密),避免单点故障:

# .sops.yaml 中配置多个 recipient
age: >-
  age1-primary-key,
  age1-backup-key,
  age1-ci-server-key

坑 2:sops --encrypt 误加密整个文件导致不可读

解决: 使用 encrypted_regex 限定只加密特定字段:

creation_rules:
  - path_regex: .*secret.*\.yaml$
    age: age1...
    encrypted_regex: "^(data|stringData)$"

这样 metadataapiVersion 等字段保持明文,PR Review 时仍能看到 Secret 名称和命名空间变更。

坑 3:本地编辑加密文件时忘记用 sops 命令

解决: 永远用 sops db-secret.yaml 打开(自动解密到编辑器,保存时自动重加密)。可配合 Git hook 检查:

# .git/hooks/pre-commit
#!/bin/bash
for file in $(git diff --cached --name-only | grep secret); do
  if ! grep -q "^sops:" "$file"; then
    echo "ERROR: $file 未加密!请用 sops --encrypt 处理"
    exit 1
  fi
done

总结

维度 SOPS + age Vault Sealed Secrets
外部依赖 需 Vault Server 需集群内 Controller
学习成本 低(3 条命令)
密钥轮换 手动 sops updatekeys 自动 重新 seal
审计追踪 Git History Vault Audit Log Git History
适用规模 小~中团队 中~大企业 单集群

核心结论: 对于 10 人以下 DevOps 团队、不想引入额外基础设施的场景,SOPS + age 是投入产出比最高的 GitOps 密钥管理方案。3 条命令上手,Git 原生审计,Flux/ArgoCD 原生支持,零运维成本。

密钥管理的黄金法则:加密成本永远低于泄露成本。 今天就把仓库里的明文 Secret 迁移到 SOPS 吧。

您还没有登录,请登录后发表评论。