Do not allow users to reuse recent passwords. This can be accomplished by using the
remember option for the pam_pwhistory PAM module.
On systems with newer versions of authselect, the pam_pwhistory PAM module
can be enabled via authselect feature:
authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile.
Newer systems also have the /etc/security/pwhistory.conf file for setting
pam_pwhistory module options. This file should be used whenever available.
Otherwise, the pam_pwhistory module options can be set in PAM files.
The value for remember option must be equal or greater than
warning alert:
Warning
If the system relies on authselect tool to manage PAM settings, the remediation
will also use authselect tool. However, if any manual modification was made in
PAM files, the authselect integrity check will fail and the remediation will be
aborted in order to preserve intentional changes. In this case, an informative message will
be shown in the remediation report.
warning alert:
Warning
Newer versions of authselect contain an authselect feature to easily and properly
enable pam_pwhistory.so module. If this feature is not yet available in your
system, an authselect custom profile must be used to avoid integrity issues in PAM files.
If a custom profile was created and used in the system before this authselect feature was
available, the new feature can't be used with this custom profile and the
remediation will fail. In this case, the custom profile should be recreated or manually
updated.
Rationale
Preventing re-use of previous passwords helps ensure that a compromised password is not
re-used by a user.
ISA-62443-2-1-2009, Security for Industrial Automation and Control Systems Part 2-1: Establishing an Industrial Automation and Control Systems Security Program
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: XCCDF Value var_password_pam_remember # promote to variable
set_fact:
var_password_pam_remember: !!str <xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_remember" use="legacy"/>
tags:
- always
- name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
set_fact:
var_password_pam_remember_control_flag: !!str <xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_remember_control_flag" use="legacy"/>
tags:
- always
- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
tool'
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - Collect the available authselect features'
ansible.builtin.command:
cmd: authselect list-features minimal
register: result_authselect_available_features
changed_when: false
when:
- '"pam" in ansible_facts.packages'
- result_authselect_present.stat.exists
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so using authselect
feature'
block:
- name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
profile'
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: 'Limit Password Reuse: password-auth - Informative message based on the
authselect integrity check result'
ansible.builtin.assert:
that:
- 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
- name: 'Limit Password Reuse: password-auth - Get authselect current features'
ansible.builtin.shell:
cmd: authselect current | tail -n+3 | awk '{ print $2 }'
register: result_authselect_features
changed_when: false
when:
- result_authselect_check_cmd is success
- name: 'Limit Password Reuse: password-auth - Ensure "with-pwhistory" feature is
enabled using authselect tool'
ansible.builtin.command:
cmd: authselect enable-feature with-pwhistory
register: result_authselect_enable_feature_cmd
when:
- result_authselect_check_cmd is success
- result_authselect_features.stdout is not search("with-pwhistory")
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_enable_feature_cmd is not skipped
- result_authselect_enable_feature_cmd is success
when:
- '"pam" in ansible_facts.packages'
- result_authselect_present.stat.exists
- result_authselect_available_features.stdout is search("with-pwhistory")
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so in appropriate
PAM files'
block:
- name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
as a local fact'
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/password-auth
- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
tool'
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
is used if authselect is present'
block:
- name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
profile'
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: 'Limit Password Reuse: password-auth - Informative message based on the
authselect integrity check result'
ansible.builtin.assert:
that:
- 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
- name: 'Limit Password Reuse: password-auth - Get authselect current profile'
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
based on the current profile'
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("custom/")
- not result_authselect_custom_profile_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
is selected'
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
according to the custom authselect profile'
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
is present in {{ pam_file_path }}'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+{{ '{{ var_password_pam_remember_control_flag.split(",")[0]
}}' | regex_escape() }}\s+pam_pwhistory.so\s*.*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_present
- name: 'Limit Password Reuse: password-auth - Include or update the PAM module
line in {{ pam_file_path }}'
block:
- name: 'Limit Password Reuse: password-auth - Check if required PAM module line
is present in {{ pam_file_path }} with different control'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_other_control_present
- name: 'Limit Password Reuse: password-auth - Ensure the correct control for
the required PAM module line in {{ pam_file_path }}'
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
replace: \1{{ var_password_pam_remember_control_flag.split(",")[0] }} \2
register: result_pam_module_edit
when:
- result_pam_line_other_control_present.found == 1
- name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
line is included in {{ pam_file_path }}'
ansible.builtin.lineinfile:
dest: '{{ pam_file_path }}'
insertafter: ^password.*requisite.*pam_pwquality\.so
line: password {{ var_password_pam_remember_control_flag.split(",")[0]
}} pam_pwhistory.so
register: result_pam_module_add
when:
- result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
> 1
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present is defined
- result_authselect_present.stat.exists
- |-
(result_pam_module_add is defined and result_pam_module_add.changed)
or (result_pam_module_edit is defined and result_pam_module_edit.changed)
when:
- result_pam_line_present.found is defined
- result_pam_line_present.found == 0
when:
- '"pam" in ansible_facts.packages'
- |
(result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - Check the presence of /etc/security/pwhistory.conf
file'
ansible.builtin.stat:
path: /etc/security/pwhistory.conf
register: result_pwhistory_conf_check
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
in /etc/security/pwhistory.conf file'
block:
- name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
parameter in /etc/security/pwhistory.conf'
ansible.builtin.lineinfile:
path: /etc/security/pwhistory.conf
regexp: ^\s*remember\s*=
line: remember = {{ var_password_pam_remember }}
state: present
- name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
parameter is removed from PAM files'
block:
- name: 'Limit Password Reuse: password-auth - Check if /etc/pam.d/password-auth
file is present'
ansible.builtin.stat:
path: /etc/pam.d/password-auth
register: result_pam_file_present
- name: 'Limit Password Reuse: password-auth - Check the proper remediation for
the system'
block:
- name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
as a local fact'
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/password-auth
- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
tool'
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
is used if authselect is present'
block:
- name: 'Limit Password Reuse: password-auth - Check integrity of authselect
current profile'
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: 'Limit Password Reuse: password-auth - Informative message based on
the authselect integrity check result'
ansible.builtin.assert:
that:
- 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
- name: 'Limit Password Reuse: password-auth - Get authselect current profile'
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - Create an authselect custom
profile based on the current profile'
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("custom/")
- not result_authselect_custom_profile_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - Ensure the authselect custom
profile is selected'
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
according to the custom authselect profile'
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Ensure the "remember" option
from "pam_pwhistory.so" is not present in {{ pam_file_path }}'
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
applied'
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- result_pam_option_removal is changed
when:
- result_pam_file_present.stat.exists
when:
- '"pam" in ansible_facts.packages'
- result_pwhistory_conf_check.stat.exists
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
in PAM files'
block:
- name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
as a local fact'
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/password-auth
- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
tool'
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
is used if authselect is present'
block:
- name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
profile'
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: 'Limit Password Reuse: password-auth - Informative message based on the
authselect integrity check result'
ansible.builtin.assert:
that:
- 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
- name: 'Limit Password Reuse: password-auth - Get authselect current profile'
ansible.builtin.shell:
cmd: authselect current -r | awk '{ print $1 }'
register: result_authselect_profile
changed_when: false
when:
- result_authselect_check_cmd is success
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_profile.stdout is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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
when:
- result_authselect_profile is not skipped
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - 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:
- authselect_current_profile is not match("custom/")
- name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
based on the current profile'
ansible.builtin.command:
cmd: authselect create-profile hardening -b {{ authselect_current_profile
}}
when:
- result_authselect_check_cmd is success
- authselect_current_profile is not match("custom/")
- not result_authselect_custom_profile_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
is selected'
ansible.builtin.command:
cmd: authselect select {{ authselect_custom_profile }}
register: result_pam_authselect_select_profile
when:
- 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)
- name: 'Limit Password Reuse: password-auth - 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:
- result_authselect_profile is not skipped
- result_authselect_features is not skipped
- result_pam_authselect_select_profile is not skipped
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
when:
- result_authselect_check_cmd is success
- result_authselect_profile is not skipped
- result_pam_authselect_restore_features is not skipped
- name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
according to the custom authselect profile'
ansible.builtin.set_fact:
pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
| basename }}
when:
- result_authselect_present.stat.exists
- name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
is present in {{ pam_file_path }}'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_present
- name: 'Limit Password Reuse: password-auth - Include or update the PAM module
line in {{ pam_file_path }}'
block:
- name: 'Limit Password Reuse: password-auth - Check if required PAM module line
is present in {{ pam_file_path }} with different control'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
state: absent
check_mode: true
changed_when: false
register: result_pam_line_other_control_present
- name: 'Limit Password Reuse: password-auth - Ensure the correct control for
the required PAM module line in {{ pam_file_path }}'
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
replace: \1requisite \2
register: result_pam_module_edit
when:
- result_pam_line_other_control_present.found == 1
- name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
line is included in {{ pam_file_path }}'
ansible.builtin.lineinfile:
dest: '{{ pam_file_path }}'
line: password requisite pam_pwhistory.so
register: result_pam_module_add
when:
- result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
> 1
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present is defined
- result_authselect_present.stat.exists
- |-
(result_pam_module_add is defined and result_pam_module_add.changed)
or (result_pam_module_edit is defined and result_pam_module_edit.changed)
when:
- result_pam_line_present.found is defined
- result_pam_line_present.found == 0
- name: 'Limit Password Reuse: password-auth - Check if the required PAM module
option is present in {{ pam_file_path }}'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
state: absent
check_mode: true
changed_when: false
register: result_pam_module_remember_option_present
- name: 'Limit Password Reuse: password-auth - Ensure the "remember" PAM option
for "pam_pwhistory.so" is included in {{ pam_file_path }}'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
backrefs: true
regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so.*)
line: \1 remember={{ var_password_pam_remember }}
state: present
register: result_pam_remember_add
when:
- result_pam_module_remember_option_present.found == 0
- name: 'Limit Password Reuse: password-auth - Ensure the required value for "remember"
PAM option from "pam_pwhistory.so" in {{ pam_file_path }}'
ansible.builtin.lineinfile:
path: '{{ pam_file_path }}'
backrefs: true
regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]+\s*(.*)
line: \1\2={{ var_password_pam_remember }} \3
register: result_pam_remember_edit
when:
- result_pam_module_remember_option_present.found > 0
- name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
ansible.builtin.command:
cmd: authselect apply-changes -b
when:
- result_authselect_present.stat.exists
- (result_pam_remember_add is defined and result_pam_remember_add.changed) or
(result_pam_remember_edit is defined and result_pam_remember_edit.changed)
when:
- '"pam" in ansible_facts.packages'
- not result_pwhistory_conf_check.stat.exists
tags:
- CCE-86354-8
- CJIS-5.6.2.1.1
- DISA-STIG-RHEL-09-611015
- NIST-800-171-3.5.8
- NIST-800-53-IA-5(1)(e)
- NIST-800-53-IA-5(f)
- PCI-DSS-Req-8.2.5
- PCI-DSSv4-8.3.7
- accounts_password_pam_pwhistory_remember_password_auth
- configure_strategy
- low_complexity
- medium_disruption
- medium_severity
- no_reboot_needed
Remediation - Shell Script
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then
var_password_pam_remember='<xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_remember" use="legacy"/>'
var_password_pam_remember_control_flag='<xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_password_pam_remember_control_flag" use="legacy"/>'
var_password_pam_remember_control_flag="$(echo $var_password_pam_remember_control_flag | cut -d \, -f 1)"
if [ -f /usr/bin/authselect ]; then
if authselect list-features minimal | grep -q with-pwhistory; 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
authselect enable-feature with-pwhistory
authselect apply-changes -b
else
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 }')
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
PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "$PAM_FILE_PATH"
else
LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
if [ ! -z $LAST_MATCH_LINE ]; then
sed -i --follow-symlinks $LAST_MATCH_LINE' a password '"$var_password_pam_remember_control_flag"' pam_pwhistory.so' "$PAM_FILE_PATH"
else
echo 'password '"$var_password_pam_remember_control_flag"' pam_pwhistory.so' >> "$PAM_FILE_PATH"
fi
fi
fi
fi
else
if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "/etc/pam.d/password-auth"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/password-auth")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "/etc/pam.d/password-auth"
else
LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/password-auth" | tail -n 1 | cut -d: -f 1)
if [ ! -z $LAST_MATCH_LINE ]; then
sed -i --follow-symlinks $LAST_MATCH_LINE' a password '"$var_password_pam_remember_control_flag"' pam_pwhistory.so' "/etc/pam.d/password-auth"
else
echo 'password '"$var_password_pam_remember_control_flag"' pam_pwhistory.so' >> "/etc/pam.d/password-auth"
fi
fi
fi
fi
PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
regex="^\s*remember\s*="
line="remember = $var_password_pam_remember"
if ! grep -q $regex $PWHISTORY_CONF; then
echo $line >> $PWHISTORY_CONF
else
sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
fi
if [ -e "/etc/pam.d/password-auth" ] ; then
PAM_FILE_PATH="/etc/pam.d/password-auth"
if [ -f /usr/bin/authselect ]; 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 }')
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
PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP '^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks 's/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "/etc/pam.d/password-auth was not found" >&2
fi
else
PAM_FILE_PATH="/etc/pam.d/password-auth"
if [ -f /usr/bin/authselect ]; 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 }')
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
PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
# Line matching group + control + module was not found. Check group + module.
if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
# The control is updated only if one single line matches.
sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "$PAM_FILE_PATH"
else
echo 'password '"requisite"' pam_pwhistory.so' >> "$PAM_FILE_PATH"
fi
fi
# Check the option
if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*\sremember\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks '/\s*password\s+'"requisite"'\s+pam_pwhistory.so.*/ s/$/ remember='"$var_password_pam_remember"'/' "$PAM_FILE_PATH"
else
sed -i -E --follow-symlinks 's/(\s*password\s+'"requisite"'\s+pam_pwhistory.so\s+.*)('"remember"'=)[[:alnum:]]+\s*(.*)/\1\2'"$var_password_pam_remember"' \3/' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
fi
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi