Ansible Vault

Last updated: July 1, 2025

My Journey with Sensitive Data in Automation

Early in my automation journey, I made a critical mistake that still makes me cringe. I was working on automating server deployments and database configurations, and I had hardcoded passwords, API keys, and SSH private keys directly into my playbooks. Everything worked perfectly in development, and I was proud of my automation scripts.

Then came the moment of sharing. When I pushed my "brilliant" automation code to our Git repository, a colleague immediately pulled me aside. "You just committed database passwords and API keys to version control," he said quietly. My heart sank as I realized the security implications of what I'd done.

That incident taught me a valuable lesson about the importance of securing sensitive data in automation. It was also when I discovered Ansible Vault – a built-in encryption system that allows you to protect passwords, keys, and other sensitive information while still maintaining the power and flexibility of Ansible automation.

In this blog post, I'll share what I've learned about using Ansible Vault effectively across Linux and Windows environments. Whether you're managing database credentials, API tokens, or SSL certificates, mastering Ansible Vault is essential for secure automation.

Understanding Ansible Vault

Ansible Vault is a feature that allows you to encrypt sensitive data at rest. Instead of storing passwords and keys in plain text, Vault uses AES256 encryption to protect your sensitive information. The encrypted files can be safely stored in version control systems and shared with team members.

Key features of Ansible Vault:

  1. File-level encryption - Encrypt entire files containing sensitive data

  2. Variable-level encryption - Encrypt individual variables within files

  3. Multiple password support - Use different passwords for different environments

  4. Seamless integration - Works transparently with playbooks and roles

  5. AES256 encryption - Industry-standard encryption for data security

Basic Vault Operations

Creating Encrypted Files

The simplest way to start with Ansible Vault is to create encrypted files from scratch:

# Create a new encrypted file
ansible-vault create secrets.yml

This command opens your default editor with an empty file. When you save and exit, the file is automatically encrypted.

Encrypting Existing Files

You can also encrypt existing files:

# Encrypt an existing file
ansible-vault encrypt database_passwords.yml

Let's look at a practical example. Suppose you have a file with database credentials:

database_passwords.yml (before encryption)

---
mysql_root_password: "MySuperSecretPassword123!"
mysql_app_password: "AppUser2023@#"
postgres_admin_password: "PostgresAdmin456$"
redis_password: "RedisCache789%"

After running ansible-vault encrypt database_passwords.yml, the file becomes:

$ANSIBLE_VAULT;1.1;AES256
66386439653331363538653738316233366436666134383936643265333234633536356534656265
3136373533346631616631623930366632656235656637390a313634646565353533363761316638
35636366636264626337336132336234666439393166653036343534643762623838636534303737
6639636364396265370a353138663632653764313463396362373031343638376530376137623933
...

Viewing Encrypted Content

To view the content of encrypted files without decrypting them permanently:

# View encrypted file content
ansible-vault view secrets.yml

# Edit encrypted file content
ansible-vault edit secrets.yml

Decrypting Files

To permanently decrypt files (use with caution):

# Decrypt a file permanently
ansible-vault decrypt secrets.yml

Linux Automation Examples

Example 1: Linux User Management with Encrypted Passwords

Let's create a comprehensive example for managing Linux users with encrypted password data:

user_secrets.yml (encrypted vault file)

---
# User account passwords
user_passwords:
  alice: "$6$rounds=4096$salt$hashed_password_here"
  bob: "$6$rounds=4096$salt$another_hashed_password"
  charlie: "$6$rounds=4096$salt$yet_another_password"

# SSH keys for deployment
deployment_ssh_key: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAFwAAAAdzc2gtcn
  ...
  -----END OPENSSH PRIVATE KEY-----

# Database connection details
database_credentials:
  mysql:
    host: "mysql.internal.company.com"
    port: 3306
    username: "app_user"
    password: "SecureMySQL2023!"
  
  postgresql:
    host: "postgres.internal.company.com"
    port: 5432
    username: "pg_app_user"
    password: "PostgresSecure456$"

linux_users.yml (main playbook)

