Practical Ansible Security Role Examples

April 17, 2024
ansible

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