概述

在自动化运维中,Ansible Playbook 经常需要处理数据库密码、API 密钥、SSH 私钥等敏感信息。如果这些数据以明文形式存储在代码仓库中,一旦仓库泄露,所有凭据都将暴露。Ansible Vault 作为 Ansible 内置的加密工具,通过 AES-256 对称加密算法保护敏感数据,确保只有授权用户才能访问。

我将从基础概念到生产实践,详细梳理 Ansible Vault 的使用方法、密码管理策略,以及在 CI/CD 流水线中的集成方案。

为什么需要 Ansible Vault

明文存储的风险

在实际运维中,敏感信息散落在多个位置:

位置常见敏感数据风险等级
group_vars/all.yml数据库密码、Redis 密码
host_vars/web01.ymlSSH 连接密码、BECOME 密码
Inventory 文件ansible_passwordansible_ssh_pass
Playbook 变量API Token、第三方密钥
Jinja2 模板证书私钥、JWT Secret

明文存储的风险包括:

  1. 代码仓库泄露:Git 历史记录中永远保留明文密码,即使后续删除也能从历史中恢复
  2. 合规审计失败:PCI-DSS、ISO 27001 等安全标准要求敏感数据加密存储
  3. 团队协作风险:任何有仓库访问权限的人都能看到所有密码
  4. 日志泄露:Ansible 执行日志可能输出变量值,导致密码出现在日志文件中

Ansible Vault 的定位

Ansible Vault 不是唯一的密钥管理方案,但它是 Ansible 生态中最直接的选择:

方案优点缺点适用场景
Ansible Vault内置、零依赖、YAML 原生单密码加密、无细粒度权限中小规模团队、快速上手
HashiCorp Vault动态密钥、租约管理、审计日志需要额外部署和维护大型企业、高安全要求
AWS Secrets Manager云原生、自动轮转厂商锁定、按量计费AWS 云环境
SOPS + age支持多密钥加密、Git 友好需要额外工具多人协作、GitOps 场景

参考 Ansible Vault 官方文档 了解完整功能列表。

核心概念与加密机制

加密原理

Ansible Vault 使用 AES-256 对称加密算法(在早期版本中可能是 AES-128)。加密流程:

明文 YAML → AES-256 加密(Vault 密码作为密钥)→ 密文 YAML($ANSIBLE_VAULT 头部标识)

加密后的文件以 $ANSIBLE_VAULT;1.1;AES256 开头,后面跟着 base64 编码的密文。例如:

$ANSIBLE_VAULT;1.1;AES256
66386439653236336462626566653033393664633164636136383765393730353066386230336230
363366323737366336663737333635303462653066333637356436653066333763350a6366373134
37633733336236393030303237373332643761306635316631393963363033663638366363313061
...

可加密的文件类型

Ansible Vault 可以加密任何 Ansible 使用的数据文件:

文件类型示例是否可加密
变量文件group_vars/all.yml
Inventory 文件inventory.ini
Playbook 文件site.yml是(但不推荐)
Jinja2 模板nginx.conf.j2
独立密钥文件db_creds.yml
证书/私钥文件server.key
ansible.cfgansible.cfg

注意ansible.cfg 不能被加密,因为 Ansible 启动时必须读取该文件。如果配置中有敏感信息,应将其移到加密的变量文件中,通过 ansible.cfg 引用。

基础操作

创建加密文件

# 交互式创建(会提示输入密码)
ansible-vault create secrets.yml

# 使用密码文件创建(推荐用于自动化)
ansible-vault create --vault-password-file=~/.vault_pass secrets.yml

创建后,文件内容示例:

# secrets.yml (加密后)
db_password: "prod-db-2026!"
admin_api_key: "sk_live_abc123def456"
ssh_private_key: |
  -----BEGIN RSA PRIVATE KEY-----
  MIIEowIBAAKCAQEA...
  -----END RSA PRIVATE KEY-----  

加密已有文件

# 加密单个文件
ansible-vault encrypt existing_vars.yml

# 加密多个文件
ansible-vault encrypt group_vars/all.yml host_vars/web01.yml

# 指定输出文件(不覆盖原文件)
ansible-vault encrypt --output=encrypted.yml plaintext.yml

查看、编辑和重新加密

# 查看加密文件内容(不会在磁盘留下明文)
ansible-vault view secrets.yml

