环境变量是大多数应用程序默认的密钥管理解决方案,但对于大多数生产系统来说,它们并不足够。它们会泄露到进程列表中,被提交到 .env 文件的版本控制中,出现在崩溃报告和调试日志中,并且无法在不重新部署的情况下进行轮换。然而,许多运行着重要生产工作负载的团队仍然以这种方式管理密钥,主要是因为采用更好的替代方案感觉过于复杂。
本文将探讨真实的选项——HashiCorp Vault、Mozilla SOPS、云原生密钥管理器和新兴的密封密钥模式,并提供足够的实现细节进行真正的比较。目标是为你提供一个清晰的决策框架,而不是一份工具列表。
为什么环境变量在大规模场景下会失败
在讨论替代方案之前,值得明确指出环境变量的具体失败模式:
- 进程列表暴露: 在 Linux 上,
/proc/<pid>/environ可被具有适当权限的用户读取。在共享主机或多租户环境中,一个进程的环境变量可能被其他进程读取。 - 日志泄露: 异常处理程序、调试中间件和监控代理经常将进程环境作为错误上下文的一部分转储。一个配置错误的错误跟踪器就可能导致你的数据库密码出现在日志系统中。
- .env 文件事件: .env 文件被提交到公共 GitHub 仓库并被凭据扫描器抓取的次数不容忽视。GitGuardian 的 2025 年报告发现,2024 年有超过 1000 万个新密钥被提交到公共仓库中。
- 不重新部署就无法轮换: 轮换数据库密码意味着更新使用它的所有地方的环境变量——每个 Kubernetes pod、每个 Lambda 函数、每个 EC2 实例——并重新部署。这种摩擦导致轮换频率低,这意味着受损的凭据保持有效的时间更长。
- 没有审计跟踪: 没有内置记录来跟踪谁访问了哪个密钥、何时访问以及从哪个系统访问。这对任何受监管的工作负载来说都是一个合规问题。
四层密钥管理格局
密钥管理解决方案位于四个广泛的复杂度层级:
- 版本控制中的加密文件 (SOPS): 密钥被加密后与代码一同存储。简单、可审计,无需外部基础设施。
- 云原生密钥管理器 (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault): 基于IAM访问控制、自动轮换和审计日志的托管服务。如果您已经选择云服务商,这是最佳选择。
- 自托管密钥管理器 (HashiCorp Vault): 功能全面、与提供商无关,支持动态密钥、PKI管理和丰富的访问策略。但运营开销较高。
- GitOps原生密封密钥 (Sealed Secrets, External Secrets Operator): Kubernetes原生模式,将密钥管理集成到GitOps工作流中。
SOPS:版本控制中的加密密钥
Mozilla SOPS (Secrets OPerationS) 使用 AWS KMS、GCP KMS、Azure Key Vault 或 age 密钥加密密钥文件。加密后的文件存储在您的 git 仓库中。解密在部署时使用云 IAM 凭据或 age 私钥进行。
关键点:SOPS 只加密 YAML/JSON/ENV 文件中的值,而不是键。这意味着代码审查中的差异是可读且有意义的 — 您可以看到键被添加或删除,而无需看到其值。
# 安装 SOPS
brew install sops # macOS
# 或: apt install sops / 从 GitHub releases 下载
# 创建 age 密钥(小型团队最简单的设置)
age-keygen -o ~/.config/sops/age/keys.txt
# 已创建: /Users/you/.config/sops/age/keys.txt
# 公钥: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# .sops.yaml — 配置哪些文件使用哪些密钥
creation_rules:
- path_regex: secrets/.*.yaml$
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
- path_regex: secrets/prod/.*.yaml$
# 生产环境使用 AWS KMS 以获得更好的密钥管理
kms: arn:aws:kms:us-east-1:123456789:key/mrk-abc123
# 加密密钥文件
cat > secrets/database.yaml <<EOF
database_url: postgres://user:password@host:5432/db
api_key: sk-live-abc123def456
jwt_secret: very-secret-signing-key
EOF
sops --encrypt --in-place secrets/database.yaml
# 加密后的文件可以安全提交 — 值已加密,键可见
# 部署时解密(环境中需要 age 密钥)
sops --decrypt secrets/database.yaml | envsubst
SOPS 适用于希望将密钥放在版本控制中,并使用与代码相同的 Git 工作流(PR、审查、历史记录)的团队。其限制是解密密钥必须存在于部署发生的任何地方,并且没有内置的密钥级访问审计(只有 KMS 密钥级)。
AWS Secrets Manager:AWS 项目的正确默认选择
如果您的基础设施运行在 AWS 上,Secrets Manager 是务实的选择。它原生支持 RDS 数据库的自动轮换,与 IAM 集成实现细粒度访问控制,并且费用为每密钥每月 0.40 美元——价格足够低,对于典型应用来说成本不是考虑因素。
# 存储密钥
aws secretsmanager create-secret
--name "prod/myapp/database"
--secret-string '{"username":"myapp","password":"s3cr3t","host":"db.example.com"}'
# 为 RDS 密钥启用自动轮换
aws secretsmanager rotate-secret
--secret-id "prod/myapp/database"
--rotation-lambda-arn arn:aws:lambda:us-east-1:123:function:SecretsManagerRDSRotation
--rotation-rules AutomaticallyAfterDays=30
# 在应用代码中获取密钥 — 无需环境变量
import boto3
import json
def get_db_credentials() -> dict:
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId='prod/myapp/database')
return json.loads(response['SecretString'])
# 对于 Kubernetes:使用 External Secrets Operator 同步到 K8s Secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: prod/myapp/database
property: connection_string
External Secrets Operator 模式很重要:它会自动将云密钥管理器的值同步到 Kubernetes Secrets,并按计划轮换它们。您的 Pod 引用 Kubernetes Secret;操作员负责保持其最新状态。
HashiCorp Vault:当您需要提供商独立性时
当您需要云原生管理器之外的功能时,Vault 是值得考虑的选择:动态密钥(按需生成且具有 TTL、使用后自动撤销的凭据)、多云密钥联合、PKI 证书管理,或用于数据库加密密钥管理的 KMIP。
大多数团队没有充分利用的功能是动态密钥。与存储定期轮换的数据库密码不同,Vault 为每个服务实例生成一个具有可配置 TTL 的唯一数据库凭证:
# 为 PostgreSQL 配置 Vault 数据库密钥引擎
vault secrets enable database
vault write database/config/postgres
plugin_name=postgresql-database-plugin
allowed_roles="app-role"
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/app_db"
username="vault_admin"
password="vault_admin_password"
vault write database/roles/app-role
db_name=postgres
creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO "{{name}}";"
default_ttl="1h"
max_ttl="24h"
# 每个服务请求都会生成一个有效期为 1 小时的唯一凭证
vault read database/creds/app-role
# Key Value
# --- -----
# lease_id database/creds/app-role/Hyu1...
# lease_duration 1h
# password A1a-generated-unique-password
# username v-app-role-xyz123abc
使用动态密钥,被泄露的凭证在其 TTL 过期后会自动失效。无需轮换密码,因为每个凭证在设计上都是临时的。这比定期轮换静态密码具有更强大的安全态势。
Vault 的运营成本是真实的:您需要一个高可用的 Vault 集群(至少 3 个节点)、定期的解封程序、监控以及 Vault 特定的运营专业知识。对于大多数不需要动态密钥或多云支持的团队来说,对于等效的静态密钥管理,云原生管理器的成本更低。
Kubernetes Sealed Secrets 用于 GitOps
Sealed Secrets(来自 Bitnami/VMware)解决了在 Git 中安全存储 Kubernetes Secrets 的特定问题。它使用集群中运行的一个控制器来持有私钥。您使用相应的公钥将您的密钥加密为 SealedSecret 自定义资源,该资源可以安全地提交。只有集群的控制器可以解密它。
# 安装 Sealed Secrets 控制器
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# 获取集群的公钥
kubeseal --fetch-cert > pub-cert.pem
# 创建 Kubernetes Secret 并将其密封
kubectl create secret generic my-app-secret
--from-literal=api_key=sk-live-abc123
--dry-run=client -o yaml |
kubeseal --cert pub-cert.pem --format yaml > sealed-secret.yaml
# sealed-secret.yaml 可以安全地提交到 git
# 将其应用到集群 — 控制器解密并创建真实的 Secret
kubectl apply -f sealed-secret.yaml
Sealed Secrets 是纯 GitOps 工作流程的理想选择,在这种工作流程中,您希望所有集群状态(包括密钥)都在 Git 中定义。其限制在于密封密钥与特定集群的私钥绑定——您不能在不为每个集群重新密封的情况下在多个集群中使用相同的密封密钥。
决策矩阵
| 场景 | 推荐方法 |
|---|---|
| 小型团队,单一云平台(AWS) | AWS Secrets Manager + External Secrets Operator |
| GitOps 工作流程,Kubernetes | Sealed Secrets 或 External Secrets Operator |
| 多云或本地部署 | HashiCorp Vault |
| 密钥需要存储在 Git 中 | SOPS 与 age 或 KMS |
| 高安全性,需要动态凭证 | HashiCorp Vault 动态密钥 |
| 最大简化,小型项目 | SOPS 与 age 密钥 |
从环境变量迁移的路径
将现有应用程序从环境变量迁移是一个分阶段的过程:
- 审计: 找出环境变量中的每个密钥。
env | grep -iE 'key|secret|password|token|credential'是一个有用的起点。 - 分类: 将密钥(需要轮换、审计跟踪)与配置(数据库主机、日志级别)分开——并非环境变量中的所有内容都是密钥。
- 选择您的存储: 基于上述矩阵。
- 抽象检索: 为您的应用程序添加一个从新存储读取的密钥检索层。在过渡期间为本地开发保留环境变量回退。
- 迁移后立即轮换: 将密钥迁移到适当的存储后要做的第一件事就是轮换它。假设环境变量版本已被泄露。
结论
环境变量是一种便利,而非安全控制。解决方案已经存在,它们很成熟,而且大多数的操作复杂性并不如其声誉所示。对于 AWS 用户来说,AWS Secrets Manager 几乎不需要运营开销。SOPS 只需要一个密钥文件和一次性配置。Sealed Secrets 为您现有的 Kubernetes 工作流程添加了一个 Helm chart 和一个 CLI 工具。
密钥事件(凭证泄露、数据泄露、合规违规)的成本持续超过适当密钥管理投资的数个数量级。为您的基础设施层级选择正确的工具,并首先迁移最高价值的密钥。运营模式已被充分理解;不行动的风险不是假设性的。
