# platform = multi_platform_all
# reboot = false
# strategy = restrict
# complexity = low
# disruption = low
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Check integrity of
    authselect current profile
  ansible.builtin.command:
    cmd: authselect check
  register: result_authselect_check_cmd
  changed_when: false
  check_mode: false
  failed_when: false
  when: '"libpam-runtime" in ansible_facts.packages'
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Informative message
    based on the authselect integrity check result
  ansible.builtin.assert:
    that:
    - ansible_check_mode or result_authselect_check_cmd.rc == 0
    fail_msg:
    - authselect integrity check failed. Remediation aborted!
    - This remediation could not be applied because an authselect profile was not
      selected or the selected profile is not intact.
    - It is not recommended to manually edit the PAM files when authselect tool is
      available.
    - In cases where the default authselect profile does not cover a specific demand,
      a custom authselect profile is recommended.
    success_msg:
    - authselect integrity check passed
  when: '"libpam-runtime" in ansible_facts.packages'
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Get authselect current
    profile
  ansible.builtin.shell:
    cmd: authselect current -r | awk '{ print $1 }'
  register: result_authselect_profile
  changed_when: false
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Define the current
    authselect profile as a local fact
  ansible.builtin.set_fact:
    authselect_current_profile: '{{ result_authselect_profile.stdout }}'
    authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_profile.stdout is match("custom/")
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Define the new authselect
    custom profile as a local fact
  ansible.builtin.set_fact:
    authselect_current_profile: '{{ result_authselect_profile.stdout }}'
    authselect_custom_profile: custom/hardening
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_profile.stdout is not match("custom/")
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Get authselect current
    features to also enable them in the custom profile
  ansible.builtin.shell:
    cmd: authselect current | tail -n+3 | awk '{ print $2 }'
  register: result_authselect_features
  changed_when: false
  check_mode: false
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Check if any custom
    profile with the same name was already created
  ansible.builtin.stat:
    path: /etc/authselect/{{ authselect_custom_profile }}
  register: result_authselect_custom_profile_present
  changed_when: false
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Create an authselect
    custom profile based on the current profile
  ansible.builtin.command:
    cmd: authselect create-profile hardening -b {{ authselect_current_profile }}
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_check_cmd is success
  - authselect_current_profile is not match("^(custom/|local)")
  - not result_authselect_custom_profile_present.stat.exists
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Create an authselect
    custom profile based on sssd profile
  ansible.builtin.command:
    cmd: authselect create-profile hardening -b sssd
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_check_cmd is success
  - authselect_current_profile is match("local")
  - not result_authselect_custom_profile_present.stat.exists
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
    changes are applied
  ansible.builtin.command:
    cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  - authselect_custom_profile is not match(authselect_current_profile)
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Ensure the authselect
    custom profile is selected
  ansible.builtin.command:
    cmd: authselect select {{ authselect_custom_profile }}
  register: result_pam_authselect_select_profile
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  - authselect_custom_profile is not match(authselect_current_profile)
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Restore the authselect
    features in the custom profile
  ansible.builtin.command:
    cmd: authselect enable-feature {{ item }}
  loop: '{{ result_authselect_features.stdout_lines }}'
  register: result_pam_authselect_restore_features
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_features is not skipped
  - result_pam_authselect_select_profile is not skipped
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
    changes are applied
  ansible.builtin.command:
    cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - result_authselect_profile is not skipped
  - result_pam_authselect_restore_features is not skipped
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Get authselect current
    profile
  ansible.builtin.command: head -1 /etc/authselect/authselect.conf
  register: result_authselect_profile_name
  changed_when: false
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Determine PAM profile
    path
  ansible.builtin.set_fact:
    pam_profile_path: '{%- if result_authselect_profile_name.stdout is match(''^custom/'')
      -%} /etc/authselect/{{ result_authselect_profile_name.stdout }} {%- else -%}
      /usr/share/authselect/default/{{ result_authselect_profile_name.stdout }} {%-
      endif %}'
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - result_authselect_profile_name is not skipped
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Ensure PAM modules
    are present in system-auth and password-auth
  block:

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if {{ item
      }} file exists
    ansible.builtin.stat:
      path: '{{ pam_profile_path }}/{{ item }}'
    register: pam_file_stat
    loop:
    - system-auth
    - password-auth
    when:
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Set list of PAM
      files to process
    ansible.builtin.set_fact:
      pam_files_to_process: '{{ pam_file_stat.results | default([]) | selectattr(''stat.exists'',
        ''equalto'', true) | map(attribute=''item'') | list }}'

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_faillock.so
      exists in auth section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*auth\s+\S+\s+pam_faillock\.so\s+preauth
      state: absent
    check_mode: true
    changed_when: false
    register: pam_faillock_auth_check_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_faillock.so
      preauth entry in auth section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*auth\s+\S+\s+pam_faillock\.so\s+preauth
      insertbefore: ^auth
      line: auth     required    pam_faillock.so preauth
      state: present
    register: pam_faillock_auth_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_faillock_auth_check_result.results | selectattr('item', 'equalto', item)
      | map(attribute='found') | first | default(1) == 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_faillock.so
      exists in account section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*account\s+\S+\s+pam_faillock\.so
      state: absent
    check_mode: true
    changed_when: false
    register: pam_faillock_account_check_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_faillock.so
      entry in account section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*account\s+\S+\s+pam_faillock\.so
      insertafter: ^account
      line: account     required    pam_faillock.so
      state: present
    register: pam_faillock_account_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_faillock_account_check_result.results | selectattr('item', 'equalto', item)
      | map(attribute='found') | first | default(1) == 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_pwquality.so
      exists in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_pwquality\.so
      state: absent
    check_mode: true
    changed_when: false
    register: pam_pwquality_check_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwquality.so
      entry in password section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_pwquality\.so
      insertbefore: ^password
      line: password     requisite    pam_pwquality.so
      state: present
    register: pam_pwquality_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_pwhistory.so
      exists in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
      state: absent
    check_mode: true
    changed_when: false
    register: pam_pwhistory_check_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwhistory.so
      entry after pam_pwquality in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
      insertafter: ^.*pam_pwquality\.so.*
      line: password     requisite    pam_pwhistory.so
      state: present
    register: pam_pwhistory_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0
    - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(0) > 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwhistory.so
      entry at beginning of password section in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
      insertbefore: ^password
      line: password     requisite    pam_pwhistory.so
      state: present
    register: pam_pwhistory_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0
    - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_unix.so
      exists in password section of {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_unix\.so
      state: absent
    check_mode: true
    changed_when: false
    register: pam_unix_check_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_unix.so
      entry after pam_pwhistory in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_unix\.so
      insertafter: ^.*pam_pwhistory\.so.*
      line: password     sufficient    pam_unix.so
      state: present
    register: pam_unix_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_unix_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0
    - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(0) > 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_unix.so
      entry at end of password section in {{ item }}
    ansible.builtin.lineinfile:
      path: '{{ pam_profile_path }}/{{ item }}'
      regexp: ^\s*password\s+\S+\s+pam_unix\.so
      insertafter: ^password.*
      line: password     sufficient    pam_unix.so
      state: present
    register: pam_unix_add_result
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
    - pam_unix_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0
    - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
      | first | default(1) == 0

  - name: Ensure Active Authselect Profile Includes PAM Modules - Store results for
      {{ item }}
    ansible.builtin.set_fact:
      pam_changes_{{ item | replace('-', '_') }}: |-
        {{ ((pam_faillock_auth_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
           (pam_faillock_account_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
           (pam_pwquality_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
           (pam_pwhistory_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
           (pam_unix_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false))) }}
    loop: '{{ pam_files_to_process | default([]) }}'
    when:
    - item is defined
    - pam_profile_path is defined
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - pam_profile_path is defined
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
    changes are applied
  ansible.builtin.command:
    cmd: authselect apply-changes -b
  when:
  - '"libpam-runtime" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - (pam_changes_system_auth is defined and pam_changes_system_auth) or (pam_changes_password_auth
    is defined and pam_changes_password_auth)
  tags:
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