---
- name: Manage Linux users with encrypted credentials
  hosts: linux_servers
  become: yes
  vars_files:
    - user_secrets.yml
  
  vars:
    users_to_create:
      - name: alice
        groups: ['sudo', 'developers']
        shell: /bin/bash
      - name: bob
        groups: ['developers']
        shell: /bin/bash
      - name: charlie
        groups: ['operators']
        shell: /bin/zsh
  
  tasks:
    - name: Create user groups
      group:
        name: "{{ item }}"
        state: present
      loop:
        - developers
        - operators

    - name: Create users with encrypted passwords
      user:
        name: "{{ item.name }}"
        password: "{{ user_passwords[item.name] }}"
        groups: "{{ item.groups }}"
        shell: "{{ item.shell }}"
        create_home: yes
        state: present
      loop: "{{ users_to_create }}"

    - name: Set up SSH key for deployment user
      authorized_key:
        user: "{{ item.name }}"
        key: "{{ deployment_ssh_key }}"
        state: present
      loop: "{{ users_to_create }}"
      when: "'developers' in item.groups"

    - name: Configure database connection for applications
      template:
        src: database_config.j2
        dest: "/opt/app/config/database.conf"
        owner: "{{ item.name }}"
        group: "{{ item.name }}"
        mode: '0600'
      loop: "{{ users_to_create }}"
      when: "'developers' in item.groups"

Example 2: SSL Certificate Management

ssl_secrets.yml (encrypted vault file)

---
ssl_certificates:
  company_cert:
    private_key: |
      -----BEGIN PRIVATE KEY-----
      MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7...
      -----END PRIVATE KEY-----
    
    certificate: |
      -----BEGIN CERTIFICATE-----
      MIIDXTCCAkWgAwIBAgIJAKoK/OvD6ngDMA0GCSqGSIb3DQEBCwUA...
      -----END CERTIFICATE-----
    
    ca_certificate: |
      -----BEGIN CERTIFICATE-----
      MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQsw...
      -----END CERTIFICATE-----

ssl_keystore_password: "KeystoreSecure2023!"

ssl_setup.yml

---
- name: Configure SSL certificates on Linux servers
  hosts: web_servers
  become: yes
  vars_files:
    - ssl_secrets.yml
  
  tasks:
    - name: Create SSL directory
      file:
        path: /etc/ssl/company
        state: directory
        owner: root
        group: root
        mode: '0755'

    - name: Install private key
      copy:
        content: "{{ ssl_certificates.company_cert.private_key }}"
        dest: /etc/ssl/company/server.key
        owner: root
        group: root
        mode: '0600'
      notify: restart apache

    - name: Install certificate
      copy:
        content: "{{ ssl_certificates.company_cert.certificate }}"
        dest: /etc/ssl/company/server.crt
        owner: root
        group: root
        mode: '0644'
      notify: restart apache

    - name: Install CA certificate
      copy:
        content: "{{ ssl_certificates.company_cert.ca_certificate }}"
        dest: /etc/ssl/company/ca.crt
        owner: root
        group: root
        mode: '0644'

  handlers:
    - name: restart apache
      service:
        name: "{{ apache_service_name }}"
        state: restarted
      vars:
        apache_service_name: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"

Windows Automation Examples

Example 3: Windows Service Account Management

windows_secrets.yml (encrypted vault file)

---
# Windows service account credentials
service_accounts:
  sql_service:
    username: "DOMAIN\\sql_service_account"
    password: "SQLService2023#Strong!"
  
  web_service:
    username: "DOMAIN\\iis_service_account"
    password: "WebService2023$Secure!"
  
  backup_service:
    username: "DOMAIN\\backup_service_account"
    password: "BackupService2023%Safe!"

# Windows local administrator passwords
local_admin_passwords:
  web_servers: "WebAdmin2023!Local"
  db_servers: "DBAdmin2023@Local"
  file_servers: "FileAdmin2023#Local"

# API keys for Windows applications
api_credentials:
  monitoring_api: "mon_12345abcdef67890ghijklmnop"
  backup_api: "bak_98765zyxwvu43210fedcba9876"
  antivirus_api: "av_qwertyuiop1234567890asdfgh"

windows_services.yml

