Why Automate Hardening?

Manual server hardening is error-prone and doesn't scale. When you're managing 200+ servers, you need consistency. This Ansible playbook implements CIS Benchmark Level 1 controls automatically.

Project Structure

rhel-hardening/
├── playbook.yml
├── inventory/
│   └── hosts.ini
└── roles/
    └── hardening/
        ├── tasks/
        │   ├── main.yml
        │   ├── ssh.yml
        │   ├── kernel.yml
        │   ├── filesystem.yml
        │   └── audit.yml
        ├── handlers/
        │   └── main.yml
        └── defaults/
            └── main.yml

SSH Hardening Tasks

---
# roles/hardening/tasks/ssh.yml
- name: Configure SSH hardening
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: "{{ item.regexp }}"
    line:   "{{ item.line }}"
    state:  present
  loop:
    - { regexp: '^PermitRootLogin',      line: 'PermitRootLogin no' }
    - { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
    - { regexp: '^X11Forwarding',         line: 'X11Forwarding no' }
    - { regexp: '^MaxAuthTries',          line: 'MaxAuthTries 3' }
    - { regexp: '^LoginGraceTime',        line: 'LoginGraceTime 60' }
    - { regexp: '^AllowTcpForwarding',    line: 'AllowTcpForwarding no' }
    - { regexp: '^ClientAliveInterval',   line: 'ClientAliveInterval 300' }
    - { regexp: '^ClientAliveCountMax',   line: 'ClientAliveCountMax 0' }
  notify: Restart SSHD

Kernel Hardening

- name: Apply kernel security parameters
  sysctl:
    name:   "{{ item.key }}"
    value:  "{{ item.value }}"
    state:  present
    reload: yes
  loop:
    # Disable IP forwarding (unless router)
    - { key: net.ipv4.ip_forward,               value: '0' }
    # Protect against SYN flood attacks
    - { key: net.ipv4.tcp_syncookies,            value: '1' }
    # Ignore ICMP broadcast requests
    - { key: net.ipv4.icmp_echo_ignore_broadcasts, value: '1' }
    # Disable source routing
    - { key: net.ipv4.conf.all.accept_source_route, value: '0' }
    # Enable reverse path filtering
    - { key: net.ipv4.conf.all.rp_filter,        value: '1' }
    # Disable IPv6 if not needed
    - { key: net.ipv6.conf.all.disable_ipv6,     value: '1' }

Running the Playbook

# Dry run first
ansible-playbook -i inventory/hosts.ini playbook.yml --check --diff

# Apply to a test server first
ansible-playbook -i inventory/hosts.ini playbook.yml --limit test-server-01

# Apply to all servers
ansible-playbook -i inventory/hosts.ini playbook.yml

Compliance Reporting

# Generate compliance report with OpenSCAP
oscap xccdf eval 
  --profile xccdf_org.ssgproject.content_profile_cis 
  --results /tmp/scan-results.xml 
  --report /tmp/scan-report.html 
  /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

This playbook is available on my GitHub. Star it if it helps you!