Best Practices for Ansible Security Roles

April 17, 2024
ansible

Security in infrastructure automation requires careful planning and consistent practices. When creating Ansible roles for security tasks, following established best practices ensures your roles remain organized, maintainable, and secure. Let’s explore key guidelines for building effective security roles.

Role Organization

Keep Roles Focused

Each role should have a single, clear responsibility:

  • Separate different security concerns into distinct roles (e.g., firewall, SSH, audit)
  • Avoid mixing unrelated tasks within a single role
  • Break down complex security configurations into smaller, manageable roles

Example directory structure for a focused role:

ssh_hardening/
├── tasks/
│   ├── main.yml          # Task orchestration
│   ├── install.yml       # Package installation
│   ├── configure.yml     # SSH configuration
│   └── audit.yml         # Security checks
├── templates/
│   └── sshd_config.j2    # SSH configuration template
└── defaults/
    └── main.yml          # Default variables

Use Role Dependencies

Declare role dependencies in meta/main.yml to ensure proper execution order:

# meta/main.yml
dependencies:
  - role: firewall
  - role: ssh_hardening
  - role: audit

Benefits:

  • Ensures prerequisites are met before role execution
  • Makes dependencies explicit and visible
  • Maintains consistent security baseline across systems

Variable Management

Default Variables

Place default security settings in defaults/main.yml:

# defaults/main.yml
---
security_ssh_port: 22
security_allowed_users: []
security_min_password_length: 12
security_password_complexity: true

Best practices:

  • Use descriptive variable names with appropriate prefixes
  • Document each variable’s purpose and impact
  • Set secure defaults that follow principle of least privilege

Role-Specific Variables

Store internal role variables in vars/main.yml:

# vars/main.yml
---
security_packages:
  - ufw
  - fail2ban
  - aide
  - rkhunter

security_services:
  - name: sshd
    state: running
    enabled: true

Testing and Validation

Molecule Testing

Use Molecule for comprehensive role testing:

# molecule/default/converge.yml
---
- name: Converge
  hosts: all
  roles:
    - role: security_baseline

# molecule/default/verify.yml
---
- name: Verify
  hosts: all
  tasks:
    - name: Verify firewall status
      command: ufw status
      register: ufw_status
      failed_when: "'Status: active' not in ufw_status.stdout"

    - name: Check SSH configuration
      command: sshd -T
      register: ssh_config
      failed_when: |
        'permitrootlogin no' not in ssh_config.stdout.lower() or
        'passwordauthentication no' not in ssh_config.stdout.lower()

Security Checks

Include comprehensive security validations:

  • Verify service configurations
  • Check file permissions
  • Validate security policies
  • Test network restrictions

Security Best Practices

Secret Management

Use Ansible Vault for sensitive data:

# group_vars/all/vault.yml
---
vault_api_tokens:
  monitoring: !vault |
    $ANSIBLE_VAULT;1.1;AES256
    ...

vault_ssl_certificates:
  private_key: !vault |
    $ANSIBLE_VAULT;1.1;AES256
    ...

Logging and Monitoring

Implement proper logging for security events:

# tasks/logging.yml
---
- name: Configure audit logging
  template:
    src: auditd.conf.j2
    dest: /etc/audit/auditd.conf
    mode: '0600'
  notify: restart auditd

- name: Enable security-related audit rules
  template:
    src: audit.rules.j2
    dest: /etc/audit/rules.d/security.rules
    mode: '0600'
  notify: reload audit rules

File Permissions

Always set appropriate permissions:

# tasks/secure_files.yml
---
- name: Set secure permissions on configuration files
  file:
    path: "{{ item.path }}"
    mode: "{{ item.mode }}"
    owner: "{{ item.owner | default('root') }}"
    group: "{{ item.group | default('root') }}"
  loop:
    - path: /etc/ssh/sshd_config
      mode: '0600'
    - path: /etc/sudoers
      mode: '0440'

Documentation

Role Documentation

Maintain comprehensive documentation in README.md:

# Security Baseline Role

This role implements security baseline configurations following CIS benchmarks.

## Requirements
- Ansible 2.9+
- Target systems: Ubuntu 20.04+

## Role Variables
| Variable | Default | Description |
|----------|---------|-------------|
| security_ssh_port | 22 | SSH port number |
| security_allowed_users | [] | List of allowed SSH users |

## Dependencies
- firewall
- ssh_hardening

## Example Playbook
```yaml
- hosts: servers
  roles:
    - role: security_baseline
      vars:
        security_ssh_port: 2222

Task Documentation

Include clear comments in tasks:

# tasks/main.yml
---
- name: Install security packages
  apt:
    name: "{{ security_packages }}"
    state: present
    update_cache: yes
  tags: [security, packages]
  # Ensures essential security tools are available

- name: Configure firewall rules
  ufw:
    rule: allow
    port: "{{ security_ssh_port }}"
    proto: tcp
  tags: [security, firewall]
  # Allows SSH access on configured port

Conclusion

Creating effective security roles requires attention to detail and adherence to best practices. By following these guidelines, you can build roles that are:

  • Secure by default
  • Easy to maintain
  • Well-documented
  • Thoroughly tested
  • Reusable across projects

Remember to regularly review and update your roles as security requirements evolve and new best practices emerge.