Ansible for Security — CIS Benchmarks and Hardening

Introduction

CIS Benchmarks and Hardening. This guide covers implementing security controls, automating compliance checks, and maintaining audit-ready infrastructure with Ansible playbooks.

Overview

Security automation with Ansible ensures consistent policy enforcement across your entire fleet. Instead of manually configuring each server, define your security baseline as code and apply it uniformly.

Security Baseline Playbook

---
- name: Apply security baseline
  hosts: all
  become: true
  vars:
    security_ssh_port: 22
    security_password_max_age: 90
    security_password_min_length: 14
    security_failed_login_attempts: 5
    security_lockout_time: 900

  tasks:
    - name: Ensure security packages are installed
      ansible.builtin.package:
        name:
          - fail2ban
          - aide
          - auditd
          - rkhunter
        state: present

    - name: Configure SSH hardening
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      loop:
        - { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
        - { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
        - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
        - { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
        - { regexp: '^#?ClientAliveInterval', line: 'ClientAliveInterval 300' }
        - { regexp: '^#?ClientAliveCountMax', line: 'ClientAliveCountMax 2' }
      notify: Restart SSH

    - name: Set password policy
      ansible.builtin.lineinfile:
        path: /etc/login.defs
        regexp: "{{ item.regexp }}"
        line: "{{ item.line }}"
      loop:
        - { regexp: '^PASS_MAX_DAYS', line: 'PASS_MAX_DAYS {{ security_password_max_age }}' }
        - { regexp: '^PASS_MIN_LEN', line: 'PASS_MIN_LEN {{ security_password_min_length }}' }
        - { regexp: '^PASS_WARN_AGE', line: 'PASS_WARN_AGE 14' }

Audit and Compliance Checks

    - name: Check for unauthorized SUID binaries
      ansible.builtin.command:
        cmd: find / -perm -4000 -type f 2>/dev/null
      register: suid_files
      changed_when: false

    - name: Verify file permissions
      ansible.builtin.file:
        path: "{{ item.path }}"
        mode: "{{ item.mode }}"
        owner: root
        group: root
      loop:
        - { path: '/etc/passwd', mode: '0644' }
        - { path: '/etc/shadow', mode: '0640' }
        - { path: '/etc/group', mode: '0644' }
        - { path: '/etc/gshadow', mode: '0640' }

    - name: Ensure auditd is running
      ansible.builtin.systemd:
        name: auditd
        state: started
        enabled: true

    - name: Configure audit rules
      ansible.builtin.copy:
        content: |
          # Monitor authentication events
          -w /etc/passwd -p wa -k identity
          -w /etc/shadow -p wa -k identity
          -w /etc/group -p wa -k identity
          -w /var/log/faillog -p wa -k logins
          -w /var/log/lastlog -p wa -k logins
          # Monitor sudo usage
          -w /etc/sudoers -p wa -k sudo_changes
          -w /etc/sudoers.d/ -p wa -k sudo_changes
        dest: /etc/audit/rules.d/security.rules
        mode: '0640'
      notify: Restart auditd

Firewall Configuration

    - name: Configure firewall (UFW)
      community.general.ufw:
        rule: "{{ item.rule }}"
        port: "{{ item.port }}"
        proto: "{{ item.proto | default('tcp') }}"
      loop:
        - { rule: allow, port: '22' }
        - { rule: allow, port: '80' }
        - { rule: allow, port: '443' }

    - name: Set default deny policy
      community.general.ufw:
        default: deny
        direction: incoming

    - name: Enable firewall
      community.general.ufw:
        state: enabled

Compliance Report

    - name: Generate compliance report
      ansible.builtin.template:
        src: compliance-report.j2
        dest: "/var/log/compliance-{{ ansible_date_time.date }}.txt"
        mode: '0600'

    - name: Check compliance status
      ansible.builtin.assert:
        that:
          - ansible_facts['os_family'] in ['Debian', 'RedHat']
          - suid_files.stdout_lines | length < 50
        fail_msg: "Compliance check failed"
        success_msg: "All compliance checks passed"

Handlers

  handlers:
    - name: Restart SSH
      ansible.builtin.systemd:
        name: sshd
        state: restarted

    - name: Restart auditd
      ansible.builtin.systemd:
        name: auditd
        state: restarted

Scheduled Compliance Scans

---
- name: Automated compliance scan
  hosts: all
  become: true
  tasks:
    - name: Run AIDE integrity check
      ansible.builtin.command:
        cmd: aide --check
      register: aide_result
      changed_when: false
      failed_when: false

    - name: Alert on integrity violations
      ansible.builtin.debug:
        msg: "AIDE detected changes on {{ inventory_hostname }}"
      when: aide_result.rc != 0

Troubleshooting

IssueSolution
SSH lockoutEnsure SSH key auth works before disabling passwords
Audit log fullConfigure log rotation for /var/log/audit/
False positivesTune rules to exclude known-good changes
Performance impactSchedule intensive scans during maintenance windows

Best Practices

  1. Start with a baseline — apply minimum security standards to all hosts
  2. Layer controls — combine network, host, and application security
  3. Automate scanning — run compliance checks on schedule
  4. Version control everything — track security policy changes in Git
  5. Test before enforcing — use --check --diff mode first
  6. Document exceptions — maintain a risk register for accepted deviations

Conclusion

Security automation with Ansible transforms manual, error-prone compliance work into repeatable, auditable code. Apply these patterns to your environment, customize the controls for your compliance framework, and run regular scans to maintain your security posture.