diff --git a/tasks/section_5/cis_5.4.1.x.yml b/tasks/section_5/cis_5.4.1.x.yml new file mode 100644 index 0000000..fb30e24 --- /dev/null +++ b/tasks/section_5/cis_5.4.1.x.yml @@ -0,0 +1,193 @@ +--- + +- name: "5.4.1.1 | PATCH | Ensure password expiration is configured" + when: + - rhel9cis_rule_5_4_1_1 + tags: + - level1-server + - level1-workstation + - patch + - password + - rule_5.4.1.1 + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + block: + - name: "5.4.1.1 | PATCH | Ensure password expiration is configured" + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MAX_DAYS' + line: "PASS_MAX_DAYS {{ rhel9cis_pass['max_days'] }}" + + - name: "5.4.1.1 | AUDIT | Ensure password expiration is configured | Get existing users PASS_MAX_DAYS" + ansible.builtin.shell: "awk -F: '(/^[^:]+:[^!*]/ && ($5> {{ rhel9cis_pass['max_days'] }} || $5< {{ rhel9cis_pass['max_days'] }} || $5 == -1)){print $1}' /etc/shadow" + changed_when: false + failed_when: false + register: discovered_max_days + + - name: "5.4.1.1 | PATCH | Ensure password expiration is configured | Set existing users PASS_MAX_DAYS" + ansible.builtin.user: + name: "{{ item }}" + password_expire_max: "{{ rhel9cis_pass['max_days'] }}" + loop: "{{ discovered_max_days.stdout_lines }}" + when: + - discovered_max_days.stdout_lines | length > 0 + - item in discovered_interactive_usernames.stdout + - rhel9cis_force_user_maxdays + +- name: "5.4.1.2 | PATCH | Ensure minimum days between password changes is 7 or more" + when: + - rhel9cis_rule_5_4_1_2 + tags: + - level2-server + - level2-workstation + - patch + - password + - rule_5.4.1.2 + block: + - name: "5.4.1.2 | PATCH | Ensure minimum password days is configured | set login.defs" + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MIN_DAYS' + line: "PASS_MIN_DAYS {{ rhel9cis_pass['min_days'] }}" + + - name: "5.4.1.2 | AUDIT | Ensure minimum password days is configured | Get existing users PASS_MIN_DAYS" + ansible.builtin.shell: "awk -F: '/^[^:]+:[^!*]/ && $4< {{ rhel9cis_pass['min_days'] }} {print $1}' /etc/shadow" + changed_when: false + failed_when: false + register: discovered_min_days + + - name: "5.4.1.2 | PATCH | Ensure minimum password days is configured | Set existing users PASS_MIN_DAYS" + ansible.builtin.user: + name: "{{ item }}" + password_expire_max: "{{ rhel9cis_pass['min_days'] }}" + loop: "{{ discovered_min_days.stdout_lines }}" + when: + - discovered_min_days.stdout_lines | length > 0 + - item in discovered_interactive_usernames.stdout + - rhel9cis_force_user_mindays + +- name: "5.4.1.3 | PATCH | Ensure password expiration warning days is configured" + when: + - rhel9cis_rule_5_4_1_3 + tags: + - level1-server + - level1-workstation + - patch + - password + - rule_5.4.1.3 + block: + - name: "5.4.1.3 | PATCH | Ensure password expiration warning days is configured | set login.defs" + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_WARN_AGE' + line: "PASS_WARN_AGE {{ rhel9cis_pass['warn_age'] }}" + + - name: "5.4.1.3 | AUDIT | Ensure password expiration warning days is configured | Get existing users WARN_DAYS" + ansible.builtin.shell: "awk -F: '/^[^:]+:[^!*]/ && $6< {{ rhel9cis_pass['warn_age'] }} {print $1}' /etc/shadow" + changed_when: false + failed_when: false + register: discovered_warn_days + + - name: "5.4.1.3 | PATCH | Ensure password expiration warning days is configured | Set existing users WARN_DAYS" + ansible.builtin.shell: "chage --warndays {{ rhel9cis_pass['warn_age'] }} {{ item }}" + loop: "{{ discovered_warn_days.stdout_lines }}" + when: + - discovered_warn_days.stdout_lines | length > 0 + - item in discovered_interactive_usernames.stdout + - rhel9cis_force_user_warnage + +- name: "5.4.1.4 | PATCH | Ensure strong password hashing algorithm is configured" + when: + - rhel9cis_rule_5_4_1_4 + tags: + - level1-server + - level1-workstation + - patch + - rule_5.4.1.4 + - pam + - NIST800-53R5_IA-5 + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^ENCRYPT_METHOD' + line: 'ENCRYPT_METHOD {{ rhel9cis_passwd_hash_algo | upper }}' + +- name: "5.4.1.5 | PATCH | Ensure inactive password lock is configured" + when: + - rhel9cis_rule_5_4_1_5 + tags: + - level1-server + - level1-workstation + - patch + - password + - rule_5.4.1.5 + block: + - name: "5.4.1.5 | AUDIT | Ensure inactive password lock is configured | Check current settings" + ansible.builtin.shell: useradd -D | grep INACTIVE={{ rhel9cis_inactivelock.lock_days }} | cut -f2 -d= + changed_when: false + failed_when: false + check_mode: false + register: rhel9cis_5_4_1_5_inactive_settings + + - name: "5.4.1.5 | PATCH | Ensure inactive password lock is configured | Set default inactive setting" + ansible.builtin.shell: useradd -D -f {{ rhel9cis_inactivelock.lock_days }} + when: rhel9cis_5_4_1_5_inactive_settings.stdout | length == 0 + + - name: "5.4.1.5 | AUDIT | Ensure inactive password lock is configured | Getting user list" + ansible.builtin.shell: "awk -F: '/^[^#:]+:[^\\!\\*:]*:[^:]*:[^:]*:[^:]*:[^:]*:(\\s*|-1|3[1-9]|[4-9][0-9]|[1-9][0-9][0-9]+):[^:]*:[^:]*\\s*$/ {print $1}' /etc/shadow" + changed_when: false + check_mode: false + register: rhel9cis_5_4_1_5_user_list + + - name: "5.4.1.5 | PATCH | Ensure inactive password lock is configured | Apply Inactive setting to existing accounts" + ansible.builtin.shell: chage --inactive {{ rhel9cis_inactivelock.lock_days }} "{{ item }}" + loop: "{{ rhel9cis_5_4_1_5_user_list.stdout_lines }}" + when: item in discovered_interactive_usernames.stdout + +- name: "5.4.1.6 | PATCH | Ensure all users last password change date is in the past" + when: + - rhel9cis_rule_5_4_1_6 + tags: + - level1-server + - level1-workstation + - patch + - rule_5.4.1.6 + vars: + warn_control_id: '5.4.1.6' + block: + - name: "5.4.1.6 | AUDIT | Ensure all users last password change date is in the past | Get current date in Unix Time" + ansible.builtin.shell: echo $(($(date --utc --date "$1" +%s)/86400)) + changed_when: false + failed_when: false + check_mode: false + register: rhel9cis_5_4_1_6_currentut + + - name: "5.4.1.6 | AUDIT | Ensure all users last password change date is in the past | Get list of users with last changed pw date in the future" + ansible.builtin.shell: "cat /etc/shadow | awk -F: '{if($3>{{ rhel9cis_5_4_1_6_currentut.stdout }})print$1}'" + changed_when: false + failed_when: false + check_mode: false + register: rhel9cis_5_4_1_6_user_list + + - name: "5.4.1.6 | AUDIT | Ensure all users last password change date is in the past | Alert on accounts with pw change in the future" + when: + - rhel9cis_5_4_1_6_user_list.stdout | length > 0 + - not rhel9cis_futurepwchgdate_autofix + ansible.builtin.debug: + msg: "Warning!! The following accounts have the last PW change date in the future: {{ rhel9cis_5_4_1_5_user_list.stdout_lines }}" + + - name: "5.4.1.6 | AUDIT | Ensure all users last password change date is in the past | warning count" + when: + - rhel9cis_5_4_1_6_user_list.stdout | length > 0 + - not rhel9cis_futurepwchgdate_autofix + ansible.builtin.import_tasks: + file: warning_facts.yml + + - name: "5.4.1.6 | PATCH | Ensure all users last password change date is in the past | Fix accounts with pw change in the future" + when: + - rhel9cis_5_4_1_6_user_list.stdout | length > 0 + - rhel9cis_futurepwchgdate_autofix + ansible.builtin.shell: passwd --expire {{ item }} + loop: "{{ rhel9cis_5_4_1_6_user_list.stdout_lines }}" diff --git a/tasks/section_5/cis_5.4.2.x.yml b/tasks/section_5/cis_5.4.2.x.yml new file mode 100644 index 0000000..d22e4d0 --- /dev/null +++ b/tasks/section_5/cis_5.4.2.x.yml @@ -0,0 +1,243 @@ +--- + +- name: "5.4.2.1 | PATCH | Ensure root is the only UID 0 account" + when: + - rhel9cis_rule_5_4_2_1 + - prelim_uid_zero_accounts_except_root.rc + - rhel9cis_disruption_high + tags: + - level1-server + - level1-workstation + - patch + - accounts + - users + - rule_5.4.2.1 + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + ansible.builtin.shell: passwd -l {{ item }} + changed_when: false + failed_when: false + loop: "{{ prelim_uid_zero_accounts_except_root.stdout_lines }}" + +- name: "5.4.2.2 | PATCH | Ensure root is the only GID 0 account" + when: + - rhel9cis_rule_5_4_2_2 + - rhel9cis_disruption_high + tags: + - level1-server + - level1-workstation + - patch + - rule_5.4.2.2 + - user + - system + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + block: + - name: "5.4.2.2 | AUDIT | Ensure root is the only GID 0 account | Get members of gid 0" + ansible.builtin.shell: "awk -F: '($1 !~ /^(sync|shutdown|halt|operator)/ && $4==\"0\") {print $1}' /etc/passwd | grep -wv 'root'" + register: discovered_gid0_members + changed_when: false + failed_when: discovered_gid0_members.rc not in [ 0, 1 ] + + - name: "5.4.2.2 | PATCH | Ensure root is the only GID 0 account | Remove users not root from gid 0" + when: + - discovered_gid0_members is defined + - discovered_gid0_members.stdout | length > 0 + ansible.builtin.user: + name: "{{ item }}" + gid: 0 + state: absent + loop: + - discovered_gid0_members.stdout_lines + +- name: "5.4.2.3 | AUDIT | Ensure group root is the only GID 0 group" + when: + - rhel9cis_rule_5_4_2_3 + tags: + - level1-server + - level1-workstation + - patch + - rule_5.4.2.2 + - user + - system + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + block: + - name: "5.4.2.3 | AUDIT | Ensure group root is the only GID 0 group | Get groups with gid 0" + ansible.builtin.shell: "awk -F: '$3==\"0\"{print $1}' /etc/group | grep -vw 'root'" + register: discovered_gid0_groups + changed_when: false + failed_when: discovered_gid0_groups.rc not in [ 0, 1 ] + + - name: "5.4.2.3 | AUDIT | Ensure group root is the only GID 0 group | Warning if others gid 0 groups" + when: + - discovered_gid0_groups is defined + - discovered_gid0_groups.stdout | length > 0 + ansible.builtin.debug: + msg: + - "Warning!! You have other groups assigned to GID 0 - Please resolve" + - "{{ discovered_gid0_groups.stdout_lines }}" + + - name: "5.4.2.3 | WARN | Ensure group root is the only GID 0 group | warn_count" + when: + - discovered_gid0_groups is defined + - discovered_gid0_groups.stdout | length > 0 + ansible.builtin.import_tasks: + file: warning_facts.yml + vars: + warn_control_id: '5.4.2.3' + +- name: "5.4.2.4 | PATCH | Ensure root account access is controlled" + when: + - rhel9cis_rule_5_4_2_4 + tags: + - level1-server + - level1-workstation + - patch + - shadow_suite + - rule_5.4.2.4 + ansible.builtin.debug: + msg: "This is set as an assert in tasks/main" + +- name: "5.4.2.5 | PATCH | Ensure root PATH Integrity" + when: + - rhel9cis_rule_5_4_2_5 + tags: + - level1-server + - level1-workstation + - patch + - paths + - rule_5.4.2.5 + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + block: + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Get root paths" + ansible.builtin.shell: sudo -Hiu root env | grep '^PATH' | cut -d= -f2 + changed_when: false + register: discovered_root_paths + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Get root paths" + when: discovered_root_paths is defined + ansible.builtin.shell: sudo -Hiu root env | grep '^PATH' | cut -d= -f2 | tr ":" "\n" + changed_when: false + register: discovered_root_paths_split + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Set fact" + when: discovered_root_paths is defined + ansible.builtin.set_fact: + root_paths: "{{ discovered_root_paths.stdout }}" + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Check for empty dirs" + when: discovered_root_paths is defined + ansible.builtin.shell: 'echo {{ root_paths }} | grep -q "::" && echo "roots path contains a empty directory (::)"' + changed_when: false + failed_when: discovered_root_path_empty_dir.rc not in [ 0, 1 ] + register: discovered_root_path_empty_dir + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Check for trailing ':'" + when: discovered_root_paths is defined + ansible.builtin.shell: '{{ root_paths }} | cut -d= -f2 | grep -q ":$" && echo "roots path contains a trailing (:)"' + changed_when: false + failed_when: discovered_root_path_trailing_colon.rc not in [ 0, 1 ] + register: discovered_root_path_trailing_colon + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Check for owner and permissions" + when: discovered_root_paths is defined + block: + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Check for owner and permissions" + ansible.builtin.stat: + path: "{{ item }}" + register: discovered_root_path_perms + loop: "{{ discovered_root_paths_split.stdout_lines }}" + + - name: "5.4.2.5 | AUDIT | Ensure root PATH Integrity | Set permissions" + when: + - item.stat.exists + - item.stat.isdir + - item.stat.pw_name != 'root' or item.stat.gr_name != 'root' or item.stat.woth or item.stat.wgrp + - (item != 'root') and (not rhel9cis_uses_root) + ansible.builtin.file: + path: "{{ item.stat.path }}" + state: directory + owner: root + group: root + mode: '0755' + follow: false + loop: "{{ discovered_root_path_perms.results }}" + loop_control: + label: "{{ item }}" + +- name: "5.4.2.6 | PATCH | Ensure root user umask is configured" + when: + - rhel9cis_rule_5_4_2_6 + tags: + - level1-server + - level1-workstation + - patch + - shadow_suite + - rule_5.4.2.6 + - NIST800-53R5_AC-3 + - NIST800-53R5_MP-2 + ansible.builtin.lineinfile: + path: /root/.bash_profile + regexp: \s*umask + line: "umask {{ rhel9cis_root_umask }}" + create: true + +- name: "5.4.2.7 | PATCH | Ensure system accounts do not have a valid login shell" + when: + - rhel9cis_rule_5_4_2_7 + - "item.id not in prelim_interactive_usernames.stdout" + - "'root' not in item.id" + - rhel9cis_disruption_high + tags: + - level1-server + - level1-workstation + - patch + - shadow_suite + - rule_5.4.2.7 + - NIST800-53R5_AC-2 + - NIST800-53R5_AC-3 + - NIST800-53R5_AC-11 + - NIST800-53R5_MP-2 + ansible.builtin.user: + name: "{{ item.id }}" + shell: /usr/sbin/nologin + loop: "{{ rhel9cis_passwd }}" + loop_control: + label: "{{ item.id }}" + +- name: "5.4.2.8 | PATCH | Ensure accounts without a valid login shell are locked | Lock accounts" + when: + - rhel9cis_rule_5_4_2_8 + - rhel9cis_disruption_high + - "item.id not in prelim_interactive_usernames.stdout" + - "'root' not in item.id" + tags: + - level1-server + - level1-workstation + - patch + - shadow_suite + - rule_5.4.2.8 + - NIST800-53R5_AC-2 + - NIST800-53R5_AC-3 + - NIST800-53R5_AC-11 + - NIST800-53R5_MP-2 + ansible.builtin.user: + name: "{{ item.id }}" + password_lock: true + loop: "{{ rhel9cis_passwd }}" + loop_control: + label: "{{ item.id }}" diff --git a/tasks/section_5/cis_5.4.3.x.yml b/tasks/section_5/cis_5.4.3.x.yml new file mode 100644 index 0000000..15e8e12 --- /dev/null +++ b/tasks/section_5/cis_5.4.3.x.yml @@ -0,0 +1,62 @@ +--- + +- name: "5.4.3.1 | PATCH | Ensure nologin is not listed in /etc/shells" + when: + - rhel9cis_rule_5_4_3_1 + tags: + - level2-server + - level2-workstation + - patch + - shells + - rule_5.4.3.1 + - NIST800-53R5_CM-1 + - NIST800-53R5_CM-2 + - NIST800-53R5_CM-6 + - NIST800-53R5_CM-7 + - NIST800-53R5_IA-5 + ansible.builtin.replace: + path: /etc/shells + regexp: nologin + replace: "" + +- name: "5.4.3.2 | PATCH | Ensure default user shell timeout is configured" + when: + - rhel9cis_rule_5_4_3_2 + tags: + - level1-server + - level1-workstation + - patch + - shell + - rule_5.4.3.2 + ansible.builtin.blockinfile: + path: "{{ item.path }}" + state: "{{ item.state }}" + marker: "# {mark} - CIS benchmark - Ansible-lockdown" + create: true + mode: '0644' + block: | + TMOUT={{ rhel9cis_shell_session_timeout }} + readonly TMOUT + export TMOUT + loop: + - { path: "{{ rhel9cis_shell_session_file }}", state: present } + - { path: /etc/profile, state: "{{ (rhel9cis_shell_session_file == '/etc/profile') | ternary('present', 'absent') }}" } + +- name: "5.4.3.3 | PATCH | Ensure default user umask is configured" + when: + - rhel9cis_rule_5_4_3_3 + tags: + - level1-server + - level1-workstation + - patch + - umask + - rule_5.4.3.3 + - NIST800-53R5_AC-3 + - NIST800-53R5_MP-2 + ansible.builtin.replace: + path: "{{ item.path }}" + regexp: (?i)(umask\s+\d\d\d) + replace: '{{ item.line }} {{ rhel9cis_bash_umask }}' + loop: + - { path: '/etc/profile', line: 'umask' } + - { path: '/etc/login.defs', line: 'UMASK' }