使用SOPS管理 Kubernetes部署中的Secrets

117 阅读5分钟

核心架构概览

该方案的核心思想是在应用 Pod 启动前,通过一个临时的 InitContainer动态解密并创建 Kubernetes Secrets。

• SOPS 加密文件: 将包含敏感数据的 values.yaml文件(例如 secret-values.yaml)使用 SOPS 和 Age (或 PGP) 进行加密。加密后的文件可以安全地提交到 Git 仓库。

• Helm Chart: 一个标准的 Helm Chart,但经过特殊设计,能够接收加密文件和相关配置。

• 解密 Init 容器 (The "Magic Container"): 这是一个自定义的 Docker 镜像,其中打包了 SOPS和 kubectl两个关键工具以及一个解密脚本。它作为 InitContainer在主应用容器之前运行。

• 解密密钥: 用于解密的私钥(如 Age key)被预先存储在目标命名空间的一个标准 Kubernetes Secret 中。

• RBAC 权限: Chart 中包含一个 ServiceAccount、Role和 RoleBinding,授予 Init 容器足够的权限(主要是创建和管理 Secret对象)来执行解密和创建操作。

工作流程揭秘

1. 部署触发: 开发者通过 ArgoCD 发起部署。ArgoCD 从 Git 仓库拉取 Helm Chart 和相关的配置文件。

2. 传递加密文件: ArgoCD 使用 Helm 的 --set-file参数,将 SOPS 加密后的文件内容传递给 Helm Chart。这个加密内容被临时存储在一个名为 -encrypted-secret的 Secret 中。

3. Init 容器启动: 当应用的 Pod 开始创建时,解密 InitContainer首先启动。

4. 解密与创建:

• InitContainer从 -age-keysSecret 中读取解密私钥。

• 它再读取 -encrypted-secret中的加密数据。

• 利用内置的 SOPS 工具和私钥,在容器内解密数据。

• 最后,使用内置的 kubectl工具,将解密后的明文数据动态创建一个新的、标准的 Kubernetes Secret,例如 -secret。

5. 主容器启动: InitContainer成功退出后,主应用容器启动。此时,它可以像往常一样挂载和使用刚刚由 Init 容器创建的 -secret。

实战指南:具体步骤与代码

以下我们将通过一个具体场景,演示如何从零开始配置和部署。

1. 前提准备 (Prerequisites)

确保你已安装并配置好以下工具:

• SOPS

• Helm

• kubectl

• Docker

• ArgoCD

2. 生成加密密钥 (Age)

首先,我们使用 age生成一对公私钥。私钥将用于解密。

# 生成一个名为 age-key.txt 的密钥文件
age-keygen -o age-key.txt

注意: age-key.txt包含了你的私钥,绝对不能将其提交到 Git 仓库。请务必将其添加到 .gitignore文件中。

3. 准备并加密敏感数据文件

创建一个用于存放敏感信息的文件,例如 secret-values.yaml:

# secret-values.yaml
data: "some secret you want to protect"
another_secret: "password123"

然后,使用 SOPS 和之前生成的公钥(可以从 age-key.txt文件中查看,以 age1开头)来加密此文件。

# 使用 -e (encrypt) 参数进行加密
# --age 后面跟你的公钥
sops --encrypt --age 'age1...' secret-values.yaml > secret-values.enc.yaml

现在你可以安全地将 secret-values.enc.yaml提交到 Git。

4. 在 Kubernetes 中创建解密密钥 Secret

将 age-key.txt的全部内容存入一个 Kubernetes Secret 中。这个 Secret 需要和你的应用部署在同一个 namespace。

# age-key-secret.yaml
apiVersion:v1
kind:Secret
metadata:
# Secret 名称需遵循 <chart-name>-age-keys 的格式
# 如果你的 chart 最终叫 myapp,这里就是 myapp-age-keys
name:myapp-age-keys
namespace:your-app-namespace# 替换成你的命名空间
type:Opaque
stringData:
# 键名必须是 age-key.txt
age-key.txt: |
    # Created: 2024-08-09T10:00:00Z
    # public key: age1...
    AGE-SECRET-KEY-1... # 从 age-key.txt 文件粘贴全部内容

应用它:kubectl apply -f age-key-secret.yaml

5. 配置 Helm Chart 和 ArgoCD

在你的 Helm Chart 的 values.yaml(或一个自定义的 custom-values.yaml) 文件中,配置 initContainer和其他参数。

# values.yaml

# 使用 nameOverride 来统一所有资源的名称前缀
nameOverride:"myapp"

# 主应用的配置...
image:
repository:nginx
tag:latest

# 解密 Init Container 的配置
initContainer:
image:mrupnikm/olm-chart-sops-decryption:latest# 使用作者提供的或自建的镜像
k8s_args:"-n your-app-namespace"# 指定操作的命名空间
encrypted_secret:
    # 如果使用 age,只需提供一个非空值即可
    age:"x"

# 这个字段留空,ArgoCD 会通过 fileParameters 填充它
extraSecretFile: ""

在你的 ArgoCD Application argo.yaml中,你需要使用 helm.fileParameters来指定加密文件。

# argo.yaml
apiVersion:argoproj.io/v1alpha1
kind:Application
metadata:
name:my-web-app
namespace:argocd
spec:
project:default
source:
    repoURL:'https://github.com/your/repo.git'# 你的 Git 仓库地址
    targetRevision:HEAD
    path:'path/to/your/helm-chart'# Helm Chart 所在的路径
    helm:
      valueFiles:
        -values.yaml
      # 关键部分:使用 fileParameters 将加密文件内容传递给 --set-file
      fileParameters:
        -name:extraSecretFile# 对应 values.yaml 中的 extraSecretFile
          path:secret-values.enc.yaml# 加密文件在 Git 仓库中的路径
destination:
    server:'https://kubernetes.default.svc'
    namespace:your-app-namespace# 应用部署的目标命名空间
syncPolicy:
    automated:
      prune:true
      selfHeal: true

6. 部署应用

最后,将 ArgoCD 应用清单提交到集群:

argocd app create -f argo.yaml

ArgoCD 将会自动同步应用,执行上述工作流,最终你的应用将能成功挂载并使用解密后的 Secret。

总结与思考

本文介绍的方法巧妙地利用了 Kubernetes 的 InitContainer和 Helm 的 --set-file特性,构建了一个无需 ArgoCD 插件即可自动化处理 SOPS 加密文件的 GitOps 流程。

• 优点:

原生兼容: 无需为 ArgoCD 安装或配置任何插件,降低了维护复杂性。

安全: 敏感信息以加密形式存储在 Git 中,符合 GitOps 最佳实践。

灵活: 可以通过修改 InitContainer中的脚本来适应不同的 Secret 结构或解密逻辑。

• 注意事项:

• 部署完成后,手动创建的解密密钥 Secret (myapp-age-keys) 不受 ArgoCD 管理,如果项目清理,需要手动删除。

• 该方案引入了一个自定义的 InitContainer镜像,需要确保该镜像的来源可靠并进行妥善维护。

总而言之,这并非管理 Kubernetes Secrets 的唯一方案(其他方案如 External Secrets Operator, HashiCorp Vault 等也十分优秀),但它为特定场景——尤其是追求环境简洁、希望避免引入过多外部依赖的团队——提供了一个极具参考价值的、轻量级的实现范本。