---
- name: Configure Windows services with encrypted credentials
  hosts: windows_servers
  vars_files:
    - windows_secrets.yml
  
  tasks:
    - name: Create service accounts in local users (if not domain joined)
      win_user:
        name: "{{ item.value.username.split('\\')[-1] }}"
        password: "{{ item.value.password }}"
        groups:
          - Users
        password_never_expires: yes
        user_cannot_change_password: yes
        state: present
      loop: "{{ service_accounts | dict2items }}"
      when: not ansible_facts['domain_role'] is defined or ansible_facts['domain_role'] == 'workgroup'

    - name: Set local administrator password
      win_user:
        name: Administrator
        password: "{{ local_admin_passwords[group_names[0]] }}"
        state: present
      when: group_names | length > 0

    - name: Configure SQL Server service account
      win_service:
        name: MSSQLSERVER
        username: "{{ service_accounts.sql_service.username }}"
        password: "{{ service_accounts.sql_service.password }}"
        state: restarted
      when: "'sql_servers' in group_names"

    - name: Configure IIS application pool identity
      win_iis_webapppool:
        name: DefaultAppPool
        attributes:
          processModel.identityType: SpecificUser
          processModel.userName: "{{ service_accounts.web_service.username }}"
          processModel.password: "{{ service_accounts.web_service.password }}"
        state: present
      when: "'web_servers' in group_names"

    - name: Install monitoring agent with API key
      win_package:
        path: C:\temp\monitoring-agent.msi
        arguments: '/quiet APIKEY="{{ api_credentials.monitoring_api }}"'
        state: present

Example 4: Windows Registry and Certificate Management

windows_security_secrets.yml (encrypted vault file)

---
# Windows certificates with private keys
windows_certificates:
  server_auth:
    pfx_data: "{{ lookup('file', 'certificates/server.pfx') | b64encode }}"
    pfx_password: "CertPassword2023!Secure"
  
  code_signing:
    pfx_data: "{{ lookup('file', 'certificates/codesign.pfx') | b64encode }}"
    pfx_password: "CodeSign2023@Strong!"

# Registry security settings
security_registry:
  audit_settings:
    - key: "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa"
      value: "AuditBaseObjects"
      data: 1
      type: dword
  
  password_policy:
    - key: "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters"
      value: "RequireSignOrSeal"
      data: 1
      type: dword

# Windows firewall configuration
firewall_rules:
  web_traffic:
    port: 443
    protocol: tcp
    action: allow
    description: "HTTPS traffic for web applications"
  
  sql_traffic:
    port: 1433
    protocol: tcp
    action: allow
    description: "SQL Server database connections"

windows_security.yml

---
- name: Configure Windows security with encrypted settings
  hosts: windows_servers
  vars_files:
    - windows_security_secrets.yml
  
  tasks:
    - name: Import server authentication certificate
      win_certificate_store:
        state: present
        cert_store: cert:\LocalMachine\My
        cert_data: "{{ windows_certificates.server_auth.pfx_data }}"
        cert_password: "{{ windows_certificates.server_auth.pfx_password }}"
        file_type: pkcs12
      when: "'web_servers' in group_names"

    - name: Import code signing certificate
      win_certificate_store:
        state: present
        cert_store: cert:\LocalMachine\My
        cert_data: "{{ windows_certificates.code_signing.pfx_data }}"
        cert_password: "{{ windows_certificates.code_signing.pfx_password }}"
        file_type: pkcs12
      when: "'dev_servers' in group_names"

    - name: Configure security registry settings
      win_regedit:
        path: "{{ item.key }}"
        name: "{{ item.value }}"
        data: "{{ item.data }}"
        type: "{{ item.type }}"
        state: present
      loop: "{{ security_registry.audit_settings + security_registry.password_policy }}"

    - name: Configure Windows Firewall rules
      win_firewall_rule:
        name: "{{ item.value.description }}"
        localport: "{{ item.value.port }}"
        protocol: "{{ item.value.protocol }}"
        action: "{{ item.value.action }}"
        direction: in
        state: present
        enabled: yes
      loop: "{{ firewall_rules | dict2items }}"

Sequence Diagram: Ansible Vault Workflow

Here's a sequence diagram illustrating how Ansible Vault processes encrypted content during playbook execution:

This diagram shows the critical security aspects:

  1. Passwords are only in memory during execution

  2. Encrypted files remain encrypted on disk

  3. Data transmission uses encrypted channels

  4. Memory is cleared after execution

