--- - name: Include OS-family-specific variables ansible.builtin.include_vars: "{{ item }}" with_first_found: - "{{ ansible_facts['os_family'] }}.yml" - "default.yml" tags: [cis_hardening, always] - name: Disable unneeded filesystem kernel modules ansible.builtin.copy: dest: /etc/modprobe.d/cis-disable-filesystems.conf owner: root group: root mode: "0644" content: | # {{ ansible_managed }} {% for fs in cis_disable_filesystems %} install {{ fs }} /bin/true {% endfor %} tags: [cis_hardening] - name: Set kernel/network sysctl hardening parameters ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" sysctl_set: true state: present reload: true loop: - { name: "net.ipv4.ip_forward", value: "0" } - { name: "net.ipv4.conf.all.send_redirects", value: "0" } - { name: "net.ipv4.conf.all.accept_redirects", value: "0" } - { name: "net.ipv4.conf.all.accept_source_route", value: "0" } - { name: "net.ipv4.conf.all.log_martians", value: "1" } - { name: "net.ipv4.icmp_echo_ignore_broadcasts", value: "1" } - { name: "kernel.randomize_va_space", value: "2" } - { name: "fs.suid_dumpable", value: "0" } tags: [cis_hardening] - name: Enable strict reverse-path filtering (CIS Level 2) ansible.posix.sysctl: name: "net.ipv4.conf.all.rp_filter" value: "1" sysctl_set: true state: present reload: true when: cis_level | int >= 2 tags: [cis_hardening] - name: Harden /tmp mount options (noexec,nosuid,nodev) ansible.posix.mount: path: /tmp src: tmpfs fstype: tmpfs opts: "defaults,rw,nosuid,nodev,noexec,relatime" state: mounted tags: [cis_hardening] - name: Bind and harden /var/tmp (CIS Level 2) ansible.posix.mount: path: /var/tmp src: /tmp fstype: none opts: "bind,nosuid,nodev,noexec" state: mounted when: cis_level | int >= 2 tags: [cis_hardening] - name: Disable additional kernel modules (CIS Level 2) ansible.builtin.copy: dest: /etc/modprobe.d/cis-disable-level2-modules.conf owner: root group: root mode: "0644" content: | # {{ ansible_managed }} {% for mod in cis_disable_modules_level2 %} install {{ mod }} /bin/true {% endfor %} when: cis_level | int >= 2 tags: [cis_hardening] - name: Disable core dumps ansible.builtin.copy: dest: /etc/security/limits.d/99-cis-disable-coredumps.conf owner: root group: root mode: "0644" content: | # {{ ansible_managed }} * hard core 0 tags: [cis_hardening] - name: Enforce default umask ansible.builtin.copy: dest: /etc/profile.d/99-cis-umask.sh owner: root group: root mode: "0644" content: | # {{ ansible_managed }} umask {{ cis_umask }} tags: [cis_hardening] - name: Set shell auto-logout timeout (TMOUT) ansible.builtin.copy: dest: /etc/profile.d/99-cis-tmout.sh owner: root group: root mode: "0644" content: | # {{ ansible_managed }} TMOUT={{ cis_shell_tmout_seconds }} readonly TMOUT export TMOUT tags: [cis_hardening] - name: Mask Ctrl-Alt-Del reboot target ansible.builtin.systemd: name: ctrl-alt-del.target masked: true when: cis_disable_ctrl_alt_del | bool tags: [cis_hardening] - name: Find world-writable directories missing the sticky bit ansible.builtin.shell: > set -o pipefail; df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null register: cis_hardening_sticky_bit_dirs changed_when: false when: cis_sticky_bit_enforce | bool tags: [cis_hardening] - name: Apply sticky bit to world-writable directories ansible.builtin.file: path: "{{ item }}" mode: "a+t" loop: "{{ cis_hardening_sticky_bit_dirs.stdout_lines | default([]) }}" when: cis_sticky_bit_enforce | bool tags: [cis_hardening] - name: Configure journald persistent storage and size cap ansible.builtin.lineinfile: path: /etc/systemd/journald.conf regexp: "^#?{{ item.key }}=" line: "{{ item.key }}={{ item.value }}" loop: - { key: "Storage", value: "persistent" } - { key: "SystemMaxUse", value: "{{ cis_journald_max_use }}" } when: cis_journald_persistent | bool notify: restart systemd-journald tags: [cis_hardening] - name: Schedule weekly report of unowned/ungrouped files ansible.builtin.cron: name: "Weekly unowned files report" weekday: "0" hour: "{{ cis_unowned_files_report_cron_hour }}" minute: "0" job: >- /usr/bin/find / -xdev \( -nouser -o -nogroup \) 2>/dev/null | /usr/bin/mail -s "Unowned files report {{ inventory_hostname }}" root user: root when: cis_unowned_files_report_enabled | bool tags: [cis_hardening] - name: Harden /boot mount options in fstab (requires manual review and reboot) ansible.builtin.replace: path: /etc/fstab regexp: '^(\S+\s+/boot\s+\S+\s+)(?!.*nodev)(\S+)(\s+.*)$' replace: '\1\2,nodev,nosuid\3' when: cis_boot_hardening_enabled | bool tags: [cis_hardening] - name: Deploy baseline auditd rules (logins, time changes, account changes) ansible.builtin.template: src: audit-baseline.rules.j2 dest: /etc/audit/rules.d/10-baseline.rules owner: root group: root mode: "0640" notify: reload auditd rules tags: [cis_hardening] - name: Deploy extended auditd rules (CIS Level 2) ansible.builtin.template: src: audit-level2.rules.j2 dest: /etc/audit/rules.d/20-level2.rules owner: root group: root mode: "0640" when: cis_level | int >= 2 notify: reload auditd rules tags: [cis_hardening] - name: Configure auditd log rotation and disk-space handling ansible.builtin.lineinfile: path: /etc/audit/auditd.conf regexp: "^{{ item.key }}\\s*=" line: "{{ item.key }} = {{ item.value }}" loop: - { key: "max_log_file", value: "50" } - { key: "max_log_file_action", value: "rotate" } - { key: "num_logs", value: "10" } - { key: "space_left", value: "10%" } - { key: "space_left_action", value: "email" } - { key: "admin_space_left", value: "5%" } - { key: "admin_space_left_action", value: "halt" } notify: restart auditd tags: [cis_hardening] - name: Set login.defs password aging policy ansible.builtin.lineinfile: path: /etc/login.defs regexp: "^{{ item.key }}" line: "{{ item.key }}\t{{ item.value }}" loop: - { key: "PASS_MAX_DAYS", value: "90" } - { key: "PASS_MIN_DAYS", value: "1" } - { key: "PASS_WARN_AGE", value: "7" } tags: [cis_hardening] - name: Restrict cron/at to authorized users ansible.builtin.file: path: "{{ item }}" state: touch owner: root group: root mode: "0600" loop: - /etc/cron.allow - /etc/at.allow tags: [cis_hardening] - name: Configure GRUB bootloader password (pre-generated PBKDF2 hash) ansible.builtin.blockinfile: path: /etc/grub.d/40_custom marker: "# {mark} ANSIBLE MANAGED BLOCK" insertafter: "EOF" block: | set superusers="{{ grub_bootloader_username }}" password_pbkdf2 {{ grub_bootloader_username }} {{ grub_bootloader_password_hash }} when: - cis_grub_password_enabled | bool - grub_bootloader_password_hash | length > 0 notify: regenerate grub config tags: [cis_hardening] - name: Enable and start auditd ansible.builtin.systemd: name: auditd enabled: true state: started tags: [cis_hardening]