项目代码放进 Git 后,最难交接的往往是一份没进仓库的配置。数据库密码在某个人的电脑里,部署令牌存在聊天记录中,生产环境用的文件又与开发环境不同。机器换了,服务还能运行;负责部署的人离开了,下一次发版却可能卡在找密码这一步。
把 .env 加入 .gitignore 只能防止正常提交,解决不了配置版本、协作和恢复问题。另一种做法是把加密后的配置放进仓库:代码变更和配置变更一起审阅,获得授权的人在使用时解密。SOPS 负责编辑和保存这些文件,age 提供加密身份。两者配合适合小团队,也适合一个人维护几台服务器的项目。
这套方法需要先想清楚谁可以解密、私钥在哪里、密码泄露时如何撤销。安装工具只占很小一部分工作。
仓库里留下密文,解密身份留在仓库外
SOPS 官方文档介绍了不同文件格式和密钥后端。这里选 YAML 配置和 age,避免一开始引入云平台权限。应用读取明文配置,Git 保存加密版本,开发者或部署环境持有解密身份。
age 的公钥可以分享,用来指定接收者;私钥必须单独保管。SOPS 文件还会保存加密元数据,因此只有业务字段看起来像密文并不够,文件尾部的元数据也不能随意删改。审阅时可以看到字段结构,但密文不会告诉审阅者新密码是否符合应用要求。这类要求仍需在受控环境验证。
加密不能挽救已经公开的密码。曾经提交过明文,或者在 CI 日志里输出过令牌,应先在对应服务撤销或更新凭据。删除 Git 的最新文件不会让历史副本消失。
先用演示配置走通一次
按 SOPS 安装文档和 age 项目说明安装工具,确认 sops --version 与 age-keygen --version 能运行。下面以类 Unix 系统为例,先用虚构密码练习,不接生产数据库。
umask 077
mkdir -p "$HOME/.config/sops/age"
age-keygen -o "$HOME/.config/sops/age/keys.txt"
export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt"
命令会显示一个以 age1 开头的接收者公钥,私钥写进指定文件。备份私钥时,应放进受控的密码库或离线加密介质,并验证自己能读回。将它复制到同一个 Git 仓库,即使换了文件名,也会让这套隔离失去意义。
在项目根目录建立 .sops.yaml,将下面的占位内容替换成刚生成的完整公钥:
creation_rules:
- path_regex: ^secrets/.*\.enc\.yaml$
age: age1REPLACE_WITH_YOUR_PUBLIC_RECIPIENT
这条规则限定 secrets/ 下以 .enc.yaml 结尾的文件。路径相对规则文件所在目录匹配;项目移动目录或增加嵌套配置时,需要重新检查匹配结果。不要用一个不受限制的兜底规则,让所有环境默默落到同一批接收者。
创建 secrets/demo.enc.yaml,暂时写入演示内容:
database:
username: demo_app
password: demo-password-not-for-production
随后加密并检查:
sops encrypt --in-place secrets/demo.enc.yaml
sops decrypt secrets/demo.enc.yaml
sops edit secrets/demo.enc.yaml
第一次加密前,这个文件仍是明文,不能提交。加密完成后打开文件,确认密码已变为加密值,并存在 SOPS 元数据;再用 git diff 检查待提交的真实内容。编辑时让 SOPS 打开文件,保存后由它重新加密,不要手动剪贴密文。
演示成功还不能说明恢复可靠。临时取消私钥路径,并在没有默认身份文件的隔离环境尝试解密,确认缺少授权身份时确实失败。随后从备份恢复身份,确认能够解密。这个练习能提前发现“备份里只保存了公钥”一类问题。
开发、生产与 CI 应有各自的接收者
只有一个人的项目可以先使用一个身份,但生产权限扩大后,最好分出开发和生产规则。开发人员不必因为需要本地调试,就获得生产邮件账户或云存储密钥。CI 也应有独立身份,便于单独撤销。
creation_rules:
- path_regex: ^secrets/production/.*\.enc\.yaml$
age: >-
age1REPLACE_WITH_PRODUCTION_OPERATOR,
age1REPLACE_WITH_DEPLOYMENT_RECIPIENT
- path_regex: ^secrets/development/.*\.enc\.yaml$
age: age1REPLACE_WITH_DEVELOPMENT_RECIPIENT
示例中的接收者都需要替换,不能直接运行。规则先后顺序有意义,具体行为见 SOPS 配置文档。新增环境时,把规则变更与加密文件放进同一次审阅,说明新增的是谁的权限。公钥的名字可以写在内部登记表里,否则半年后只剩下一串 age1...,很难判断是否可以删除。
每个接收者通常都能独立解密对应文件。这不是两个人共同批准后才能解密的机制。团队需要双人审批时,应在部署权限和变更审批上落实,不能凭两个公钥推断出双人授权。
让应用拿到配置,又少留一份明文
应用启动前解密到固定目录最容易理解,但也最容易留下忘记清理的文件。确实需要文件时,目录应由服务账户控制,文件权限设为仅必要账户可读,备份和故障排查流程也要认识这个明文位置。不要将它放进会被上传的构建产物目录。
对于能够从环境变量读取配置的命令,可以使用 exec-env。官方支持的一个直接形式是加密 JSON 对象:
{
"APP_DATABASE_PASSWORD": "demo-password-not-for-production",
"APP_API_TOKEN": "demo-token-not-for-production"
}
为对应文件补充正确的创建规则并加密后,执行:
sops exec-env secrets/runtime.enc.json './bin/start-app'
start-app 应从环境读取值,不能打印完整环境用于“调试”。执行命令文档说明了 exec-env 和 exec-file 的使用方式。环境变量减少了磁盘上的明文副本,但仍可能被同权限或高权限进程看到,不能因此放宽部署账户的权限。
CI 将 age 私钥保存为受保护的机密变量,运行时写进权限受限的临时文件,再设置 SOPS_AGE_KEY_FILE。清理动作必须覆盖失败路径。不要启用会回显命令参数的调试模式,不要把解密结果作为 artifact,不要允许来源不明的合并请求运行拥有生产私钥的任务。
密码更新还应检查应用实际行为。例如已经初始化的数据库,不一定会因为容器环境变量改了就更新账户密码。修改密文、提交代码、重新部署只是配置链路;数据库、邮件服务或第三方平台里的凭据变更需要按照各自机制完成。
加一个人和撤销一个人,是两种操作
新增成员时,先核实公钥属于正确的人,再修改 .sops.yaml。已有文件不会因为规则文件变化就自动获得新接收者,需对相关文件执行:
sops updatekeys secrets/production/app.enc.yaml
检查提示和差异,再让新成员独立验证解密。这比把自己的私钥发给对方更容易管理。
人员离职或私钥泄露时,先从规则移除其接收者,再更新相关文件的密钥信息,随后轮换文件的数据密钥:
sops updatekeys secrets/production/app.enc.yaml
sops rotate --in-place secrets/production/app.enc.yaml
这个顺序来自 SOPS 密钥管理文档。它影响新的文件版本,不能夺回别人已经保存的旧密文与旧私钥,更不能抹掉已经读过的密码。因此还需要轮换文件里的真实业务凭据,更新运行中的服务,并检查旧凭据确实失效。
Git 历史需要处理时,应作为独立工作安排。改写历史会影响其他克隆和自动化引用,不能把它当成已经完成凭据撤销的证据。
如果开发者换了电脑,如何判断缺的是哪一环
先不要修改仓库密文。检查本机 SOPS_AGE_KEY_FILE 是否指向存在且可读的身份文件,再确认身份对应的公钥仍在文件接收者中。一个常见情况是新电脑生成了新身份,但仓库仍只有旧公钥。新身份不能解密旧文件,需要拥有现有解密权限的人更新接收者;把新私钥路径设置正确也不能补出权限。
若演示文件能解密而生产文件失败,可能是环境规则有意隔离。此时应核对授权,不能把开发公钥直接加进生产规则来“修好报错”。若所有文件都失败,则检查工具版本、身份来源与文件完整性。复制密文时丢失元数据,或者把 YAML 的结构手动改坏,也会造成问题。
编辑器还可能留下交换文件、恢复文件或自动保存副本。SOPS 启动编辑器时会处理明文,具体残留取决于编辑器和系统行为。正式处理秘密之前,在演示目录验证编辑器的保存方式,安排缓存目录与备份范围。只查看 Git 暂存区并不能发现这些仓库外副本。
多人协作中,同一个加密文件的不同修改可能产生难读的合并冲突。不要在密文中手动挑几段拼起来。让有权限的维护者基于一份可解密的版本重做另一份业务变更,随后重新加密,验证字段与接收者。合并完成以后,再执行应用配置校验,而不是只确认 Git 不再显示冲突标记。
审阅与恢复也要有人能执行
加密文件的 PR 可以审查字段是否增加、环境是否正确、接收者是否扩大;密文内容本身需要有权限的人在本地验证。不要为了让每个审阅者方便,就把解密内容贴进 PR 评论。可以报告“连接验证通过、字段名正确、旧凭据已撤销”,同时把必要的受控证据保存在内部记录里。
恢复时至少需要仓库中的加密配置、对应版本的规则、有效解密身份,以及应用理解这些字段所需的版本信息。仅有运行中容器的镜像不能恢复密钥。备份身份后,找一台隔离机器恢复演示服务,确认不是依赖原电脑的默认路径或钥匙串才成功。
当这套流程稳定下来,一次配置变更应能回答几个具体问题:它属于哪个环境,由谁授权,部署使用哪个版本,旧凭据何时失效,机器全部丢失后从哪里恢复。SOPS 与 age 让密文能够随代码管理,剩下的权限和恢复流程需要维护者继续负责。