Managing Vault Passwords

Single Password Approach

For simple environments, you can use a single vault password:

# Interactive password prompt
ansible-playbook site.yml --ask-vault-pass

# Password file approach
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Multiple Vault IDs

For more complex environments, use vault IDs to manage different passwords:

# Encrypt with specific vault ID
ansible-vault encrypt --vault-id prod@prompt secrets/prod.yml
ansible-vault encrypt --vault-id dev@~/.vault_dev secrets/dev.yml

# Use multiple vault IDs in playbooks
ansible-playbook site.yml --vault-id prod@prompt --vault-id dev@~/.vault_dev

Password Scripts

You can use scripts to retrieve passwords from external systems:

vault_password_script.py

#!/usr/bin/env python3
import subprocess
import sys

def get_vault_password():
    # Example: Get password from AWS Secrets Manager
    try:
        result = subprocess.run([
            'aws', 'secretsmanager', 'get-secret-value',
            '--secret-id', 'ansible-vault-password',
            '--query', 'SecretString',
            '--output', 'text'
        ], capture_output=True, text=True, check=True)
        return result.stdout.strip()
    except subprocess.CalledProcessError:
        print("Failed to retrieve vault password from AWS", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    print(get_vault_password())

Usage:

# Make script executable
chmod +x vault_password_script.py

# Use script for vault password
ansible-playbook site.yml --vault-password-file ./vault_password_script.py

Variable-Level Encryption

Instead of encrypting entire files, you can encrypt individual variables:

# Encrypt a single variable
ansible-vault encrypt_string 'SecretPassword123!' --name 'db_password'

This produces output like:

db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386439653331363538653738316233366436666134383936643265333234633536356534656265
          3136373533346631616631623930366632656235656637390a313634646565353533363761316638
          35636366636264626337336132336234666439393166653036343534643762623838636534303737
          6639636364396265370a353138663632653764313463396362373031343638376530376137623933

You can then use this directly in your playbooks:

---
- name: Database setup with encrypted password
  hosts: db_servers
  vars:
    db_user: app_user
    db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386439653331363538653738316233366436666134383936643265333234633536356534656265
          3136373533346631616631623930366632656235656637390a313634646565353533363761316638
          35636366636264626337336132336234666439393166653036343534643762623838636534303737
          6639636364396265370a353138663632653764313463396362373031343638376530376137623933
  
  tasks:
    - name: Create database user
      mysql_user:
        name: "{{ db_user }}"
        password: "{{ db_password }}"
        priv: "appdb.*:ALL"
        state: present

Advanced Vault Techniques

Vault in Roles

You can use vault-encrypted files within roles:

roles/
  database/
    vars/
      main.yml          # Non-sensitive variables
      vault.yml         # Encrypted sensitive variables
    tasks/
      main.yml
    defaults/
      main.yml

roles/database/vars/vault.yml (encrypted)

---
database_root_password: "RootPassword2023!"
database_replication_password: "ReplicationPass456$"
database_backup_password: "BackupPass789%"

roles/database/tasks/main.yml

---
- name: Include vault variables
  include_vars: vault.yml

- name: Set MySQL root password
  mysql_user:
    name: root
    password: "{{ database_root_password }}"
    login_user: root
    login_password: "{{ database_root_password }}"
    check_implicit_admin: yes
    priv: "*.*:ALL,GRANT"
    host: localhost
    state: present

Rekey Operations

Change vault passwords when needed:

# Change password interactively
ansible-vault rekey secrets.yml

# Change password using password files
ansible-vault rekey secrets.yml --vault-password-file old_pass.txt --new-vault-password-file new_pass.txt

Vault and CI/CD Integration

For automated deployments, integrate vault passwords securely:

GitLab CI Example (.gitlab-ci.yml)

deploy_production:
  stage: deploy
  script:
    - echo "$VAULT_PASSWORD" > /tmp/vault_pass
    - ansible-playbook 
        --inventory production/inventory 
        --vault-password-file /tmp/vault_pass 
        site.yml
    - rm /tmp/vault_pass
  environment:
    name: production
  only:
    - main
  variables:
    VAULT_PASSWORD: $PRODUCTION_VAULT_PASSWORD  # Set in GitLab CI/CD variables

Configuration File Integration

Set default vault behavior in ansible.cfg:

[defaults]
vault_password_file = ~/.ansible_vault_pass

# Multiple vault IDs
[vault_secrets]
vault_password_file = scripts/vault_password.py
vault_identity_list = prod@~/.vault_prod, dev@~/.vault_dev

Best Practices and Security Considerations

Security Best Practices

  1. Use Strong Passwords: Vault passwords should be complex and unique

  2. Rotate Passwords Regularly: Use the rekey feature to change passwords periodically

  3. Separate Environments: Use different vault passwords for dev, staging, and production

  4. Limit Access: Only grant vault password access to authorized personnel

  5. Monitor Usage: Log and audit vault password usage

Development Workflow

  1. Never commit vault passwords: Add password files to .gitignore

  2. Use vault IDs: Organize secrets by environment or purpose

  3. Document requirements: Clearly indicate which vault passwords are needed

  4. Test encryption: Verify that sensitive data is properly encrypted before committing

Common Pitfalls to Avoid

  1. Don't use weak passwords: Avoid simple or predictable vault passwords

  2. Don't share passwords insecurely: Use secure channels for password distribution

  3. Don't decrypt permanently: Avoid using ansible-vault decrypt unless absolutely necessary

  4. Don't log sensitive output: Use no_log: true for tasks handling sensitive data

- name: Create user with sensitive password
  user:
    name: "{{ username }}"
    password: "{{ user_password }}"
    state: present
  no_log: true  # Prevents password from appearing in logs

Real-World Multi-Environment Example

Here's a comprehensive example showing how to manage multiple environments with different vault configurations:

Directory Structure

ansible-project/
├── inventories/
│   ├── development/
│   │   ├── inventory
│   │   └── group_vars/
│   │       └── all/
│   │           ├── vars.yml
│   │           └── vault.yml (encrypted with dev vault ID)
│   ├── staging/
│   │   ├── inventory
│   │   └── group_vars/
│   │       └── all/
│   │           ├── vars.yml
│   │           └── vault.yml (encrypted with staging vault ID)
│   └── production/
│       ├── inventory
│       └── group_vars/
│           └── all/
│               ├── vars.yml
│               └── vault.yml (encrypted with prod vault ID)
├── site.yml
└── scripts/
    └── get_vault_password.py

site.yml

---
- name: Multi-environment deployment with vault
  hosts: all
  become: yes
  
  pre_tasks:
    - name: Verify vault variables are available
      assert:
        that:
          - database_password is defined
          - api_key is defined
          - ssl_certificate is defined
        fail_msg: "Required vault variables are not defined"

  roles:
    - database
    - webserver
    - monitoring

  post_tasks:
    - name: Verify services are running
      service:
        name: "{{ item }}"
        state: started
      loop:
        - "{{ database_service }}"
        - "{{ webserver_service }}"
        - "{{ monitoring_service }}"

Deployment Commands

# Development deployment
ansible-playbook -i inventories/development site.yml --vault-id dev@prompt

# Staging deployment
ansible-playbook -i inventories/staging site.yml --vault-id staging@scripts/get_vault_password.py

# Production deployment (with multiple vault IDs)
ansible-playbook -i inventories/production site.yml \
  --vault-id prod@prompt \
  --vault-id ssl@~/.vault_ssl_pass

Conclusion

Ansible Vault has become an indispensable part of my automation toolkit. What started as a lesson learned from accidentally committing sensitive data has evolved into a comprehensive security practice that protects credentials across all my automation projects.

The beauty of Ansible Vault lies in its simplicity and seamless integration. You don't need to fundamentally change how you write playbooks or structure your automation. Instead, you simply encrypt the sensitive parts and provide the appropriate passwords during execution.

Remember that security is not just about the tools you use, but also about the processes and practices you follow. Use strong passwords, rotate them regularly, separate environments appropriately, and always follow the principle of least privilege when sharing vault access.

As you implement Ansible Vault in your own projects, start small with a few encrypted variables and gradually expand to more complex multi-environment setups. The investment in learning these security practices will pay dividends in the long run, protecting both your infrastructure and your organization from security breaches.

Last updated