ansible-vault — 可以放进仓库的密钥
一句话总结
Vault 不是把秘密移到仓库外部的工具,而是将其以密文形式放在仓库内部,仅在运行时解密的工具。
为什么需要它
把 .env 加入 .gitignore 并不会让它变得安全。在同一主机上,环境变量可从 /proc/PID/environ 直接读取,还可能流入崩溃报告、CI 日志和容器镜像层。更重要的问题仍未解决——**秘密泄露后,能否在几分钟内撤销并更换该密钥?**大多数团队的回答是‘不知道’,因为没人清楚它被复制到了多少地方。
Vault 解决了这个问题的一半。秘密与代码位于同一个仓库,因此可以统计它们的位置和数量,可以接受评审,也会留下历史记录。另一半(自动轮换、短期凭据)则属于外部秘密管理器的范畴。
它如何工作
Vault 使用 AES256 对称密钥加密。密钥是一个密码,可通过文件或提示输入。
| 命令 | 作用 |
|---|---|
ansible-vault create |
创建新的加密文件 |
ansible-vault encrypt |
加密现有明文文件 |
ansible-vault view |
只解密并显示内容 |
ansible-vault edit |
解密 → 编辑 → 重新加密 |
ansible-vault rekey |
用新密码重新加密 |
ansible-vault encrypt_string |
仅加密单个值,以内联形式放入 YAML |
加密文件第一行是 $ANSIBLE_VAULT;1.1;AES256 形式的头部。没有这一行,文件就是明文。审计时首先检查的就是这个头部。
encrypt_string 只加密一个值,而不是整个文件。这样变量文件的其余部分仍为明文,diff 依然可读,只有秘密保留为密文。在实际工作中,这种方式更常用。
--vault-id 用标签区分多个密钥。写成 prod@파일 之类的形式后,标签会写入头部,可防止混用生产密钥和开发密钥而引发事故。
**处理秘密的任务必须添加 no_log: true。**否则费力加密的值会以明文出现在运行日志中。日志收集系统通常比应用拥有更宽泛的访问权限。
实际工作中常见的情况
**第一,密码文件的位置。**把 .vault_pass 放在仓库中,就等于把钥匙贴在锁旁边。务必将其加入 .gitignore,并把权限设为 600。
第二,先撤销。如果发现秘密曾以明文提交,正确顺序是撤销 → 调查影响 → 清理历史。先重写历史的顺序是错的——公开的值可能已经被自动扫描器收集,并残留在 fork、clone 和 CI 缓存中。清理历史不是逆转泄露的措施,而是减少再次发生的卫生工作。
**第三,rekey 会使旧密钥失效。**rekey 后,旧密码无法再打开该文件。如果密钥由多人共用,就必须协调更换时间。
秘密泄露的其他通道
即使加密了文件并添加 no_log,仍有一些值可能流出的通道。最好把它们写成检查清单。
**命令行参数。**如果把值作为命令参数传递,同一主机上的其他用户可直接从进程列表看到它。若可通过文件或标准输入传递,就应采用那些方式。
**Shell 历史记录。**手动输入的命令中若包含值,它会原样保存在文件中,而该文件通常也会被备份。
**错误消息与异常。**常见做法是把失败请求完整写入日志,其中可能包含认证头。记录请求时,必须预先确定要删除哪些内容,并在每次增加新头时更新该列表。
**中间文件。**通过模板生成配置文件时,临时文件有时会以默认权限短暂存在,随后才被删除。即使只有片刻,其他进程也可能在此时读取它,因此从一开始就应以严格权限创建。
**备份与快照。**加密文件原样备份没有问题,但若解密后的产物留在服务器上,它也会被纳入备份。因此,规定部署产物的权限和位置也是秘密管理的一部分。
再进一步就是检测。有些工具可以在提交阶段或 CI 中发现明文秘密;启用后,前面提到的‘撤销 → 调查 → 清理’甚至不必经历。依赖人的注意力的规则终会失败,而阻止提交的检查每次都会运行。
下一项实验要做什么
安全地创建密码文件,加密整个变量文件,并仅以内联方式加密单个值。在 playbook 中使用解密后的值创建权限为 600 的文件,并通过 no_log 防止日志泄露。完成 rekey 和 vault-id 操作后,最后审计整个仓库。