# 编辑加密文件(自动解密 → 编辑 → 保存时重新加密)
ansible-vault edit secrets.yml

# 修改加密密码(rekey)
ansible-vault rekey secrets.yml

# 永久解密文件(移除加密,慎用!)
ansible-vault decrypt secrets.yml

常用命令速查

命令功能安全等级
ansible-vault create创建并加密新文件安全
ansible-vault encrypt加密已有文件安全
ansible-vault edit编辑加密文件安全(临时解密在内存)
ansible-vault view查看加密内容安全(不分页到磁盘)
ansible-vault rekey修改加密密码安全
ansible-vault decrypt永久解密危险(明文落盘)

密码管理策略

密码文件方式

创建一个只包含密码字符串的文件:

# 创建密码文件
echo "MyVaultPassword2026!" > ~/.vault_pass

# 设置严格权限(必须!)
chmod 600 ~/.vault_pass

ansible.cfg 中配置默认密码文件路径:

[defaults]
vault_password_file = ~/.vault_pass

之后执行 Ansible 命令时不需要手动指定密码:

# 自动使用 ansible.cfg 中配置的密码文件
ansible-playbook site.yml

多密码(Vault ID)方式

在生产环境中,不同环境(开发、测试、生产)应使用不同的 Vault 密码。Ansible 2.4+ 引入了 Vault ID 概念:

# 创建带 Vault ID 的加密文件
ansible-vault create --vault-id dev@~/.vault_dev_pass dev_secrets.yml
ansible-vault create --vault-id prod@~/.vault_prod_pass prod_secrets.yml

执行 Playbook 时指定多个 Vault ID:

ansible-playbook site.yml \
  --vault-id dev@~/.vault_dev_pass \
  --vault-id prod@~/.vault_prod_pass

Ansible 会根据文件加密时使用的 Vault ID 自动选择正确的密码解密。

ansible.cfg 中配置多个 Vault ID:

[defaults]
vault_identity_list = dev@~/.vault_dev_pass, prod@~/.vault_prod_pass, staging@~/.vault_staging_pass

Vault ID 命名规范

建议使用环境名作为 Vault ID 标识符:

Vault ID用途密码文件
dev开发环境~/.vault_dev_pass
staging预发环境~/.vault_staging_pass
prod生产环境~/.vault_prod_pass
db数据库专用密码~/.vault_db_pass
network网络设备密码~/.vault_network_pass

好的实践:生产环境密码文件应存储在受限位置(如密码管理器、HSM),而不是用户 home 目录。

在 Playbook 中使用加密变量

方式一:vars_files 引用

# site.yml
---
- name: Deploy Application
  hosts: prod_servers
  vars_files:
    - secrets.yml  # 自动解密并加载变量
  tasks:
    - name: Configure database connection
      template:
        src: db.conf.j2
        dest: /etc/app/db.conf
      # {{ db_password }} 在运行时自动解密并填充

方式二:include_vars 动态加载

---
- name: Load secrets based on environment
  hosts: all
  tasks:
    - name: Include encrypted vars for current environment
      include_vars:
        file: "{{ env }}_secrets.yml"
      no_log: true  # 防止变量内容出现在日志中

方式三:命令行传入

# 使用 --ask-vault-pass 交互式输入
ansible-playbook site.yml --ask-vault-pass

# 使用 --vault-password-file 指定密码文件
ansible-playbook site.yml --vault-password-file=~/.vault_pass

# 使用 --vault-id 指定带标识的密码
ansible-playbook site.yml --vault-id prod@~/.vault_prod_pass

防止日志泄露

即使使用了 Vault 加密,Ansible 在执行过程中也可能将变量值输出到日志。使用 no_log 属性防止泄露:

---
- name: Create database user
  community.mysql.mysql_user:
    name: "{{ db_user }}"
    password: "{{ db_password }}"
    priv: "*.*:ALL"
    state: present
  no_log: true  # 关键!防止密码出现在日志中

也可以在全局级别设置 no_log

---
- name: Sensitive operations
  hosts: all
  no_log: true  # 该 Play 中所有 task 都不记录日志
  tasks:
    - name: Deploy with secrets
      ...

项目结构设计

推荐目录结构

