For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/security/default_rules/def-000-r9i.md. A documentation index is available at /llms.txt.

Ensure Active Authselect Profile Includes PAM Modules

Description

The active authselect profile must include the required PAM modules: pam_pwquality.so, pam_pwhistory.so, pam_faillock.so, and pam_unix.so in both system-auth and password-auth files. A custom authselect profile can be created by copying and customizing one of the default profiles. The default profiles include: local, sssd, and winbind. These profiles can be customized to follow site specific requirements.

Rationale

A custom profile is required to customize many of the PAM options. Modifications made to a default profile may be overwritten during an update. When you deploy a profile, the profile is applied to every user logging into the given host.

Remediation

Shell script

The following script can be run on the host to remediate the issue.

#!/bin/bash

# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

if ! authselect check; then
echo "
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."
exit 1
fi

CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
# If not already in use, a custom profile is created preserving the enabled features.
if [[ ! $CURRENT_PROFILE == custom/* ]]; then
    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
    # The "local" profile does not contain essential security features required by multiple Benchmarks.
    # If currently used, it is replaced by "sssd", which is the best option in this case.
    if [[ $CURRENT_PROFILE == local ]]; then
        CURRENT_PROFILE="sssd"
    fi
    authselect create-profile hardening -b $CURRENT_PROFILE
    CURRENT_PROFILE="custom/hardening"
    
    authselect apply-changes -b --backup=before-hardening-custom-profile
    authselect select $CURRENT_PROFILE
    for feature in $ENABLED_FEATURES; do
        authselect enable-feature $feature;
    done
    
    authselect apply-changes -b --backup=after-hardening-custom-profile
fi

# Function to add a missing PAM module to a file
# This function handles module placement based on typical PAM stack ordering
add_pam_module() {
    local authselect_file="$1"
    local module="$2"

    # Check if module already exists in any group
    if grep -Pq "^\s*\S+\s+\S+\s+$module" "$authselect_file"; then
        return 0
    fi

    # Special handling for pam_faillock.so - needs entries in auth and account groups
    if [ "$module" = "pam_faillock.so" ]; then
        # Add preauth entry in auth section (at the beginning of auth section)
        if ! grep -Pq "^\s*auth\s+\S+\s+$module" "$authselect_file"; then
            if grep -qP "^auth" "$authselect_file"; then
                FIRST_AUTH_LINE=$(grep -nP "^auth" "$authselect_file" | head -n 1 | cut -d: -f 1)
                if [ ! -z "$FIRST_AUTH_LINE" ]; then
                    sed -i --follow-symlinks "${FIRST_AUTH_LINE}i auth     required    pam_faillock.so preauth" "$authselect_file"
                fi
            else
                # If no auth section exists, create it at the beginning
                sed -i --follow-symlinks "1i auth     required    pam_faillock.so preauth" "$authselect_file"
            fi
        fi
        # Add entry in account section
        if ! grep -Pq "^\s*account\s+\S+\s+$module" "$authselect_file"; then
            if grep -qP "^account" "$authselect_file"; then
                FIRST_ACCOUNT_LINE=$(grep -nP "^account" "$authselect_file" | head -n 1 | cut -d: -f 1)
                if [ ! -z "$FIRST_ACCOUNT_LINE" ]; then
                    sed -i --follow-symlinks "${FIRST_ACCOUNT_LINE}a account     required    pam_faillock.so" "$authselect_file"
                fi
            else
                # If no account section exists, add it after auth section
                if grep -qP "^auth" "$authselect_file"; then
                    LAST_AUTH_LINE=$(grep -nP "^auth" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                    if [ ! -z "$LAST_AUTH_LINE" ]; then
                        sed -i --follow-symlinks "${LAST_AUTH_LINE}a account     required    pam_faillock.so" "$authselect_file"
                    fi
                else
                    echo "account     required    pam_faillock.so" >> "$authselect_file"
                fi
            fi
        fi
        return 0
    fi

    # Handle pam_pwquality.so - goes in password section, before other password modules
    if [ "$module" = "pam_pwquality.so" ]; then
        if grep -qP "^password" "$authselect_file"; then
            FIRST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | head -n 1 | cut -d: -f 1)
            if [ ! -z "$FIRST_PASSWORD_LINE" ]; then
                sed -i --follow-symlinks "${FIRST_PASSWORD_LINE}i password     requisite    pam_pwquality.so" "$authselect_file"
            fi
        else
            # If no password section exists, add it after account section
            if grep -qP "^account" "$authselect_file"; then
                LAST_ACCOUNT_LINE=$(grep -nP "^account" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                if [ ! -z "$LAST_ACCOUNT_LINE" ]; then
                    sed -i --follow-symlinks "${LAST_ACCOUNT_LINE}a password     requisite    pam_pwquality.so" "$authselect_file"
                fi
            else
                echo "password     requisite    pam_pwquality.so" >> "$authselect_file"
            fi
        fi
        return 0
    fi

    # Handle pam_pwhistory.so - goes in password section, after pam_pwquality
    if [ "$module" = "pam_pwhistory.so" ]; then
        if grep -qP "pam_pwquality\.so" "$authselect_file"; then
            # Add after pam_pwquality
            PWQUALITY_LINE=$(grep -nP "pam_pwquality\.so" "$authselect_file" | tail -n 1 | cut -d: -f 1)
            if [ ! -z "$PWQUALITY_LINE" ]; then
                sed -i --follow-symlinks "${PWQUALITY_LINE}a password     requisite    pam_pwhistory.so" "$authselect_file"
            fi
        elif grep -qP "^password" "$authselect_file"; then
            # Add at the beginning of password section if pam_pwquality not found
            FIRST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | head -n 1 | cut -d: -f 1)
            if [ ! -z "$FIRST_PASSWORD_LINE" ]; then
                sed -i --follow-symlinks "${FIRST_PASSWORD_LINE}i password     requisite    pam_pwhistory.so" "$authselect_file"
            fi
        else
            echo "password     requisite    pam_pwhistory.so" >> "$authselect_file"
        fi
        return 0
    fi

    # Handle pam_unix.so - typically appears in multiple groups (auth, account, password, session)
    # We'll add it to password group if missing, as that's most critical for this rule
    if [ "$module" = "pam_unix.so" ]; then
        # Check if it exists in password group
        if ! grep -Pq "^\s*password\s+\S+\s+$module" "$authselect_file"; then
            if grep -qP "pam_pwhistory\.so" "$authselect_file"; then
                # Add after pam_pwhistory
                PWHISTORY_LINE=$(grep -nP "pam_pwhistory\.so" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                if [ ! -z "$PWHISTORY_LINE" ]; then
                    sed -i --follow-symlinks "${PWHISTORY_LINE}a password     sufficient    pam_unix.so" "$authselect_file"
                fi
            elif grep -qP "^password" "$authselect_file"; then
                # Add at the end of password section
                LAST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                if [ ! -z "$LAST_PASSWORD_LINE" ]; then
                    sed -i --follow-symlinks "${LAST_PASSWORD_LINE}a password     sufficient    pam_unix.so" "$authselect_file"
                fi
            else
                echo "password     sufficient    pam_unix.so" >> "$authselect_file"
            fi
        fi
        return 0
    fi
}

# Check and ensure modules are present in both system-auth and password-auth
pam_profile="$(head -1 /etc/authselect/authselect.conf)"
pam_profile_path="/etc/authselect/$pam_profile"
for authselect_file in "$pam_profile_path"/system-auth "$pam_profile_path"/password-auth; do
    if [ ! -f "$authselect_file" ]; then
        echo "Warning: $authselect_file not found"
        continue
    fi
    for module in pam_pwquality.so pam_pwhistory.so pam_faillock.so pam_unix.so; do
        if ! grep -Pq "^\s*\S+\s+\S+\s+$module" "$authselect_file"; then
            add_pam_module "$authselect_file" "$module"
        fi
    done
done

authselect apply-changes

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi

Ansible playbook

The following playbook can be run with Ansible to remediate the issue.

- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-90716-2
  - 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: '"pam" in ansible_facts.packages'
  tags:
  - CCE-90716-2
  - 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: '"pam" in ansible_facts.packages'
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_profile.stdout is match("custom/")
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - result_authselect_profile.stdout is not match("custom/")
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_profile is not skipped
  - authselect_current_profile is not match("custom/")
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - result_authselect_profile_name is not skipped
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" in ansible_facts.packages'
  - result_authselect_check_cmd is success
  - pam_profile_path is defined
  tags:
  - CCE-90716-2
  - 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:
  - '"pam" 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:
  - CCE-90716-2
  - accounts_password_pam_modules_in_authselect_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Warning

If local site customizations have been made to the authselect template or files in /etc/pam.d, these custom entries should be added to the newly created custom profile before it’s applied to the system. The order within the PAM stacks is important when adding these entries.