Ansible Security Practices
Introduction
Security is a critical aspect of any automation tool, especially one as powerful as Ansible which can manage configurations across your entire infrastructure. This guide covers essential security practices that will help you use Ansible safely and protect your systems from potential vulnerabilities.
Ansible's architecture already provides some inherent security benefits - it's agentless, uses SSH by default, and doesn't require opening additional ports on managed nodes. However, there are still many important security considerations when implementing Ansible in production environments.
Securing Ansible Control Node
The Ansible control node is where you run your playbooks from and is a high-value target for attackers since it has access to your entire infrastructure.
Restrict Access to Control Node
Only authorized administrators should have access to the Ansible control node:
# Example: Using sudo to restrict who can run ansible commands
sudo usermod -aG ansible john
Keep Ansible Updated
Running outdated software versions can expose you to known vulnerabilities:
# Update Ansible to the latest version
pip install --upgrade ansible
Output:
Successfully installed ansible-8.3.0
Use Version Control
Store your Ansible playbooks and roles in a secure version control system:
# Initialize a Git repository for your Ansible code
git init
git add .
git commit -m "Initial ansible configuration"
Credential Management
One of the most important aspects of Ansible security is how you manage credentials.
Ansible Vault
Ansible Vault encrypts sensitive data like passwords, API keys, and other secrets:
# Encrypt sensitive variables file
ansible-vault encrypt group_vars/all/secrets.yml
Output:
New Vault password:
Confirm New Vault password:
Encryption successful
To use the encrypted file in your playbook:
# Run playbook with vault password
ansible-playbook site.yml --ask-vault-pass
Use ansible-vault Edit
Never decrypt sensitive files to edit them:
# Edit encrypted file securely
ansible-vault edit group_vars/all/secrets.yml
Vault ID Management
For more complex scenarios, use Vault IDs to manage different secrets:
# Encrypt with a specific ID
ansible-vault encrypt --vault-id dev@prompt group_vars/dev/secrets.yml
ansible-vault encrypt --vault-id prod@prompt group_vars/prod/secrets.yml
Running a playbook with different vault IDs:
ansible-playbook site.yml --vault-id dev@prompt --vault-id prod@prompt
Connection Security
Securing connections between control node and managed nodes is crucial.
SSH Keys Instead of Passwords
Always use SSH key-based authentication instead of passwords:
# Example inventory with SSH key configuration
all:
hosts:
webserver:
ansible_host: 192.168.1.10
ansible_user: deploy
ansible_ssh_private_key_file: ~/.ssh/id_webserver
Disable Host Key Checking with Caution
While it's convenient for testing, in production you should verify host keys:
# inventory.yml - Example of host key verification setup
webservers:
hosts:
web01:
ansible_host: 10.0.0.1
ansible_user: ansible
vars:
ansible_ssh_common_args: '-o StrictHostKeyChecking=yes'
Playbook Security
Following best practices when writing playbooks can enhance your security posture.
Limit Task Privileges
Use become
only when necessary and be specific about which user to become:
---
- name: Configure web server
hosts: webservers
tasks:
- name: Install packages
become: true
ansible.builtin.apt:
name: nginx
state: present
- name: Configure application files
# No become here - using regular user permissions
ansible.builtin.template:
src: app-config.j2
dest: ~/app/config.json
- name: Restart nginx service
become: true
become_user: root
ansible.builtin.service:
name: nginx
state: restarted
Validate External Input
Always validate inputs, especially when they come from external sources:
- name: Deploy application with validated parameters
hosts: appservers
vars_prompt:
- name: "app_version"
prompt: "Enter application version to deploy"
private: no
tasks:
- name: Validate input
ansible.builtin.assert:
that:
- app_version is regex('^[0-9]+\.[0-9]+\.[0-9]+$')
fail_msg: "Version must be in semver format (e.g., 1.2.3)"
- name: Deploy application
when: app_version is regex('^[0-9]+\.[0-9]+\.[0-9]+$')
ansible.builtin.git:
repo: https://github.com/company/app.git
version: "{{ app_version }}"
dest: /opt/application
Inventory Security
Your inventory contains sensitive information about your infrastructure.
Dynamic Inventory Security
When using dynamic inventory scripts, ensure they're secure:
# Set proper permissions on inventory scripts
chmod 700 inventory/aws_ec2.py
Use Inventory Groups for Access Control
Organize your inventory to facilitate security segmentation:
# inventory.yml
all:
children:
production:
children:
db_servers:
hosts:
db01:
ansible_host: 10.0.1.1
web_servers:
hosts:
web01:
ansible_host: 10.0.2.1
development:
hosts:
dev01:
ansible_host: 10.0.10.1
Then in your playbooks, explicitly target only needed groups:
---
- name: Database maintenance
hosts: db_servers
# Only runs on database servers
tasks:
- name: Backup databases
# Tasks here
Role-Based Access Control
Implementing RBAC (Role-Based Access Control) for Ansible.
Ansible Tower/AWX
For larger environments, consider using Ansible Tower or AWX which provide robust RBAC:
# Example of RBAC structure in Tower/AWX (conceptual, not actual code)
---
organizations:
- name: IT Department
teams:
- name: Database Admins
permissions:
- inventory: db_servers
permission: use
- projects: db_maintenance
permission: use
- name: Web Admins
permissions:
- inventory: web_servers
permission: use
- projects: web_maintenance
permission: use
Limit sudo Access on Managed Nodes
Configure specific sudo permissions for the Ansible user:
# /etc/sudoers.d/ansible
ansible ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/apt-get
Securing Sensitive Data in Logs
Prevent leaking sensitive information in logs.
No Logging for Sensitive Tasks
Use no_log
for tasks that handle sensitive data:
- name: Configure database password
ansible.builtin.template:
src: db_config.j2
dest: /etc/app/database.conf
no_log: true
Avoid Debugging with Sensitive Data
Be careful with debug
modules and verbose output:
# Bad practice - exposes secrets in logs
- name: Debug connection
ansible.builtin.debug:
var: db_password
# Better practice - mask sensitive data
- name: Confirm database connection
ansible.builtin.debug:
msg: "Successfully connected to database as {{ db_user }}"
Network Security
Securing network communication in Ansible deployments.
Jump Hosts / Bastion Hosts
Use jump hosts to access isolated environments:
# inventory.yml
all:
children:
private_network:
hosts:
internal01:
ansible_host: 10.0.0.5
vars:
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p bastion.example.com"'
SSH Tunneling
Create secure tunnels for accessing services:
- name: Set up SSH tunnel for database access
hosts: localhost
tasks:
- name: Establish SSH tunnel
ansible.builtin.command:
cmd: ssh -f -N -L 5432:db.internal:5432 bastion.example.com
changed_when: false
Security Auditing
Regular security audits help identify and address vulnerabilities.
Ansible Lint
Use ansible-lint to check your playbooks for security issues:
# Install ansible-lint
pip install ansible-lint
# Run lint checks
ansible-lint playbooks/
Output:
WARNING Unnamed task should be named [name[missing]]
playbooks/deploy.yml:15 Task/Handler: debug var=result
WARNING Jinja2 templates should have .j2 extension [jinja[ext]]
playbooks/templates/nginx.conf:1
Playbook Review Process
Implement a review process for playbooks before they run in production:
Common Vulnerabilities to Avoid
Here are some common Ansible security pitfalls to avoid.
Command Injection
Never construct shell commands with unsanitized variables:
# DANGEROUS - command injection vulnerability
- name: Dangerous command
ansible.builtin.shell: "find /data -name {{ user_input }}"
# SAFER - use appropriate modules
- name: Find files
ansible.builtin.find:
paths: /data
patterns: "{{ user_input }}"
Hardcoded Secrets
Never include secrets directly in playbooks:
# BAD - hardcoded secrets
- name: Connect to service
ansible.builtin.uri:
url: https://api.example.com
user: admin
password: Password123! # NEVER do this!
# GOOD - use vault variables
- name: Connect to service
ansible.builtin.uri:
url: https://api.example.com
user: "{{ api_user }}"
password: "{{ api_password }}" # From encrypted vault
Summary
Securing your Ansible environment involves multiple layers of protection:
- Control Node Security: Restrict access, keep updated, use version control
- Credential Management: Use Ansible Vault for secrets, never store plaintext credentials
- Connection Security: Use SSH keys, verify host keys in production
- Playbook Security: Limit privileges, validate inputs
- Inventory Security: Protect inventory information, use groups for access control
- Role-Based Access Control: Implement RBAC with Tower/AWX
- Log Security: Use no_log for sensitive tasks, be careful with debugging
- Network Security: Use jump hosts and secure tunnels
- Security Auditing: Regularly audit with tools like ansible-lint
- Vulnerability Prevention: Avoid command injection and hardcoded secrets
By following these best practices, you can ensure your Ansible automation remains secure while still being powerful and flexible.
Additional Resources
Exercises
- Create a playbook that uses Ansible Vault to manage sensitive database credentials.
- Set up a secure connection to a remote server using SSH keys and verify the host key.
- Analyze an existing playbook for security vulnerabilities using ansible-lint.
- Implement a RBAC structure for your Ansible environment.
- Create a playbook that securely updates system packages without exposing sensitive information in logs.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)