project/
├── ansible.cfg
├── inventory/
   ├── production/
      ├── hosts.ini
      ├── group_vars/
         ├── all/
            ├── common.yml          # 公共变量(明文)
            └── vault.yml           # 加密变量
         └── webservers/
             ├── vars.yml            # 明文变量
             └── vault.yml           # 加密变量
      └── host_vars/
          └── web01/
              ├── vars.yml
              └── vault.yml
   └── staging/
       ├── hosts.ini
       └── group_vars/
           └── all/
               └── vault.yml
├── playbooks/
   ├── site.yml
   ├── deploy.yml
   └── db_setup.yml
├── roles/
   ├── nginx/
   ├── mysql/
   └── app/
├── files/
   └── ssl/
       └── server.key                   # 加密的 SSL 私钥
└── templates/
    └── db.conf.j2

明密分离原则

将明文变量和加密变量放在不同文件中,便于管理和审计:

# group_vars/all/common.yml (明文,可提交到 Git)
---
app_name: "myapp"
app_port: 8080
db_host: "db.internal"
db_port: 3306
# group_vars/all/vault.yml (加密,可提交到 Git 但内容不可读)
$ANSIBLE_VAULT;1.1;AES256
6638643965323633646262656665303339366463316463613638376539373035...
# ansible.cfg
[defaults]
inventory = inventory/
vault_identity_list = dev@~/.vault_dev_pass, staging@~/.vault_staging_pass, prod@~/.vault_prod_pass
host_key_checking = False

CI/CD 集成

GitLab CI 集成

在 CI/CD 环境中,Vault 密码不能交互式输入。推荐通过 CI 变量注入:

# .gitlab-ci.yml
stages:
  - deploy

deploy_production:
  stage: deploy
  image: ansible/ansible-runner:latest
  variables:
    # Vault 密码通过 CI 变量注入(在 GitLab UI 中设置为 Masked 变量)
    VAULT_PASS_PROD: $VAULT_PASS_PROD
  before_script:
    # 将 CI 变量写入临时密码文件
    - echo "$VAULT_PASS_PROD" > /tmp/vault_pass
    - chmod 600 /tmp/vault_pass
  script:
    - ansible-playbook -i inventory/production playbooks/deploy.yml
        --vault-id prod@/tmp/vault_pass
  after_script:
    # 清理密码文件
    - rm -f /tmp/vault_pass
  only:
    - main

GitHub Actions 集成

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Ansible
        run: pip install ansible-core

      - name: Create vault password file
        run: |
          echo "${{ secrets.VAULT_PASS_PROD }}" > ~/.vault_pass
          chmod 600 ~/.vault_pass          

      - name: Run playbook
        run: |
          ansible-playbook -i inventory/production playbooks/deploy.yml \
            --vault-id prod@~/.vault_pass          

      - name: Cleanup
        if: always()
        run: rm -f ~/.vault_pass

Jenkins Pipeline 集成

pipeline {
    agent any
    environment {
        VAULT_PASS = credentials('ansible-vault-prod')
    }
    stages {
        stage('Deploy') {
            steps {
                sh '''
                    echo "${VAULT_PASS}" > /tmp/vault_pass
                    chmod 600 /tmp/vault_pass
                    ansible-playbook -i inventory/production playbooks/deploy.yml \
                        --vault-id prod@/tmp/vault_pass
                    rm -f /tmp/vault_pass
                '''
            }
        }
    }
}

安全提示:CI/CD 中的密码文件应在 after_scriptpost 步骤中清理,确保即使任务失败也不会残留。

密码轮转

定期轮转策略

安全合规通常要求定期更换密码。Ansible Vault 的 rekey 命令支持密码轮转:

# 交互式轮转(输入旧密码 → 设置新密码)
ansible-vault rekey secrets.yml

# 使用密码文件轮转
ansible-vault rekey \
  --vault-password-file=~/.vault_old_pass \
  --new-vault-password-file=~/.vault_new_pass \
  secrets.yml

# 使用 Vault ID 轮转
ansible-vault rekey \
  --vault-id prod@~/.vault_prod_old \
  --new-vault-id prod@~/.vault_prod_new \
  prod_secrets.yml

批量轮转脚本

#!/bin/bash
# rotate-vault-passwords.sh
# 批量轮转所有加密文件的 Vault 密码

set -euo pipefail

OLD_PASS_FILE="$1"
NEW_PASS_FILE="$2"
VAULT_DIR="${3:-group_vars}"

