---
- name: Verify
  hosts: all
  any_errors_fatal: true

  tasks:
    - name: Reboot host
      become: true
      ansible.builtin.reboot:

    - name: Wait for the host and reconnect
      ansible.builtin.wait_for:
        port: 22
        host: '{{ (ansible_ssh_host | default(ansible_host)) | default(inventory_hostname) }}'
        delay: 10
        timeout: 120

    - name: Include default vars
      ansible.builtin.include_vars:
        dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
        extensions:
          - 'yml'

    - name: Include host vars
      ansible.builtin.include_vars:
        file: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/host_vars/{{ ansible_hostname }}"

    - name: Set sysctl configuration directory as fact
      tags:
        - fact
        - sysctl
      block:
        - name: Stat /usr/lib/sysctl.d/ exists
          ansible.builtin.stat:
            path: "/usr/lib/sysctl.d/"
          register: usr_lib_sysctl_d

        - name: Set sysctl fact
          ansible.builtin.set_fact:
            sysctl_conf_dir: "{{ '/usr/lib/sysctl.d' if usr_lib_sysctl_d.stat.exists else '/etc/sysctl.d' }}"

        - name: Debug sysctl
          ansible.builtin.debug:
            msg: "{{ sysctl_conf_dir }}"

    - name: Get installed ssh version
      ansible.builtin.command:
        cmd: ssh -V
      changed_when: false
      failed_when: false
      register: ssh_version
      tags:
        - sshd
        - sshd_config

    - name: Set ssh version as fact
      ansible.builtin.set_fact:
        ssh_installed_version: "{{ ssh_version.stderr | regex_search('^OpenSSH_([0-9+].[0-9]+)', '\\1') | join('.') | float }}"  # noqa jinja[spacing]
      tags:
        - sshd
        - sshd_config

    - name: Stat IPv6 status
      become: true
      ansible.builtin.stat:
        path: /proc/sys/net/ipv6
      register: stat_ipv6

    - name: Set IPv6 fact
      ansible.builtin.set_fact:
        system_has_ipv6: "{{ stat_ipv6.stat.exists }}"

    - name: Update current facts
      ansible.builtin.setup: ~

    - name: Verify auditd configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/audit/auditd.conf
        line: "{{ item }}"
        state: present
      check_mode: true
      register: auditd_conf
      failed_when: auditd_conf is changed
      with_items:
        - action_mail_acct = {{ auditd_action_mail_acct }}
        - admin_space_left_action = {{ auditd_admin_space_left_action }}
        - disk_error_action = {{ auditd_disk_error_action }}
        - disk_full_action = {{ auditd_disk_full_action }}
        - max_log_file = {{ auditd_max_log_file }}
        - max_log_file_action = {{ auditd_max_log_file_action }}
        - name_format = hostname
        - num_logs = {{ auditd_num_logs }}
        - space_left = {{ auditd_space_left }}
        - space_left_action = {{ auditd_space_left_action }}

    - name: Verify systemd system.conf
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/systemd/system.conf
        line: "{{ item }}"
        state: present
      check_mode: true
      register: systemd_system_conf
      failed_when: systemd_system_conf is changed
      with_items:
        - DumpCore=no
        - CrashShell=no
        - CtrlAltDelBurstAction=none
        - DefaultLimitCORE=0
        - DefaultLimitNOFILE={{ limit_nofile_hard }}
        - DefaultLimitNPROC={{ limit_nproc_hard }}

    - name: Verify systemd user.conf
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/systemd/user.conf
        line: "{{ item }}"
        state: present
      check_mode: true
      register: systemd_user_conf
      failed_when: systemd_user_conf is changed
      with_items:
        - DefaultLimitCORE=0
        - DefaultLimitNOFILE={{ limit_nofile_hard }}
        - DefaultLimitNPROC={{ limit_nproc_hard }}

    - name: Verify postfix configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/postfix/main.cf
        line: "{{ item }}"
        state: present
      check_mode: true
      register: postfix_conf
      failed_when: postfix_conf is changed
      with_items:
        - disable_vrfy_command = yes
        - inet_interfaces = loopback-only
        - smtpd_banner = \$myhostname - ESMTP
        - smtpd_client_restrictions = permit_mynetworks,reject

    - name: Stat /etc/ssh/sshd_config
      ansible.builtin.stat:
        path: '/etc/ssh/sshd_config'
      register: sshd_config_file

    - name: Stat /etc/ssh/ssh_config
      ansible.builtin.stat:
        path: '/etc/ssh/ssh_config'
      register: ssh_config_file

    - name: Stat /etc/ssh/sshd_config.d/01-hardening.conf
      become: true
      ansible.builtin.stat:
        path: '/etc/ssh/sshd_config.d/01-hardening.conf'
      register: sshd_config_directory

    - name: Assert /etc/ssh/ssh_config permissions
      ansible.builtin.assert:
        that:
          - ssh_config_file.stat.mode == "0644"
        success_msg: "{{ ssh_config_file.stat.path }} has correct permissions: {{ ssh_config_file.stat.mode }}"
        fail_msg: "{{ ssh_config_file.stat.path }} permissions are incorrect: {{ ssh_config_file.stat.mode }}"

    - name: Assert /etc/ssh/sshd_config permissions
      ansible.builtin.assert:
        that:
          - sshd_config_file.stat.mode == "0600"
        success_msg: "{{ sshd_config_file.stat.path }} has correct permissions: {{ sshd_config_file.stat.mode }}"
        fail_msg: "{{ sshd_config_file.stat.path }} permissions are incorrect: {{ sshd_config_file.stat.mode }}"

    - name: Assert /etc/ssh/sshd_config.d/01-hardening.conf permissions
      ansible.builtin.assert:
        that:
          - sshd_config_directory.stat.mode == "0600"
        success_msg: "{{ sshd_config_directory.stat.path }} has correct permissions: {{ sshd_config_directory.stat.mode }}"
        fail_msg: "{{ sshd_config_directory.stat.path }} permissions are incorrect: {{ sshd_config_directory.stat.mode }}"
      when: sshd_config_directory.stat.exists

    - name: Verify sshd_config PermitRootLogin configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/sshd_config
        line: "PermitRootLogin yes"
        state: absent
      check_mode: true
      register: sshd_config
      failed_when: sshd_config is changed

    - name: Verify sshd_config.d PermitRootLogin configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/sshd_config.d/01-hardening.conf
        line: "PermitRootLogin yes"
        state: absent
      check_mode: true
      register: sshd_config_d
      failed_when: sshd_config_d is changed
      when: sshd_config_directory.stat.exists

    - name: Verify sshd_config configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/sshd_config
        line: "{{ item }}"
        state: present
      check_mode: true
      register: sshd_config
      failed_when: sshd_config is changed
      with_items:
        - AllowGroups vagrant sudo
        - AllowAgentForwarding {{ sshd_allow_agent_forwarding }}
        - AllowTcpForwarding {{ sshd_allow_tcp_forwarding }}
        - AuthenticationMethods {{ sshd_authentication_methods }}
        - Banner {{ sshd_banner }}
        - ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }}
        - Ciphers {{ sshd_ciphers }}
        - ClientAliveCountMax {{ sshd_client_alive_count_max | int }}
        - ClientAliveInterval {{ sshd_client_alive_interval | int }}
        - Compression {{ sshd_compression }}
        - GSSAPIAuthentication {{ sshd_gssapi_authentication }}
        - HostbasedAuthentication {{ sshd_hostbased_authentication }}
        - IgnoreUserKnownHosts {{ sshd_ignore_user_known_hosts }}
        - KerberosAuthentication {{ sshd_kerberos_authentication }}
        - HostKeyAlgorithms {{ sshd_host_key_algorithms }}
        - KexAlgorithms {{ sshd_kex_algorithms }}
        - LogLevel {{ sshd_log_level }}
        - LoginGraceTime {{ sshd_login_grace_time | int }}
        - Macs {{ sshd_macs }}
        - MaxAuthTries {{ sshd_max_auth_tries | int }}
        - MaxSessions {{ sshd_max_sessions | int }}
        - MaxStartups {{ sshd_max_startups }}
        - PasswordAuthentication {{ sshd_password_authentication }}
        - PermitEmptyPasswords {{ sshd_permit_empty_passwords }}
        - PermitRootLogin {{ sshd_permit_root_login }}
        - PermitUserEnvironment {{ sshd_permit_user_environment }}
        - Port {{ sshd_port | int }}
        - PrintLastLog {{ sshd_print_last_log }}
        - PrintMotd {{ sshd_print_motd }}
        - RekeyLimit {{ sshd_rekey_limit }}
        - StrictModes {{ sshd_strict_modes }}
        - TCPKeepAlive {{ sshd_tcp_keep_alive }}
        - UseDNS {{ sshd_use_dns }}
        - UsePAM {{ sshd_use_pam }}
        - X11Forwarding {{ sshd_x11_forwarding }}
      when: not sshd_config_directory.stat.exists

    - name: Verify sshd_config.d configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/sshd_config.d/01-hardening.conf
        line: "{{ item }}"
        state: present
      check_mode: true
      register: sshd_config
      failed_when: sshd_config is changed
      with_items:
        - AllowGroups vagrant sudo
        - AllowAgentForwarding {{ sshd_allow_agent_forwarding }}
        - AllowTcpForwarding {{ sshd_allow_tcp_forwarding }}
        - AuthenticationMethods {{ sshd_authentication_methods }}
        - Banner {{ sshd_banner }}
        - ChallengeResponseAuthentication {{ sshd_challenge_response_authentication }}
        - Ciphers {{ sshd_ciphers }}
        - ClientAliveCountMax {{ sshd_client_alive_count_max | int }}
        - ClientAliveInterval {{ sshd_client_alive_interval | int }}
        - Compression {{ sshd_compression }}
        - GSSAPIAuthentication {{ sshd_gssapi_authentication }}
        - HostbasedAuthentication {{ sshd_hostbased_authentication }}
        - IgnoreUserKnownHosts {{ sshd_ignore_user_known_hosts }}
        - KerberosAuthentication {{ sshd_kerberos_authentication }}
        - HostKeyAlgorithms {{ sshd_host_key_algorithms }}
        - KexAlgorithms {{ sshd_kex_algorithms }}
        - LogLevel {{ sshd_log_level }}
        - LoginGraceTime {{ sshd_login_grace_time | int }}
        - Macs {{ sshd_macs }}
        - MaxAuthTries {{ sshd_max_auth_tries | int }}
        - MaxSessions {{ sshd_max_sessions | int }}
        - MaxStartups {{ sshd_max_startups }}
        - PasswordAuthentication {{ sshd_password_authentication }}
        - PermitEmptyPasswords {{ sshd_permit_empty_passwords }}
        - PermitRootLogin {{ sshd_permit_root_login }}
        - PermitUserEnvironment {{ sshd_permit_user_environment }}
        - Port {{ sshd_port | int }}
        - PrintLastLog {{ sshd_print_last_log }}
        - PrintMotd {{ sshd_print_motd }}
        - RekeyLimit {{ sshd_rekey_limit }}
        - StrictModes {{ sshd_strict_modes }}
        - Subsystem {{ sshd_subsystem }}
        - TCPKeepAlive {{ sshd_tcp_keep_alive }}
        - UseDNS {{ sshd_use_dns }}
        - UsePAM {{ sshd_use_pam }}
        - X11Forwarding {{ sshd_x11_forwarding }}
      when: sshd_config_directory.stat.exists

    - name: Verify sshd RequiredRSASize
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/sshd_config.d/01-hardening.conf
        line: "RequiredRSASize {{ sshd_required_rsa_size }}"
        state: present
      check_mode: true
      register: sshd_config_rsasize
      failed_when: sshd_config_rsasize is changed
      when: sshd_config_directory.stat.exists and ssh_installed_version is version('9.1', '>')

    - name: Verify sshd runtime configuration
      become: true
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.command: sshd -T
      register: sshd_config
      changed_when: false
      failed_when: item not in sshd_config.stdout_lines
      with_items:
        - allowgroups sudo
        - allowgroups vagrant
        - allowagentforwarding {{ sshd_allow_agent_forwarding }}
        - allowtcpforwarding {{ sshd_allow_tcp_forwarding }}
        - authenticationmethods {{ sshd_authentication_methods }}
        - banner {{ sshd_banner }}
        - clientalivecountmax {{ sshd_client_alive_count_max | int }}
        - clientaliveinterval {{ sshd_client_alive_interval | int }}
        - compression {{ sshd_compression }}
        - logingracetime {{ sshd_login_grace_time | int }}
        - macs {{ sshd_macs }}
        - maxauthtries {{ sshd_max_auth_tries | int }}
        - maxsessions {{ sshd_max_sessions | int }}
        - maxstartups {{ sshd_max_startups }}
        - permitrootlogin {{ sshd_permit_root_login }}
        - port {{ sshd_port | int }}
        - subsystem {{ sshd_subsystem }}
        - usedns {{ sshd_use_dns }}
        - usepam {{ sshd_use_pam }}
        - x11forwarding {{ sshd_x11_forwarding }}

    - name: Verify ssh_config configuration
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/ssh/ssh_config
        line: "    {{ item }}"
        state: present
      check_mode: true
      register: ssh_config
      failed_when: ssh_config is changed
      with_items:
        - Ciphers {{ sshd_ciphers }}
        - GSSAPIAuthentication {{ sshd_gssapi_authentication }}
        - HashKnownHosts yes
        - HostKeyAlgorithms {{ sshd_host_key_algorithms }}
        - KexAlgorithms {{ sshd_kex_algorithms }}
        - MACs {{ sshd_macs }}
        - RekeyLimit {{ sshd_rekey_limit }}

    - name: Merge sysctl settings
      ansible.builtin.set_fact:
        sysctl_settings: "{{ generic_sysctl_settings | combine(ipv4_sysctl_settings) }}"

    - name: Verify sysctl configuration
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        grep -R "^{{ item.key }}.*{{ item.value | int }}$" "{{ sysctl_conf_dir }}/"
      args:
        executable: /bin/bash
      with_dict: "{{ sysctl_settings | dict2items | rejectattr('key', 'search', 'nf_conntrack') | items2dict }}"
      register: sysctl_file_config
      failed_when: sysctl_file_config.rc != 0
      changed_when: sysctl_file_config.rc != 0

    - name: Verify sysctl runtime configuration
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        sysctl -a | grep "^{{ item.key }}.*{{ item.value | int }}$"
      args:
        executable: /bin/bash
      with_dict: "{{ sysctl_settings | dict2items | rejectattr('key', 'search', 'nf_conntrack') | items2dict }}"
      register: sysctl_runtime_config
      failed_when: sysctl_runtime_config.rc != 0
      changed_when: sysctl_runtime_config.rc != 0

    - name: Stat crypto-policies config
      become: true
      ansible.builtin.stat:
        path: /etc/crypto-policies/config
      register: crypto_policies_config

    - name: Get crypto-policies value
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.shell: grep "^{{ crypto_policy | upper }}$" /etc/crypto-policies/config
      register: crypto_policy_set
      when: crypto_policies_config.stat.exists
      failed_when: crypto_policy_set.rc != 0
      changed_when: crypto_policy_set.rc != 0

    - name: Verify noexec /dev/shm
      ansible.builtin.shell: grep '^tmpfs\s/dev/shm.*noexec' /proc/mounts
      register: dev_shm_noexec
      failed_when: dev_shm_noexec.rc != 0
      changed_when: dev_shm_noexec.rc != 0

    - name: Verify /proc
      ansible.builtin.shell: |
        set -o pipefail
        grep -E '^proc\s/proc proc rw,nosuid,nodev,noexec,relatime.*hidepid=({{ hide_pid }}|invisible)' /proc/mounts
      args:
        executable: /bin/bash
      register: proc_opts
      failed_when: proc_opts.rc != 0
      changed_when: proc_opts.rc != 0
      tags:
        - skip_ansible_lint

    - name: Verify tmp.mount
      become: true
      ansible.builtin.systemd:
        name: tmp.mount
      register: tmp_mount
      failed_when: not 'nosuid,nodev,noexec' in tmp_mount.status.Options
      changed_when: not 'nosuid,nodev,noexec' in tmp_mount.status.Options

    - name: Verify /tmp
      become: true
      ansible.builtin.shell: grep '^tmpfs\s/tmp\s.*nosuid,nodev,noexec' /proc/mounts
      register: mounts_tmp
      failed_when: mounts_tmp.rc != 0
      changed_when: mounts_tmp.rc != 0

    - name: Verify login.defs settings
      become: true
      ansible.builtin.shell: grep "^{{ item }}$" /etc/login.defs
      register: login_defs
      failed_when: login_defs.rc != 0
      changed_when: login_defs.rc != 0
      with_items:
        - ENCRYPT_METHOD SHA512
        - FAILLOG_ENAB yes
        - LOG_OK_LOGINS yes
        - PASS_MAX_DAYS 60
        - PASS_MIN_DAYS 1
        - PASS_WARN_AGE 7
        - SHA_CRYPT_MAX_ROUNDS 65536
        - SHA_CRYPT_MIN_ROUNDS 10000
        - SU_NAME su
        - SYSLOG_SG_ENAB yes
        - SYSLOG_SU_ENAB yes
        - UMASK {{ umask_value }}

    - name: Verify pwquality.conf settings
      become: true
      ansible.builtin.lineinfile:
        path: /etc/security/pwquality.conf
        line: "{{ item.key }} = {{ item.value }}"
        state: present
        mode: "0644"
        owner: root
        group: root
      check_mode: true
      register: pwquality_conf
      failed_when: pwquality_conf is changed
      with_dict: "{{ pwquality_config }}"

    - name: Verify RedHat GRUB audit settings
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        grubby --info="/boot/vmlinuz-$(uname -r)" | grep "^args.*{{ grub_audit_cmdline }} {{ grub_audit_backlog_cmdline }}"
      register: audit_grubenv
      failed_when: audit_grubenv.rc != 0
      changed_when: audit_grubenv.rc != 0
      when: ansible_os_family == "RedHat"

    - name: Verify Debian audit GRUB settings
      become: true
      ansible.builtin.shell: grep "linux.*{{ grub_audit_cmdline }} {{ grub_audit_backlog_cmdline }}" /boot/grub/grub.cfg
      register: audit_grub_cfg
      failed_when: audit_grub_cfg.rc != 0
      changed_when: audit_grub_cfg.rc != 0
      when: ansible_os_family == "Debian"

    - name: Verify RedHat GRUB IPv6 settings
      become: true
      ansible.builtin.shell:
        cmd: |
          set -o pipefail
          grubby --info="/boot/vmlinuz-$(uname -r)" | grep "ipv6.disable=1"
      register: audit_grubenv
      failed_when: audit_grubenv.rc != 0
      changed_when: audit_grubenv.rc != 0
      when: ansible_os_family == "RedHat" and disable_ipv6

    - name: Verify Debian GRUB IPv6 settings
      become: true
      ansible.builtin.shell:
        cmd: grep "linux.*ipv6.disable=1" /boot/grub/grub.cfg
      register: audit_grub_cfg
      failed_when: audit_grub_cfg.rc != 0
      changed_when: audit_grub_cfg.rc != 0
      when: ansible_os_family == "Debian" and disable_ipv6

    - name: Verify IPv6 sysctl configuration
      become: true
      ansible.builtin.shell: grep -R "^{{ item }}$" /etc/sysctl.* /usr/lib/sysctl.d/*
      register: sysctl_ipv6_config
      failed_when: sysctl_ipv6_config.rc != 0
      changed_when: sysctl_ipv6_config.rc != 0
      with_items:
        - net.ipv6.conf.all.disable_ipv6=1
        - net.ipv6.conf.default.disable_ipv6=1
      when: disable_ipv6

    - name: Verify IPv6 sysctl runtime configuration
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        sysctl -a | grep "^{{ item.key }}.*{{ item.value | int }}$"
      args:
        executable: /bin/bash
      with_dict: "{{ ipv6_disable_sysctl_settings }}"
      register: sysctl_ipv6
      changed_when: sysctl_ipv6.rc != 0
      failed_when: sysctl_ipv6.rc != 0
      when: system_has_ipv6 and disable_ipv6

    - name: Find GRUB config files
      become: true
      ansible.builtin.find:
        paths: /boot
        patterns: "*.cfg,grubenv"
        recurse: true
      register: perm_grub_cfg

    - name: Verify GRUB config files permission
      become: true
      ansible.builtin.stat:
        path: "{{ item.path }}"
      changed_when: item.mode | int > 644
      failed_when: item.mode | int > 644
      with_items:
        - "{{ perm_grub_cfg.files | reject('search', '/boot/efi/EFI/ubuntu/grub.cfg') | list }}"
      loop_control:
        label: "{{ item.path }}"

    - name: Verify systemd resolved.conf
      become: true
      ansible.builtin.shell: grep "^{{ item }}$" /etc/systemd/resolved.conf
      register: systemd_resolved_conf
      failed_when: systemd_resolved_conf.rc != 0
      changed_when: systemd_resolved_conf.rc != 0
      with_items:
        - DNS={{ dns }}
        - FallbackDNS={{ fallback_dns }}
        - DNSSEC={{ dnssec }}
        - DNSOverTLS={{ dns_over_tls }}

    - name: Verify systemd timesyncd.conf
      become: true
      ansible.builtin.shell: grep "^{{ item }}$" /etc/systemd/timesyncd.conf
      register: systemd_timesyncd_conf
      failed_when: systemd_timesyncd_conf.rc != 0
      changed_when: systemd_timesyncd_conf.rc != 0
      with_items:
        - NTP={{ ntp }}
        - FallbackNTP={{ fallback_ntp }}

    - name: Stat /etc/default/motd-news
      ansible.builtin.stat:
        path: /etc/default/motd-news
      register: motd_news

    - name: Verify masked motdnews service
      become: true
      ansible.builtin.systemd:
        name: motd-news.service
        masked: true
        enabled: false
        state: stopped
      check_mode: true
      register: motdnews_service
      failed_when: motdnews_service is changed
      when: ansible_os_family == "Debian" and motd_news.stat.exists

    - name: Stat /usr/bin/pro
      ansible.builtin.stat:
        path: /usr/bin/pro
      register: ubuntu_advantage_pro
      when: ansible_os_family == "Debian"

    - name: Verify that apt_news is disabled
      ansible.builtin.shell: |
        set -o pipefail
        pro config show | grep '^apt_news.*False'
      args:
        executable: /bin/bash
      register: ubuntu_advantage_pro_state
      changed_when: ubuntu_advantage_pro_state.rc != 0
      failed_when: ubuntu_advantage_pro_state.rc != 0
      when: ansible_os_family == "Debian" and ubuntu_advantage_pro.stat.exists

    - name: Efi fact
      ansible.builtin.set_fact:
        booted_with_efi: "{{ ansible_mounts | selectattr('mount', 'equalto', '/boot/efi') | list | length > 0 }}"

    - name: Verify cracklib password list
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.shell: |
        set -o pipefail
        grep '/usr/share/dict/passwords' /var/cache/cracklib/src-dicts
      args:
        executable: /bin/bash
      register: cracklib_passwords
      failed_when: cracklib_passwords.rc != 0
      changed_when: cracklib_passwords.rc != 0
      when: ansible_os_family == "Debian"

    - name: Index blacklisted kernel modules
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.shell: |
        set -o pipefail
        modprobe -c | grep -o '^blacklist .*' | awk '{print $2}'
      args:
        executable: /bin/bash
      changed_when: false
      failed_when: modprobe_blacklist.rc != 0
      register: modprobe_blacklist
      when: block_blacklisted | bool

    - name: Verify blocked blacklisted kernel modules
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.shell: |
        set -o pipefail
        grep 'install {{ item }} /bin/true' /etc/modprobe.d/blockblacklisted.conf
      args:
        executable: /bin/bash
      register: modprobe_blockblacklisted
      failed_when: modprobe_blockblacklisted.rc != 0
      changed_when: modprobe_blockblacklisted.rc != 0
      with_items:
        - "{{ modprobe_blacklist.stdout_lines | sort }}"
      when: block_blacklisted | bool

    - name: Verify sudo settings
      ansible.builtin.shell: |
        set -o pipefail
        sudo -l | grep "{{ item }}"
      register: sudo_settings
      failed_when: sudo_settings.rc != 0
      changed_when: sudo_settings.rc != 0
      args:
        executable: /bin/bash
      with_items:
        - 'use_pty'
        - 'logfile="/var/log/sudo.log"'
        - '!pwfeedback'
        - '!visiblepw'
        - 'passwd_timeout=1'
        - 'timestamp_timeout=5'
        - 'timestamp_type=tty'
        - "!rootpw"
        - "!runaspw"
        - "!targetpw"

    - name: Stat faillock.conf
      become: true
      ansible.builtin.stat:
        path: /etc/security/faillock.conf
      register: faillockconf

    - name: Verify faillock.conf
      become: true
      ansible.builtin.lineinfile:
        dest: /etc/security/faillock.conf
        mode: "0644"
        state: present
        line: "{{ item }}"
      check_mode: true
      register: verify_faillock
      failed_when: verify_faillock is changed
      with_items:
        - "audit"
        - "local_users_only"
        - "deny = 5"
        - "fail_interval = 900"
      when: faillockconf.stat.exists

    - name: Verify wireless state
      environment:
        PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      ansible.builtin.command: nmcli -t radio
      register: wireless_state
      changed_when: not 'disabled' in wireless_state.stdout
      failed_when: not 'disabled' in wireless_state.stdout
      when: disable_wireless

    - name: Stat firewall rules when UFW is enabled
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        ufw show added | grep '^ufw' | grep -v "'ansible\smanaged'" | sed 's/ufw //g'
      args:
        executable: /bin/bash
      failed_when: ufw_not_managed.rc > 1
      changed_when: false
      register: ufw_not_managed
      when: ufw_enable

    - name: Stat firewall rules when UFW is disabled
      become: true
      ansible.builtin.shell: |
        set -o pipefail
        ufw show added | grep '^ufw' | grep "'ansible\smanaged'" | sed 's/ufw //g'
      args:
        executable: /bin/bash
      failed_when: ufw_not_managed.rc > 1
      changed_when: false
      register: ufw_not_managed
      when: not ufw_enable

    - name: Create test user
      become: true
      ansible.builtin.user:
        name: roletestuser
        password: "{{ 'Ansible Role Test User' | password_hash('sha512') }}"
        state: present
        shell: /bin/bash

    - name: Create test user salt
      ansible.builtin.set_fact:
        test_user_salt: "{{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=16') }}"

    - name: Change test user password
      become: true
      ansible.builtin.user:
        name: roletestuser
        password: "{{ 'roletestuser' | password_hash('sha512', test_user_salt, rounds=656000) }}"
      register: test_user_pass

    - name: Debug test user salt
      ansible.builtin.debug:
        msg: "{{ test_user_salt }}"

    - name: Debug test user password
      ansible.builtin.debug:
        msg: "{{ test_user_pass }}"
...
