Lock Accounts Must Persist
An XCCDF Rule
Description
This rule ensures that the system lock out accounts using pam_faillock.so
persist
after system reboot. From "pam_faillock" man pages:
Note that the default directory that "pam_faillock" uses is usually cleared on system boot so the access will be reenabled after system reboot. If that is undesirable, a different tally directory must be set with the "dir" option.pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. In order to avoid errors when manually editing these files, it is recommended to use the appropriate tools, such as
authselect
or authconfig
,
depending on the OS version.
The chosen profile expects the directory to be
.
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.
If the system supports the /etc/security/faillock.conf
file, the pam_faillock
parameters should be defined in faillock.conf
file.Rationale
Locking out user accounts after a number of incorrect attempts prevents direct password
guessing attacks. In combination with the silent
option, user enumeration attacks
are also mitigated.
- ID
- xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_dir
- Severity
- Medium
- References
- CCI: Control Correlation IdentifierNIST Special Publication 800-53 (Revision 4): Security and Privacy Controls for Federal Information Systems and OrganizationsGPOS SRG: General Purpose Operating System Security Requirements GuideCCE: Common Configuration EnumerationSTIG: Security Technical Implementation Guides for UNIX/Linux Operating SystemsSTIG References, Finding IDs and Rule IDs
- Updated
Remediation - Ansible
- name: Gather the package facts
package_facts:
manager: auto
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105 - NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - 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-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Remediation where authselect tool is present
block:
- name: Lock Accounts Must Persist - Check integrity of authselect current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - Ensure "with-faillock" feature is enabled using
authselect tool
ansible.builtin.command:
cmd: authselect enable-feature with-faillock
register: result_authselect_enable_feature_cmd
when:
- result_authselect_check_cmd is success
- result_authselect_features.stdout is not search("with-faillock")
- name: Lock Accounts Must Persist - 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
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Remediation where authselect tool is not present
block:
- name: Lock Accounts Must Persist - Check if pam_faillock.so is already enabled
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
regexp: .*auth.*pam_faillock\.so (preauth|authfail)
state: absent
check_mode: true
changed_when: false
register: result_pam_faillock_is_enabled
- name: Lock Accounts Must Persist - Enable pam_faillock.so preauth editing PAM
files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so preauth
insertbefore: ^auth.*sufficient.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Lock Accounts Must Persist - Enable pam_faillock.so authfail editing PAM
files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: auth required pam_faillock.so authfail
insertbefore: ^auth.*required.*pam_deny\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
- name: Lock Accounts Must Persist - Enable pam_faillock.so account section editing
PAM files
ansible.builtin.lineinfile:
path: '{{ item }}'
line: account required pam_faillock.so
insertbefore: ^account.*required.*pam_unix\.so.*
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_is_enabled.found == 0
when:
- '"pam" in ansible_facts.packages'
- not result_authselect_present.stat.exists
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: XCCDF Value var_accounts_passwords_pam_faillock_dir # promote to variable
set_fact:
var_accounts_passwords_pam_faillock_dir: !!str <xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_accounts_passwords_pam_faillock_dir" use="legacy"/>
tags:
- always
- name: Lock Accounts Must Persist - Check the presence of /etc/security/faillock.conf
file
ansible.builtin.stat:
path: /etc/security/faillock.conf
register: result_faillock_conf_check
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure the pam_faillock.so dir parameter in /etc/security/faillock.conf
ansible.builtin.lineinfile:
path: /etc/security/faillock.conf
regexp: ^\s*dir\s*=
line: dir = {{ var_accounts_passwords_pam_faillock_dir }}
state: present
when:
- '"pam" in ansible_facts.packages'
- result_faillock_conf_check.stat.exists
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure the pam_faillock.so dir parameter not
in PAM files
block:
- name: Lock Accounts Must Persist - Check if /etc/pam.d/system-auth file is present
ansible.builtin.stat:
path: /etc/pam.d/system-auth
register: result_pam_file_present
- name: Lock Accounts Must Persist - Check the proper remediation for the system
block:
- name: Lock Accounts Must Persist - Define the PAM file to be edited as a local
fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/system-auth
- name: Lock Accounts Must Persist - Check if system relies on authselect tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Lock Accounts Must Persist - Ensure authselect custom profile is used
if authselect is present
block:
- name: Lock Accounts Must Persist - Check integrity of authselect current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - Ensure the "dir" option from "pam_faillock.so"
is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*auth.*pam_faillock.so.*)\bdir\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Lock Accounts Must Persist - 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
- name: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - Check the proper remediation for the system
block:
- name: Lock Accounts Must Persist - Define the PAM file to be edited as a local
fact
ansible.builtin.set_fact:
pam_file_path: /etc/pam.d/password-auth
- name: Lock Accounts Must Persist - Check if system relies on authselect tool
ansible.builtin.stat:
path: /usr/bin/authselect
register: result_authselect_present
- name: Lock Accounts Must Persist - Ensure authselect custom profile is used
if authselect is present
block:
- name: Lock Accounts Must Persist - Check integrity of authselect current profile
ansible.builtin.command:
cmd: authselect check
register: result_authselect_check_cmd
changed_when: false
failed_when: false
- name: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - 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: Lock Accounts Must Persist - Ensure the "dir" option from "pam_faillock.so"
is not present in {{ pam_file_path }}
ansible.builtin.replace:
dest: '{{ pam_file_path }}'
regexp: (.*auth.*pam_faillock.so.*)\bdir\b=?[0-9a-zA-Z]*(.*)
replace: \1\2
register: result_pam_option_removal
- name: Lock Accounts Must Persist - 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_faillock_conf_check.stat.exists
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure the pam_faillock.so dir parameter in PAM
files
block:
- name: Lock Accounts Must Persist - Check if pam_faillock.so dir parameter is already
enabled in pam files
ansible.builtin.lineinfile:
path: /etc/pam.d/system-auth
regexp: .*auth.*pam_faillock\.so (preauth|authfail).*dir
state: absent
check_mode: true
changed_when: false
register: result_pam_faillock_dir_parameter_is_present
- name: Lock Accounts Must Persist - Ensure the inclusion of pam_faillock.so preauth
dir parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
line: \1required\3 dir={{ var_accounts_passwords_pam_faillock_dir }}
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_dir_parameter_is_present.found == 0
- name: Lock Accounts Must Persist - Ensure the inclusion of pam_faillock.so authfail
dir parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)
line: \1required\3 dir={{ var_accounts_passwords_pam_faillock_dir }}
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_dir_parameter_is_present.found == 0
- name: Lock Accounts Must Persist - Ensure the desired value for pam_faillock.so
preauth dir parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)(dir)=[0-9]+(.*)
line: \1required\3\4={{ var_accounts_passwords_pam_faillock_dir }}\5
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_dir_parameter_is_present.found > 0
- name: Lock Accounts Must Persist - Ensure the desired value for pam_faillock.so
authfail dir parameter in auth section
ansible.builtin.lineinfile:
path: '{{ item }}'
backrefs: true
regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(dir)=[0-9]+(.*)
line: \1required\3\4={{ var_accounts_passwords_pam_faillock_dir }}\5
state: present
loop:
- /etc/pam.d/system-auth
- /etc/pam.d/password-auth
when:
- result_pam_faillock_dir_parameter_is_present.found > 0
when:
- '"pam" in ansible_facts.packages'
- not result_faillock_conf_check.stat.exists
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure necessary SELinux packages are installed
ansible.builtin.package:
name: '{{ item }}'
state: present
with_items:
- python3-libselinux
- python3-policycoreutils
- policycoreutils-python-utils
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Create the tally directory if it does not exist
ansible.builtin.file:
path: '{{ var_accounts_passwords_pam_faillock_dir }}'
state: directory
setype: faillog_t
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure SELinux file context is permanently set
ansible.builtin.command:
cmd: semanage fcontext -a -t faillog_t "{{ var_accounts_passwords_pam_faillock_dir
}}(/.*)?"
register: result_accounts_passwords_pam_faillock_dir_semanage
ignore_errors: true
changed_when:
- result_accounts_passwords_pam_faillock_dir_semanage.rc == 0
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
- name: Lock Accounts Must Persist - Ensure SELinux file context is applied
ansible.builtin.command:
cmd: restorecon -R "{{ var_accounts_passwords_pam_faillock_dir }}"
register: result_accounts_passwords_pam_faillock_dir_restorecon
when: '"pam" in ansible_facts.packages'
tags:
- CCE-86068-4
- DISA-STIG-RHEL-09-411105
- NIST-800-53-AC-7(a)
- NIST-800-53-AC-7(b)
- NIST-800-53-AC-7.1(ii)
- accounts_passwords_pam_faillock_dir
- configure_strategy
- low_complexity
- low_disruption
- medium_severity
- no_reboot_needed
Remediation - Shell Script
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then
var_accounts_passwords_pam_faillock_dir='<xccdf-1.2:sub xmlns:xccdf-1.2="http://checklists.nist.gov/xccdf/1.2" idref="xccdf_org.ssgproject.content_value_var_accounts_passwords_pam_faillock_dir" use="legacy"/>'
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
authselect enable-feature with-faillock
authselect apply-changes -b
else
AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth required pam_faillock.so preauth silent' "$pam_file"
sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth required pam_faillock.so authfail' "$pam_file"
sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account required pam_faillock.so' "$pam_file"
fi
sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required \3/g' "$pam_file"
done
fi
AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f $FAILLOCK_CONF ]; then
regex="^\s*dir\s*="
line="dir = $var_accounts_passwords_pam_faillock_dir"
if ! grep -q $regex $FAILLOCK_CONF; then
echo $line >> $FAILLOCK_CONF
else
sed -i --follow-symlinks 's|^\s*\(dir\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_dir"'|g' $FAILLOCK_CONF
fi
for pam_file in "${AUTH_FILES[@]}"
do
if [ -e "$pam_file" ] ; then
PAM_FILE_PATH="$pam_file"
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 "$pam_file")
PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"
authselect apply-changes -b
fi
if grep -qP '^\s*auth\s.*\bpam_faillock.so\s.*\bdir\b' "$PAM_FILE_PATH"; then
sed -i -E --follow-symlinks 's/(.*auth.*pam_faillock.so.*)\bdir\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
fi
if [ -f /usr/bin/authselect ]; then
authselect apply-changes -b
fi
else
echo "$pam_file was not found" >&2
fi
done
else
for pam_file in "${AUTH_FILES[@]}"
do
if ! grep -qE '^\s*auth.*pam_faillock\.so (preauth|authfail).*dir' "$pam_file"; then
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ dir='"$var_accounts_passwords_pam_faillock_dir"'/' "$pam_file"
sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ dir='"$var_accounts_passwords_pam_faillock_dir"'/' "$pam_file"
else
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"dir"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_dir"'\3/' "$pam_file"
sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"dir"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_dir"'\3/' "$pam_file"
fi
done
fi
if ! rpm -q --quiet "python3-libselinux" ; then
dnf install -y "python3-libselinux"
fi
if ! rpm -q --quiet "python3-policycoreutils" ; then
dnf install -y "python3-policycoreutils"
fi
if ! rpm -q --quiet "policycoreutils-python-utils" ; then
dnf install -y "policycoreutils-python-utils"
fi
mkdir -p "$var_accounts_passwords_pam_faillock_dir"
semanage fcontext -a -t faillog_t "$var_accounts_passwords_pam_faillock_dir(/.*)?"
restorecon -R -v "$var_accounts_passwords_pam_faillock_dir"
else
>&2 echo 'Remediation is not applicable, nothing was done'
fi