if [ ! -f "$OLD_PASS_FILE" ] || [ ! -f "$NEW_PASS_FILE" ]; then
    echo "Usage: $0 <old_pass_file> <new_pass_file> [vault_dir]"
    exit 1
fi

# 查找所有加密文件
find "$VAULT_DIR" -type f -name "*.yml" -exec grep -l '^\$ANSIBLE_VAULT' {} \; | while read -r file; do
    echo "Rekeying: $file"
    ansible-vault rekey \
        --vault-password-file="$OLD_PASS_FILE" \
        --new-vault-password-file="$NEW_PASS_FILE" \
        "$file"
done

echo "All vault files rekeyed successfully."

轮转自动化

结合 cron 或 CI/CD 实现定期自动轮转:

# /etc/cron.d/vault-rotate
# 每季度第一天凌晨2点执行密码轮转
0 2 1 */3 * /opt/scripts/rotate-vault-passwords.sh \
    /etc/ansible/.vault_pass \
    /etc/ansible/.vault_pass_new \
    /etc/ansible/group_vars \
    && mv /etc/ansible/.vault_pass_new /etc/ansible/.vault_pass

安全审计

加密文件扫描

定期扫描仓库中的明文敏感数据:

#!/bin/bash
# scan-plaintext-secrets.sh
# 扫描可能包含明文密码的文件

PATTERNS=(
    "password.*=.*['\"].\{8,\}['\"]"
    "secret.*=.*['\"].\{8,\}['\"]"
    "api_key.*=.*['\"].\{20,\}['\"]"
    "token.*=.*['\"].\{20,\}['\"]"
    "private_key"
)

SCAN_DIR="${1:-.}"
ISSUES=0

for pattern in "${PATTERNS[@]}"; do
    while IFS= read -r file; do
        # 跳过已加密的文件
        if head -1 "$file" | grep -q '^\$ANSIBLE_VAULT'; then
            continue
        fi
        # 跳过 .git 目录
        if echo "$file" | grep -q '\.git/'; then
            continue
        fi
        echo "[WARNING] Potential plaintext secret in: $file"
        grep -n "$pattern" "$file" | head -5
        ISSUES=$((ISSUES + 1))
    done < <(grep -rl "$pattern" "$SCAN_DIR" 2>/dev/null)
done

if [ "$ISSUES" -gt 0 ]; then
    echo ""
    echo "Found $ISSUES files with potential plaintext secrets."
    echo "Consider encrypting them with: ansible-vault encrypt <file>"
    exit 1
else
    echo "No plaintext secrets found."
    exit 0
fi

审计日志

在团队协作中,记录谁在何时访问了加密数据:

# 记录 vault 操作的审计日志
vault_audit() {
    local action="$1"
    local file="$2"
    local user="$(whoami)"
    local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
    local log_file="/var/log/ansible-vault-audit.log"

    echo "[$timestamp] user=$user action=$action file=$file" >> "$log_file"
}

# 用法
vault_audit "view" "group_vars/all/vault.yml"
ansible-vault view group_vars/all/vault.yml

高级技巧

加密部分变量

有时只需要加密少量敏感字段,而非整个文件。可以使用 !vault 标签加密单个变量:

# group_vars/all.yml
---
db_host: "db.internal"
db_port: 3306
db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  66386439653236336462626566653033393664633164636136383765393730353066386230336230
  363366323737366336663737333635303462653066333637356436653066333763350a6366373134
  ...
api_key: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  37323438366638343536373633336630333134356238393630353436393866636330303037343030
  ...
# 非敏感变量保持明文
app_name: "myapp"
app_port: 8080

生成加密变量值:

# 使用 encrypt_string 命令
ansible-vault encrypt_string --vault-id prod@~/.vault_prod_pass 'my_secret_password'

输出可以直接粘贴到 YAML 文件中:

# 指定变量名
ansible-vault encrypt_string --vault-id prod@~/.vault_prod_pass \
  --name 'db_password' \
  'prod-db-2026!'

与 HashiCorp Vault 集成

对于需要更高安全级别的场景,可以结合 HashiCorp Vault 使用:

# playbook.yml
---
- name: Get secrets from HashiCorp Vault
  hosts: localhost
  vars:
    vault_url: "http://vault:8200"
  tasks:
    - name: Read database credentials from Vault
      hashivault_read:
        url: "{{ vault_url }}"
        token: "{{ lookup('env', 'VAULT_TOKEN') }}"
        secret: "secret/data/database"
        key: "password"
      register: db_secret
      no_log: true

    - name: Use the secret
      debug:
        msg: "DB password retrieved from Vault (masked)"
      no_log: true

