--- - name: Restrict core dumps when: cis_effective_rules.core_dumps | default(false) ansible.builtin.lineinfile: path: /mnt/etc/security/limits.conf regexp: '^\*\s+hard\s+core\s+' line: "* hard core 0" - name: Ensure the systemd coredump drop-in directory exists (CIS L1+) when: - cis_effective_rules.core_dumps | default(false) - cis_strict | default(false) ansible.builtin.file: path: /mnt/etc/systemd/coredump.conf.d state: directory mode: "0755" - name: Disable systemd core dump storage and backtraces (CIS L1+) when: - cis_effective_rules.core_dumps | default(false) - cis_strict | default(false) ansible.builtin.copy: dest: /mnt/etc/systemd/coredump.conf.d/10-cis.conf mode: "0644" content: | [Coredump] Storage=none ProcessSizeMax=0 - name: Set password quality requirements when: cis_effective_rules.pwquality | default(false) ansible.builtin.lineinfile: path: /mnt/etc/security/pwquality.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" loop: - {regexp: '^\s*#?\s*minlen\s*=', line: "minlen = {{ cis_cfg.pwquality_minlen }}"} - {regexp: '^\s*#?\s*dcredit\s*=', line: "dcredit = -1"} - {regexp: '^\s*#?\s*ucredit\s*=', line: "ucredit = -1"} - {regexp: '^\s*#?\s*ocredit\s*=', line: "ocredit = -1"} - {regexp: '^\s*#?\s*lcredit\s*=', line: "lcredit = -1"} loop_control: label: "{{ item.line }}" # Stricter complexity SSG cis_server_l1 checks; affects only new-password changes. - name: Set strict password quality requirements (CIS L1+) when: - cis_effective_rules.pwquality | default(false) - cis_strict | default(false) ansible.builtin.lineinfile: path: /mnt/etc/security/pwquality.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" loop: - {regexp: '^\s*#?\s*difok\s*=', line: "difok = {{ cis_cfg.pwquality_difok }}"} - {regexp: '^\s*#?\s*maxrepeat\s*=', line: "maxrepeat = {{ cis_cfg.pwquality_maxrepeat }}"} - {regexp: '^\s*#?\s*maxsequence\s*=', line: "maxsequence = {{ cis_cfg.pwquality_maxsequence }}"} - {regexp: '^\s*#?\s*minclass\s*=', line: "minclass = {{ cis_cfg.pwquality_minclass }}"} - {regexp: '^\s*#?\s*dictcheck\s*=', line: "dictcheck = {{ cis_cfg.pwquality_dictcheck }}"} - {regexp: '^\s*#?\s*enforce_for_root\b', line: "enforce_for_root"} loop_control: label: "{{ item.line }}" - name: Set the default shell umask when: cis_effective_rules.umask_default | default(false) ansible.builtin.lineinfile: path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}' regexp: '^\s*umask\s+\d+' line: "umask {{ cis_cfg.umask }}" - name: Set the shell idle timeout when: cis_effective_rules.shell_timeout | default(false) ansible.builtin.lineinfile: path: '/mnt/etc/{{ "bashrc" if is_rhel else "bash.bashrc" }}' regexp: '^\s*(export\s+)?TMOUT=' line: "export TMOUT={{ cis_cfg.tmout }}" # A drop-in survives systemd upgrades; the RHEL vendor journald.conf does not. - name: Ensure the journald drop-in directory exists when: cis_effective_rules.journald_persistent | default(false) ansible.builtin.file: path: /mnt/etc/systemd/journald.conf.d state: directory mode: "0755" - name: Enable persistent journald storage when: cis_effective_rules.journald_persistent | default(false) ansible.builtin.copy: dest: /mnt/etc/systemd/journald.conf.d/10-cis.conf mode: "0644" content: | [Journal] Storage=persistent - name: Compress large journald log files (CIS L1+) when: - cis_effective_rules.journald_persistent | default(false) - cis_strict | default(false) ansible.builtin.copy: dest: /mnt/etc/systemd/journald.conf.d/20-cis-compress.conf mode: "0644" content: | [Journal] Compress=yes - name: Log sudo commands when: cis_effective_rules.sudo_logfile | default(false) ansible.builtin.lineinfile: path: /mnt/etc/sudoers regexp: '^\s*Defaults\s+logfile=' line: 'Defaults logfile="/var/log/sudo.log"' - name: Require a pty for sudo (CIS L1+) when: - cis_effective_rules.sudo_logfile | default(false) - cis_strict | default(false) ansible.builtin.lineinfile: path: /mnt/etc/sudoers regexp: '^\s*Defaults\s+use_pty\b' line: "Defaults use_pty" - name: Restrict su to the wheel group when: cis_effective_rules.su_restriction | default(false) ansible.builtin.lineinfile: path: /mnt/etc/pam.d/su regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so' line: auth required pam_wheel.so # authselect wires the pam_faillock stack via the feature; deny/unlock_time live # in faillock.conf, the supported place (pam_faillock(8) deprecates module args). - name: Configure account lockout (authselect) when: - cis_effective_rules.faillock | default(false) - is_authselect | bool block: - name: Enable the authselect faillock feature ansible.builtin.command: "{{ chroot_command }} authselect enable-feature with-faillock" register: cis_faillock_result changed_when: cis_faillock_result.rc == 0 - name: Set faillock thresholds ansible.builtin.lineinfile: path: /mnt/etc/security/faillock.conf regexp: "{{ item.regexp }}" line: "{{ item.line }}" create: true mode: "0644" loop: - {regexp: '^\s*#?\s*deny\s*=', line: "deny = {{ cis_cfg.faillock_deny }}"} - {regexp: '^\s*#?\s*unlock_time\s*=', line: "unlock_time = {{ cis_cfg.faillock_unlock_time }}"} loop_control: label: "{{ item.line }}" - name: Configure account lockout when: - cis_effective_rules.faillock | default(false) - not is_authselect | bool ansible.builtin.lineinfile: path: "{{ item.path }}" regexp: "{{ item.regexp }}" line: "{{ item.line }}" loop: - path: '/mnt/etc/{{ "pam.d/common-auth" if is_debian | bool else "pam.d/system-auth" }}' regexp: '^\s*auth\s+required\s+pam_faillock\.so' line: >- auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }} - path: '/mnt/etc/{{ "pam.d/common-account" if is_debian | bool else "pam.d/system-auth" }}' regexp: '^\s*account\s+required\s+pam_faillock\.so' line: account required pam_faillock.so loop_control: label: "{{ item.regexp }}" - name: Enforce password history when: cis_effective_rules.password_history | default(false) ansible.builtin.lineinfile: path: >- /mnt/etc/pam.d/{{ "common-password" if is_debian | bool else "passwd" }} regexp: '^\s*password\s+\[success=1.*\]\s+pam_unix\.so' line: >- password [success=1 default=ignore] pam_unix.so obscure sha512 remember={{ cis_cfg.password_remember }} # SSG cis_server_l1 checks pam_pwhistory (not pam_unix remember) in the auth-stack # files; affects only password changes, so no login-lockout risk. EL9 has no # authselect path here (same direct-edit the faillock rule above uses). - name: Enforce password reuse limit via pam_pwhistory (CIS L1+) when: - cis_effective_rules.password_history | default(false) - cis_strict | default(false) ansible.builtin.lineinfile: path: "{{ item }}" regexp: '^\s*password\s+(requisite|required)\s+pam_pwhistory\.so' line: "password requisite pam_pwhistory.so use_authtok remember={{ cis_cfg.pwhistory_remember }} enforce_for_root" insertbefore: '^\s*password\s+.*pam_unix\.so' loop: >- {{ ['/mnt/etc/pam.d/system-auth', '/mnt/etc/pam.d/password-auth'] if is_rhel | bool else (['/mnt/etc/pam.d/common-password'] if is_debian | bool else []) }} loop_control: label: "{{ item }}" - name: Configure TCP wrappers when: cis_effective_rules.tcp_wrappers | default(false) ansible.builtin.lineinfile: path: "{{ item.path }}" regexp: "{{ item.regexp }}" line: "{{ item.line }}" loop: - {path: /mnt/etc/hosts.deny, regexp: '^ALL:\s*ALL', line: "ALL: ALL"} - {path: /mnt/etc/hosts.allow, regexp: '^sshd:\s*ALL', line: "sshd: ALL"} loop_control: label: "{{ item.path }}"