Practical Ansible Security Role Examples

April 17, 2024
ansible

Disclaimer: The examples, configurations, and code snippets provided in this article are for educational purposes only. While we strive for accuracy, there is no guarantee these will work in your specific environment. Always test configurations in a safe environment first and adapt them to your specific needs and security requirements.

When managing server infrastructure, implementing consistent security measures is crucial. Let’s build a practical system hardening role with Ansible that you can use as a foundation for your security automation needs.

Role Structure

Our system hardening role will follow this organization:

system_hardening/
├── tasks/
│   ├── main.yml
│   ├── packages.yml
│   ├── firewall.yml
│   └── ssh.yml
├── templates/
│   └── sshd_config.j2
└── defaults/
    └── main.yml

Package Management

# tasks/packages.yml
---
- name: Update package cache
  apt:
    update_cache: yes
    cache_valid_time: 3600
  when: ansible_os_family == "Debian"

- name: Upgrade all packages
  apt:
    upgrade: yes
  when: ansible_os_family == "Debian"

- name: Install security packages
  apt:
    name:
      - ufw
      - fail2ban
      - rkhunter
      - aide
    state: present
  when: ansible_os_family == "Debian"

Firewall Configuration

# tasks/firewall.yml
---
- name: Ensure UFW is installed
  apt:
    name: ufw
    state: present
  when: ansible_os_family == "Debian"

- name: Set default UFW policies
  ufw:
    direction: "{{ item.direction }}"
    policy: "{{ item.policy }}"
  loop:
    - { direction: incoming, policy: deny }
    - { direction: outgoing, policy: allow }

- name: Allow SSH access
  ufw:
    rule: allow
    port: "{{ ssh_port }}"
    proto: tcp

- name: Enable UFW
  ufw:
    state: enabled

SSH Hardening

# tasks/ssh.yml
---
- name: Configure SSH security options
  template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: '0600'
    validate: /usr/sbin/sshd -t -f %s
  notify: restart sshd

- name: Ensure SSH service is enabled and running
  service:
    name: sshd
    state: started
    enabled: yes

SSH Configuration Template

# templates/sshd_config.j2
# Security-hardened SSH configuration
Port {{ ssh_port }}
Protocol 2

# Authentication
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
MaxAuthTries 3

# Security
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitUserEnvironment no

# Logging
SyslogFacility AUTH
LogLevel VERBOSE

# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 60

Default Variables

# defaults/main.yml
---
# SSH Configuration
ssh_port: 22
ssh_allowed_users: []
ssh_max_auth_tries: 3
ssh_client_alive_interval: 300

# Firewall Configuration
ufw_allowed_ports:
  - "{{ ssh_port }}"
  - 80
  - 443

# Security Packages
security_packages:
  - ufw
  - fail2ban
  - rkhunter
  - aide

Main Tasks

# tasks/main.yml
---
- name: Include package management tasks
  include_tasks: packages.yml
  tags: [security, packages]

- name: Include firewall configuration
  include_tasks: firewall.yml
  tags: [security, firewall]

- name: Include SSH hardening
  include_tasks: ssh.yml
  tags: [security, ssh]

Using the Role

Create a playbook to apply the system hardening role:

# security.yml
---
- hosts: all
  become: yes
  roles:
    - system_hardening
  vars:
    ssh_port: 2222  # Custom SSH port
    ufw_allowed_ports:
      - 2222  # Match custom SSH port
      - 80
      - 443

Handlers

# handlers/main.yml
---
- name: restart sshd
  service:
    name: sshd
    state: restarted

- name: reload ufw
  ufw:
    state: reloaded

Testing

Create a test playbook using Molecule:

# molecule/default/converge.yml
---
- name: Converge
  hosts: all
  become: yes
  roles:
    - system_hardening

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

    - name: Verify 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 Considerations

  1. SSH Access

    • Always verify SSH access works before ending your session
    • Consider keeping a backup SSH port open during initial configuration
    • Test key-based authentication before disabling password authentication
  2. Firewall Rules

    • Ensure all required ports are allowed before enabling UFW
    • Consider rate limiting for services like SSH
    • Document all opened ports and their purposes
  3. Package Updates

    • Test package updates in staging before production
    • Consider using unattended-upgrades for automatic security updates
    • Keep a list of critical packages that require manual updates

Conclusion

This system hardening role provides a solid foundation for securing your Linux servers. Key benefits include:

  • Consistent security configurations across all systems
  • Automated firewall setup and SSH hardening
  • Easy customization through variables
  • Comprehensive testing with Molecule

Remember to:

  • Test thoroughly in a staging environment
  • Customize settings based on your security requirements
  • Keep the role updated with evolving security best practices
  • Document any modifications or special configurations