加密整个目录

#!/bin/bash
# encrypt-directory.sh
# 加密目录下所有 YAML 文件

DIR="${1:-group_vars}"

find "$DIR" -type f \( -name "*.yml" -o -name "*.yaml" \) | while read -r file; do
    # 跳过已加密的文件
    if head -1 "$file" | grep -q '^\$ANSIBLE_VAULT'; then
        echo "[SKIP] Already encrypted: $file"
        continue
    fi
    echo "[ENCRYPT] $file"
    ansible-vault encrypt --vault-id prod@~/.vault_prod_pass "$file"
done

常见问题与排错

忘记 Vault 密码

Ansible Vault 使用对称加密,没有后门。 如果忘记密码,数据将无法恢复。

预防措施:

  1. 将密码存储在企业密码管理器中(如 1Password、Bitwarden)
  2. 多人持有密码副本
  3. 定期备份加密文件

解密失败

# 错误信息示例
ERROR! Attempting to decrypt but no valid secrets found

# 排查步骤
# 1. 检查密码文件是否存在且可读
ls -la ~/.vault_pass

# 2. 检查密码文件内容(确保没有多余的换行或空格)
cat -A ~/.vault_pass

# 3. 检查 ansible.cfg 中的 vault_password_file 路径
grep vault ansible.cfg

# 4. 检查 Vault ID 是否匹配
ansible-vault view --vault-id prod@~/.vault_prod_pass secrets.yml

Git 中的加密文件差异

加密文件每次修改后,即使只改了一个字符,整个文件的密文都会变化。这导致 Git diff 无法显示实际变更。

解决方案——使用 ansible-vault decrypt 临时解密后查看 diff:

# 在 .gitattributes 中配置
echo "*.yml diff=ansible_vault" >> .gitattributes

# 在 .git/config 中配置 diff 工具
git config diff.ansible_vault.textconv "ansible-vault view"

配置后,git diff 会自动解密并显示明文差异。

常见错误速查

错误信息原因解决方案
Attempting to decrypt but no valid secrets found未提供正确的 Vault 密码检查密码文件或 --vault-id 参数
Vault format unhandled文件格式不兼容检查 Ansible 版本,升级到最新版
Decryption failed密码错误或文件损坏尝试使用正确密码,或从备份恢复
value must be valid YAML加密文件内容损坏从 Git 历史恢复,或重新创建
ansible.cfg not found配置文件被意外加密ansible.cfg 不能加密,从备份恢复

总结

Ansible Vault 为 Ansible 自动化运维提供了轻量级的敏感数据保护方案。在使用过程中,需要关注以下几点:

  1. 明密分离:将敏感变量和普通变量放在不同文件中,只加密敏感文件,便于管理和审计
  2. 多密码管理:使用 Vault ID 为不同环境设置不同密码,降低单点泄露风险
  3. CI/CD 安全:在流水线中通过 Secret 变量注入密码,使用后立即清理临时文件
  4. 定期轮转:配合 cron 或 CI/CD 实现密码定期轮转,满足安全合规要求
  5. 日志防护:在处理敏感变量的 task 上设置 no_log: true,防止密码泄露到日志
  6. 备份与恢复:加密文件同样需要备份,密码也需要安全存储在企业密码管理器中

Ansible Vault 的设计哲学是"够用就好"——它不追求 HashiCorp Vault 那样的企业级密钥管理能力,而是以最小的学习成本和零额外依赖,解决 Ansible 场景中最常见的敏感数据保护问题。对于中小团队和快速迭代的项目,它是性价比最高的选择。

参考资源

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Ansible Vault 官方文档 — Ansible 社区,参考了Ansible Vault 官方文档相关内容
  2. Ansible Vault 命令参考 — Ansible 社区,参考了Ansible Vault 命令参考相关内容
  3. Ansible 安全好的实践 — Ansible 社区,参考了Ansible 安全好的实践相关内容
  4. HashiCorp Vault 与 Ansible 集成 — HashiCorp,参考了HashiCorp Vault 与 Ansible 集成相关内容
  5. Google SRE Book — Security — Google SRE 团队,参考了Google SRE Book — Security相关内容