GitOps 中的密钥管理 - 安全处理敏感信息
GitOps 中的密钥管理 - 安全处理敏感信息
问题的核心:GitOps 与密钥的悖论
让我们再次明确这个核心矛盾:
- Git 是唯一信源: 我们希望应用的整个期望状态,包括它需要加载哪些密钥,都在 Git 中声明。
- Git 不是保险库: 即便是私有 Git 仓库,也存在被克隆、权限泄露、开发者离职后代码留存等风险。将明文的
kind: Secret
YAML 文件存入 Git,无异于将保险库的密码贴在门上。
我们需要的是一种方法,能够让我们在 Git 中安全地存储一个密钥的“表示”或“引用”,同时确保真正的、未加密的密钥值只出现在它应该出现的地方——即运行在 Kubernetes 集群内的应用 Pod 中。
方案一:Sealed Secrets - 将加密后的密钥存入 Git
这是最直观、最容易上手的一种 GitOps 密钥管理方案。
- 核心理念: 一种单向加密模型。
- 一个 Controller(控制器)运行在你的 Kubernetes 集群里。在启动时,它会自动生成一对公钥/私钥。私钥永远不会离开集群。
- 开发者或 CI/CD 流水线在集群外部,使用一个名为
kubeseal
的命令行工具和从集群中获取到的公钥,来对一个普通的、包含明文密码的 KubernetesSecret
YAML 文件进行加密。 - 加密后,会生成一个新的、类型为
SealedSecret
的 YAML 文件。这个文件里的数据是加密过的,因此可以安全地提交到 Git 仓库。 - Argo CD 会像同步其他资源一样,将这个
SealedSecret
对象同步到集群中。 - 集群内的 Sealed Secrets Controller 会监听到这个
SealedSecret
对象,然后使用它自己保管的私钥对其解密,最终在集群中创建一个原生的、包含明文数据的Secret
对象。 - 你的应用程序像往常一样,挂载和使用这个最终生成的原生
Secret
即可。
实战:安装和使用 Sealed Secrets
-
在集群中安装 Sealed Secrets Controller:
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/latest/download/controller.yaml
-
安装
kubeseal
命令行工具:- macOS 用户:
brew install kubeseal
- Linux 用户: (请参考官方文档获取最新命令)
VERSION="v0.26.1" # Use a specific version wget "https://github.com/bitnami-labs/sealed-secrets/releases/download/${VERSION}/kubeseal-${VERSION}-linux-amd64.tar.gz" tar -xvzf kubeseal-${VERSION}-linux-amd64.tar.gz kubeseal sudo install -m 755 kubeseal /usr/local/bin/kubeseal
- macOS 用户:
-
获取加密所需的公钥:
kubeseal
需要使用 Controller 的公钥来进行加密。我们可以从集群中获取它并保存到本地:kubeseal --fetch-cert \--controller-name sealed-secrets-controller \--controller-namespace sealed-secrets \> pub-cert.pem
-
创建并加密一个 Secret:
- 首先,创建一个包含明文密码的普通
Secret
YAML 文件,例如my-db-secret.yaml
。注意:这个文件永远不要提交到 Git!# my-db-secret.yaml (DO NOT COMMIT TO GIT) apiVersion: v1 kind: Secret metadata:name: db-credentialsnamespace: default stringData:password: "SuperSecretPassword123"username: "dbuser"
- 现在,使用
kubeseal
和我们下载的公钥来加密它:kubeseal --cert pub-cert.pem --format yaml < my-db-secret.yaml > my-db-sealed-secret.yaml
- 打开生成的
my-db-sealed-secret.yaml
文件。你会看到它的kind
是SealedSecret
,并且data
字段里的内容是一长串加密后的乱码。这个文件是可以安全提交到 Git 的!
- 首先,创建一个包含明文密码的普通
-
提交到 Git 并让 Argo CD 同步:
将my-db-sealed-secret.yaml
文件添加到你的my-gitops-manifests
仓库中,然后git push
。Argo CD 会自动检测到这个新文件并将其同步到集群。 -
验证结果:
- Argo CD 同步后,Sealed Secrets Controller 会自动解密并创建原生的 Secret。
- 运行
kubectl get secret db-credentials -n default -o yaml
。你会看到data
字段里包含了 Base64 编码后的明文密码。 - 你的应用现在可以正常挂载和使用这个名为
db-credentials
的 Secret 了。
- 优缺点分析:
- 优点: 模型简单清晰,容易理解和上手。安全可靠,私钥不出集群。无任何外部依赖,非常适合起步或中小型项目。
- 缺点: 密钥轮换需要重新加密并提交 Git。密钥的生命周期与 Git commit 绑定。对于跨多个集群的场景,每个集群都有自己的密钥对,管理起来稍显复杂。
方案二:外部密钥管理器集成 (External Secrets Operator)
对于已经在使用或计划采用集中式密钥管理方案(如 HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault)的组织来说,这个模式是更佳的选择。
-
核心理念: 密钥的“真实信源”是外部的专业密钥管理器。Git 中只存储一个指向外部密钥的**“引用”。
-
工作原理 (使用 External Secrets Operator - ESO):
- 将真实的密钥值(如数据库密码)存储在 Vault 或云厂商的密钥管理服务中。
- 在 Kubernetes 集群中安装 ESO 控制器。
- 配置一个
SecretStore
或ClusterSecretStore
资源,告诉 ESO 如何连接和认证到外部的密钥管理器。 - 在 Git 仓库中,我们不再存储
SealedSecret
,而是存储一个类型为ExternalSecret
的 YAML 文件。这个文件描述了:“请从名为vault-backend
的 SecretStore 中,获取路径为secret/myapp/db
的密钥,并用它在集群中创建一个名为db-credentials
的原生 Secret”。 - Argo CD 负责同步这个
ExternalSecret
对象。 - ESO 控制器监听到
ExternalSecret
后,会执行真正的拉取操作,并创建或更新最终的原生Secret
。
-
ExternalSecret
示例:apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata:name: my-app-db-secret spec:secretStoreRef:name: vault-backendkind: ClusterSecretStoretarget:name: db-credentials # 将要创建的原生 Secret 的名字dataFrom:- extract:key: secret/data/myapp/db # 从 Vault 的这个路径下提取所有 key-value
-
优缺点分析:
- 优点: 实现了密钥管理与应用配置的完全解耦。可以利用专业密钥管理器的所有高级功能(如集中审计、自动轮换、动态密钥等)。非常适合跨多个应用、团队和集群的复杂环境。
- 缺点: 增加了外部依赖(需要维护一个密钥管理器),初始设置相对复杂。
方案三:Argo CD Vault Plugin (AVP)
这是一个与 Argo CD 结合更紧密的“即时注入”方案,主要针对使用 HashiCorp Vault 的用户。
- 核心理念: 在 Argo CD 渲染 K8s 清单文件的过程中,动态地从 Vault 中拉取密钥,并直接替换掉清单中的占位符。最终应用到集群的是已经包含了明文密钥的完整清单。
- 工作原理:
- 将 AVP 配置为 Argo CD 的一个自定义配置管理插件。
- 在 Git 仓库的 K8s 清单文件(如
Deployment.yaml
)中,直接使用特殊的占位符来引用 Vault 中的密钥。 - 当 Argo CD 进行同步时,它会先将这些带有占位符的清单交给 AVP 插件处理。
- AVP 插件会连接到 Vault,用真实的密钥值替换掉所有占位符,然后将最终渲染好的、不含占位符的清单返回给 Argo CD,由 Argo CD 将其应用到集群。
- 带占位符的
Deployment
示例:# ... spec:template:spec:containers:- name: my-appenv:- name: DB_PASSWORDvalue: <path:secret/data/myapp/db#password> # AVP 的占位符语法 # ...
- 优缺点分析:
- 优点: 不会在集群中创建中间的原生
Secret
对象,密钥只在内存中存在于应用 Pod 的环境变量里。密钥总是在部署时获取最新的值。 - 缺点: 与 Vault 强绑定。密钥明文会出现在 Argo CD UI 的“渲染后清单”视图中,对 Argo CD 本身的 RBAC 权限控制要求极高。清单文件带有特殊语法,可读性略差,且不易在 Argo CD 环境之外使用。
- 优点: 不会在集群中创建中间的原生
对比与选择
方案 | 核心模型 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
Sealed Secrets | 加密后存入 Git | 简单、自包含、无外部依赖 | 密钥轮换需手动、与集群绑定 | 中小型项目、单集群、不希望引入外部依赖 |
External Secrets | 从外部源同步 | 集中管理、功能强大、解耦、可扩展性好 | 初始设置复杂、有外部依赖 | 企业级、多集群、已在使用或计划使用集中式密钥管理 |
Argo CD Vault Plugin | 渲染时注入 | 无中间 Secret、密钥最新、与应用部署同步 | 强绑定 Vault、UI 可能暴露密钥、可移植性差 | 重度 Vault 用户、偏好即时注入模型的团队 |
总结
今天,我们成功地解决了 GitOps 流程中最棘手的密钥管理悖论。我们认识到,没有“银弹”,但有多种成熟且安全的模式可供选择。我们动手实践了Sealed Secrets,并深入理解了External Secrets Operator和Argo CD Vault Plugin这两种更高级的模式。为你的项目选择合适的密钥管理策略,是实施严肃的、生产级 GitOps 的一个至关重要的架构决策。
至此,我们的 GitOps 系统已经功能完备、配置优雅、部署安全、密钥无忧。那么,作为一个 SRE,我们还需要关心什么呢?当然是系统本身的日常运维、监控、备份恢复和最佳实践。