把秘密单独放一层
敏感变量出问题的原因通常不是加密没做,而是和普通变量混在同一批文件里:改一个超时时间就会让整个密文文件产生新的密文块,评审者看不出改了什么,也容易顺手把明文提交上去。可行的做法是把秘密从普通变量里拆出来单独成文件,普通文件里只保留一个指向密文的引用。这样绝大部分改动仍然可读可评审,加密范围缩到最小。
基本信息
| 拆分方式 | 每个作用域一份明文变量文件 + 一份同名加密文件(如 vars.yml 与 vault.yml) |
|---|---|
| 引用写法 | 明文文件里写 app_db_password: "{{ vault_app_db_password }}",实际值只在密文文件里 |
| 命名约定 | 密文文件中的变量加统一前缀(如 vault_),便于一眼看出哪些是秘密 |
| 覆盖顺序 | 同一目录下各文件的合并顺序有明确规定,跨文件覆盖需按官方文档确认 |
| 单值加密 | encrypt_string 适合只有一两个秘密的场景,避免整文件加密 |
| 配套约束 | 含秘密的任务加 no_log,避免值被打进输出与日志 |
典型使用场景
- 多环境仓库:development、staging、production 各有一份加密文件,普通变量文件共享可读
- 团队协作评审:日常参数改动只碰明文文件,评审不再面对一坨密文差异
- 同一仓库多套密钥:按环境用不同 vault-id,执行时按目标环境提供对应口令
- 临时新增一个口令:用 encrypt_string 只加一行密文,避免整文件重新加密
- 与密钥管理系统并存:密文文件里只放取用凭据,真实秘密在外部系统按需拉取
- 审计追溯:明文文件里的引用关系清楚展示哪些任务依赖了秘密
目录与引用示例
目录与引用示例
# 目录布局
# inventory/production/group_vars/all/
# vars.yml 普通变量,明文
# vault.yml 秘密变量,整文件加密
# inventory/staging/group_vars/all/
# vars.yml
# vault.yml
# vars.yml(明文,可评审)
# app_db_host: db.prod.internal
# app_db_password: "{{ vault_app_db_password }}"
# app_api_token: "{{ vault_app_api_token }}"
# vault.yml(加密,只有秘密本身)
# vault_app_db_password: "..."
# vault_app_api_token: "..."
# 单个值内联加密
# ansible-vault encrypt_string --stdin-name 'vault_app_api_token' 'tok-xxx'拆分粒度对照
| 粒度 | 优点 | 代价 |
|---|---|---|
| 整体一个密文文件 | 管理最省事 | 任何秘密改动都让整文件密文变化,差异不可读 |
| 按作用域拆分(推荐) | 环境隔离清楚,改一处只影响一处 | 需要维护多份口令来源与引用关系 |
| 按秘密类型拆分(如数据库、云凭据) | 可对不同类型用不同口令与轮换节奏 | 文件数量增加,引用层级变深 |
| 单值内联加密 | 改动最小,差异直观 | 秘密散落在明文文件里,规模大时难以统一轮换与审计 |
引用层不是多余的中间环节:它把“变量名”留在明文、把“值”放进密文,于是 grep 变量名能查到谁在用这个秘密,而值不必解密即可评审变更范围。这是这类组织方式最主要的好处。
仓库历史无法靠之后加密来清理:如果秘密曾经以明文提交过,加密当前版本并不回收历史中的明文。发现这类情况应当直接轮换该凭据,而不是只补一层加密。
什么时候不要用文件加密
需要按人授权、留操作审计、自动轮换短时凭据,或者秘密数量多到需要生命周期管理时,文件加密就不合适了:它的权限边界就是口令本身。此时应把秘密放进集中式密钥管理系统,Ansible 只在执行时按需取用,仓库里最多留下取用所需的身份信息。