Files
ansible-linux-infra/roles/cis_hardening/tasks/main.yml
2026-08-19 09:51:43 +02:00

256 lines
7.4 KiB
YAML

---
- 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]