Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Ansible macros for PAM #9097

Merged
merged 22 commits into from
Jul 8, 2022

Conversation

marcusburghardt
Copy link
Member

@marcusburghardt marcusburghardt commented Jul 5, 2022

Description:

In alignment to the PR #9017 where new bash macros were created and relevant existing macros were refactored for more robustness, for authselect compatibility and scalability, the similar macros were created in Ansible to make sure all PAM related remediation are aligned to the same approach, quality and standards.

All relevant PAM rules had their respective Ansible remediations updated to use these new macros.

Rationale:

PAM rules usually demands complex remediation to make sure the respective PAM module works as expected. With the introduction of authselect, the maintainability of these remediations became even harder. Now, there are brand new macros for Ansible and Bash, created side-by-side from the ground to keep both flavors aligned. They were also designed for prosperity since they are easily scalable and are well decomposed to simplify the maintainability.

Fixes #7421
Fixes #9041
Fixes #8857

It was introduced new Ansible macros capable to deal with almost any
requirement related to PAM files. These macros are aligned to respective
Bash macros introduced by PR#9017 in order to keep consistence between
Bash and Ansible remediation. Using these new macros is pretty straight
forward while the same quality, standard and approach is shared between
all related rules and their respective Bash and Ansible remediations.
accounts_password_pam_pwhistory_remember_password_auth rule had its
Ansible remediation updated to use new Ansible macros for PAM files.
accounts_password_pam_pwhistory_remember_system_auth rule had its
Ansible remediation updated to use new Ansible macros for PAM files.
accounts_password_pam_pwquality_password_auth rule had its
Ansible remediation updated to use new Ansible macros for PAM files.
accounts_password_pam_pwquality_system_auth rule had its
Ansible remediation updated to use new Ansible macros for PAM files.
accounts_password_pam_retry rule had its Ansible remediation updated to
use new Ansible macros for PAM files. This change only affect systems
which are not relying in pwquality.conf file for pwquality.so settings.
accounts_password_pam_unix_remember rule had its Ansible remediation updated
to use new Ansible macros for PAM files.
During the tests it was noticed an inexistent group was referenced in
the replace module on ansible_ensure_pam_module_line macro.
The reference was fixed and also the systaxe simplified.
accounts_password_pam_unix_rounds_password_auth rule had its Ansible
remediation updated to use new Ansible macros for PAM files.
accounts_password_pam_unix_rounds_system_auth rule had its Ansible
remediation updated to use new Ansible macros for PAM files.
display_login_attempts rule had its Ansible remediation updated
to use new Ansible macros for PAM files.
The macro description was updated according to the Ansible remediation,
which is slightly different from the respective Bash remediation due to
technical reasons. In Ansible, the safeguard to modify the control only
for the first match in rare cases was not possible with considerable
complexity. The tests proved this safeguard is not essential for the
mapped cases. Therefore, the description was updated to make clear the
macro behavior and warn about possible atypical cases. This commit also
adjust the Ansible condition for cases where more lines are matched.
It was included a new Ansible macro to remove an option from a PAM
module line, in alignment to the respective Bash macro. While creating
this macro is was noticed an opportunity to avoid duplicated code, so a
new macro was created to share Ansible tasks between
ansible_ensure_pam_module_configuration and
ansible_remove_pam_module_option_configuration
Include Ansible remediation for
set_password_hashing_algorithm_systemauth rule. It was also fixed an
incorrect variable value for the PAM file in rule.yml.
A new Ansible macro has been added, in alignment with the respective Bash
macro, to securely and standardized enable an authselect feature. Once
this macro was created, duplicated code was also removed from existing
ansible_pam_faillock_enable macro.
The Ansible remediation was simplified to use the new Ansible macros
where authselect is present. For this specific rule, it was more
efficient to keep the replace module to remediate systems without
authselect. The replace is safe to remove "nullok" option at once for
all entries in the two relevant files.
@marcusburghardt marcusburghardt added the Ansible Ansible remediation update. label Jul 5, 2022
@marcusburghardt marcusburghardt added this to the 0.1.63 milestone Jul 5, 2022
@marcusburghardt marcusburghardt added refactoring Improvement which, once completed, will enable the project to progress faster. enhancement General enhancements to the project. labels Jul 5, 2022
@github-actions
Copy link

github-actions bot commented Jul 5, 2022

Start a new ephemeral environment with changes proposed in this pull request:

rhel8 (from CTF) Environment (using Fedora as testing environment)
Open in Gitpod

Fedora Testing Environment
Open in Gitpod

Oracle Linux 8 Environment
Open in Gitpod

@github-actions
Copy link

github-actions bot commented Jul 5, 2022

This datastream diff is auto generated by the check Compare DS/Generate Diff.
Due to the excessive size of the diff, it has been trimmed to fit the 65535-character limit.

Click here to see the trimmed diff
ansible remediation for rule 'xccdf_org.ssgproject.content_rule_display_login_attempts' differs:
--- old datastream
+++ new datastream
@@ -15,15 +15,11 @@
 - low_severity
 - no_reboot_needed
 
-- name: Check for expected pam_lastlog.so entry
- ansible.builtin.lineinfile:
+- name: Ensure PAM Displays Last Logon/Access Notification - Check if /etc/pam.d/postlogin
+ file is present
+ ansible.builtin.stat:
 path: /etc/pam.d/postlogin
- create: false
- regexp: ^\s*session.*required.*pam_lastlog.so\s\+showfailed\s*$
- state: absent
- check_mode: true
- changed_when: false
- register: result_pam_lastlog_present
+ register: result_pam_file_present
 when: '"pam" in ansible_facts.packages'
 tags:
 - CCE-80788-3
@@ -39,10 +35,262 @@
 - low_severity
 - no_reboot_needed
 
-- name: Check if system relies on authselect
+- name: Ensure PAM Displays Last Logon/Access Notification - Check the proper remediation
+ for the system
+ block:
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Define the PAM file
+ to be edited as a local fact
+ ansible.builtin.set_fact:
+ pam_file_path: /etc/pam.d/postlogin
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Check if system relies
+ on authselect
+ ansible.builtin.stat:
+ path: /usr/bin/authselect
+ register: result_authselect_present
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Remediate using authselect
+ block:
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Check integrity of
+ authselect current profile
+ ansible.builtin.command:
+ cmd: authselect check
+ register: result_authselect_check_cmd
+ changed_when: false
+ ignore_errors: true
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Informative message
+ based on the authselect integrity check result
+ ansible.builtin.assert:
+ that:
+ - result_authselect_check_cmd is success
+ 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Ensure a backup of
+ current authselect profile before selecting the custom profile
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=before-hardening-custom-profile.backup
+ register: result_authselect_backup
+ 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Ensure the authselect
+ custom profile changes are applied
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ when:
+ - result_authselect_check_cmd is success
+ - result_authselect_profile is not skipped
+ - result_pam_authselect_restore_features is not skipped
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Check if expected PAM
+ module line is present in {{ pam_file_path }}
+ ansible.builtin.lineinfile:
+ path: '{{ pam_file_path }}'
+ regexp: ^\s*session\s+required\s+pam_lastlog.so\s*.*
+ state: absent
+ check_mode: true
+ changed_when: false
+ register: result_pam_line_present
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Include or update the
+ PAM module line in {{ pam_file_path }}
+ block:
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Check if required
+ PAM module line is present in {{ pam_file_path }} with different control
+ ansible.builtin.lineinfile:
+ path: '{{ pam_file_path }}'
+ regexp: ^\s*session\s+.*\s+pam_lastlog.so\s*
+ state: absent
+ check_mode: true
+ changed_when: false
+ register: result_pam_line_other_control_present
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Ensure the correct
+ control for the required PAM module line in {{ pam_file_path }}
+ ansible.builtin.replace:
+ dest: '{{ pam_file_path }}'
+ regexp: ^(\s*session\s+).*(\bpam_lastlog.so.*)
+ replace: \1required \2
+ register: result_pam_module_edit
+ when:
+ - result_pam_line_other_control_present.found == 1
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Ensure the required
+ PAM module line is included in {{ pam_file_path }}
+ ansible.builtin.lineinfile:
+ dest: '{{ pam_file_path }}'
+ insertafter: BOF
+ line: session required pam_lastlog.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: Ensure PAM Displays Last Logon/Access Notification - Ensure the authselect
+ custom profile changes are applied after module line changes
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-pam_lastlog.so.backup
+ when:
+ - (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: Ensure PAM Displays Last Logon/Access Notification - Check if the required
+ PAM module option is present in {{ pam_file_path }}
+ ansible.builtin.lineinfile:
+ path: '{{ pam_file_path }}'
+ regexp: ^\s*session\s+required\s+pam_lastlog.so\s*.*\sshowfailed\b
+ state: absent
+ check_mode: true
+ changed_when: false
+ register: result_pam_module_showfailed_option_present
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Ensure the "showfailed"
+ PAM option for "pam_lastlog.so" is included in {{ pam_file_path }}
+ ansible.builtin.lineinfile:
+ path: '{{ pam_file_path }}'
+ backrefs: true
+ regexp: ^(\s*session\s+required\s+pam_lastlog.so.*)
+ line: \1 showfailed
+ state: present
+ register: result_pam_showfailed_add
+ when:
+ - result_pam_module_showfailed_option_present.found == 0
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Ensure the authselect
+ custom profile changes are applied
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ when:
+ - result_authselect_present.stat.exists
+ - (result_pam_showfailed_add is defined and result_pam_showfailed_add.changed)
+ or (result_pam_showfailed_edit is defined and result_pam_showfailed_edit.changed)
+ when:
+ - '"pam" in ansible_facts.packages'
+ - result_pam_file_present.stat.exists
+ tags:
+ - CCE-80788-3
+ - CJIS-5.5.2
+ - DISA-STIG-RHEL-08-020340
+ - NIST-800-53-AC-9(1)
+ - NIST-800-53-CM-6(a)
+ - PCI-DSS-Req-10.2.4
+ - configure_strategy
+ - display_login_attempts
+ - low_complexity
+ - low_disruption
+ - low_severity
+ - no_reboot_needed
+
+- name: Ensure PAM Displays Last Logon/Access Notification - Check if /etc/pam.d/postlogin
+ file is present
 ansible.builtin.stat:
- path: /usr/bin/authselect
- register: result_authselect_present
+ path: /etc/pam.d/postlogin
+ register: result_pam_file_present
 when: '"pam" in ansible_facts.packages'
 tags:
 - CCE-80788-3
@@ -58,137 +306,172 @@
 - low_severity
 - no_reboot_needed
 
-- name: Remediation where authselect tool is present
+- name: Ensure PAM Displays Last Logon/Access Notification - Check the proper remediation
+ for the system
 block:
 
- - name: Check the integrity of the current authselect profile
+ - name: Ensure PAM Displays Last Logon/Access Notification - Define the PAM file
+ to be edited as a local fact
+ ansible.builtin.set_fact:
+ pam_file_path: /etc/pam.d/postlogin
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Check if system relies
+ on authselect
+ ansible.builtin.stat:
+ path: /usr/bin/authselect
+ register: result_authselect_present
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Remediate using authselect
+ block:
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Check integrity of
+ authselect current profile
+ ansible.builtin.command:
+ cmd: authselect check
+ register: result_authselect_check_cmd
+ changed_when: false
+ ignore_errors: true
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Informative message
+ based on the authselect integrity check result
+ ansible.builtin.assert:
+ that:
+ - result_authselect_check_cmd is success
+ 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Ensure a backup of
+ current authselect profile before selecting the custom profile
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=before-hardening-custom-profile.backup
+ register: result_authselect_backup
+ 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Ensure the authselect
+ custom profile changes are applied
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ when:
+ - result_authselect_check_cmd is success
+ - result_authselect_profile is not skipped
+ - result_pam_authselect_restore_features is not skipped
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - 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: Ensure PAM Displays Last Logon/Access Notification - Ensure the "silent"
+ option from "pam_lastlog.so" is not present in {{ pam_file_path }}
+ ansible.builtin.replace:
+ dest: '{{ pam_file_path }}'
+ regexp: (.*session.*pam_lastlog.so.*)\bsilent\b=?[0-9a-zA-Z]*(.*)
+ replace: \1\2
+ register: result_pam_option_removal
+
+ - name: Ensure PAM Displays Last Logon/Access Notification - Ensure the authselect
+ custom profile changes are applied
 ansible.builtin.command:
- cmd: authselect check
- register: result_authselect_check_cmd
- changed_when: false
- ignore_errors: true
-
- - name: Informative message based on the authselect integrity check result
- ansible.builtin.assert:
- that:
- - result_authselect_check_cmd is success
- fail_msg:
- - authselect integrity check failed. Remediation aborted!
- - This remediation could not be applied because the authselect profile is not
- intact.
- - It is not recommended to manually edit the PAM files when authselect 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: 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: 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: 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: 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: Check if any custom profile with the same name was already created in the
- past
- 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: Create a custom profile based on the current profile
- ansible.builtin.command:
- cmd: authselect create-profile hardening -b sssd
- when:
- - result_authselect_check_cmd is success
- - authselect_current_profile is not match("custom/")
- - not result_authselect_custom_profile_present.stat.exists
-
- - name: Ensure the silent option is not present in the custom profile
- ansible.builtin.replace:
- dest: /etc/authselect/{{ authselect_custom_profile }}/postlogin
- regexp: ^(session.*pam_lastlog.so.*) silent( .*)$
- replace: \g<1>\g<2>
- when:
- - result_authselect_profile is not skipped
-
- - name: Ensure the desired configuration is present in the custom profile
- ansible.builtin.lineinfile:
- dest: /etc/authselect/{{ authselect_custom_profile }}/postlogin
- insertbefore: ^session.*
- firstmatch: true
- line: session required pam_lastlog.so showfailed
- when:
- - result_authselect_profile is not skipped
-
- - name: Ensure a backup of current authselect profile before selecting the custom
- profile
- ansible.builtin.command:
- cmd: authselect apply-changes -b --backup=before-pwhistory-hardening.backup
- register: result_authselect_backup
- 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: Ensure the custom profile is selected
- ansible.builtin.command:
- cmd: authselect select {{ authselect_custom_profile }} --force
- 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: Restore the authselect features in the custom profile
- ansible.builtin.command:
- cmd: authselect enable-feature {{ item }}
- loop: '{{ result_authselect_features.stdout_lines }}'
- when:
- - result_authselect_profile is not skipped
- - result_authselect_features is not skipped
- - result_pam_authselect_select_profile is not skipped
-
- - name: Ensure the custom profile changes are applied
- ansible.builtin.command:
- cmd: authselect apply-changes -b --backup=after-pwhistory-hardening.backup
- when:
- - result_authselect_check_cmd is success
- - result_authselect_profile is not skipped
+ cmd: authselect apply-changes -b --backup=after-pam_lastlog.so-silent-removal.backup
+ when:
+ - result_authselect_present.stat.exists
+ - result_pam_option_removal is changed
 when:
 - '"pam" in ansible_facts.packages'
- - result_pam_lastlog_present.found == 0
- - result_authselect_present.stat.exists
+ - result_pam_file_present.stat.exists
 tags:
 - CCE-80788-3
 - CJIS-5.5.2
@@ -202,37 +485,3 @@
 - low_disruption
 - low_severity
 - no_reboot_needed
-
-- name: Remediation where authselect tool is not present and PAM files are directly
- edited
- block:
-
- - name: Ensure the silent option is not present in PAM files
- ansible.builtin.replace:
- dest: /etc/pam.d/postlogin
- regexp: ^(session.*pam_lastlog.so.*) silent( .*)$
- replace: \g<1>\g<2>
-
- - name: Ensure the desired configuration is present in PAM files
- ansible.builtin.lineinfile:
- dest: /etc/pam.d/postlogin
- insertbefore: ^session.*
- firstmatch: true
- line: session required pam_lastlog.so showfailed
- when:
- - '"pam" in ansible_facts.packages'
- - result_pam_lastlog_present.found == 0
- - not result_authselect_present.stat.exists
- tags:
- - CCE-80788-3
- - CJIS-5.5.2
- - DISA-STIG-RHEL-08-020340
- - NIST-800-53-AC-9(1)
- - NIST-800-53-CM-6(a)
- - PCI-DSS-Req-10.2.4
- - configure_strategy
- - display_login_attempts
- - low_complexity
- - low_disruption
- - low_severity
- - no_reboot_needed

ansible remediation for rule 'xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth' differs:
--- old datastream
+++ new datastream
@@ -26,15 +26,11 @@
 tags:
 - always
 
-- name: Check for existing pam_pwhistory.so entry
- ansible.builtin.lineinfile:
+- 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
- create: false
- regexp: ^password.*pam_pwhistory.so.*
- state: absent
- check_mode: true
- changed_when: false
- register: result_pam_pwhistory_present
+ register: result_pam_file_present
 when: '"pam" in ansible_facts.packages'
 tags:
 - CCE-83478-8
@@ -51,18 +47,252 @@
 - medium_severity
 - no_reboot_needed
 
-- name: Check for existing remember parameter entry
- ansible.builtin.lineinfile:
- path: /etc/pam.d/password-auth
- create: false
- regexp: ^password.*pam_pwhistory.so.*remember=
- state: absent
- check_mode: true
- changed_when: false
- register: result_pam_pwhistory_remember_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'
+ ansible.builtin.stat:
+ path: /usr/bin/authselect
+ register: result_authselect_present
+
+ - name: 'Limit Password Reuse: password-auth - Remediate using authselect'
+ 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
+ ignore_errors: true
+
+ - name: 'Limit Password Reuse: password-auth - Informative message based on the
+ authselect integrity check result'
+ ansible.builtin.assert:
+ that:
+ - result_authselect_check_cmd is success
+ 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 a backup of current authselect
+ profile before selecting the custom profile'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=before-hardening-custom-profile.backup
+ register: result_authselect_backup
+ 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 the authselect custom profile
+ changes are applied'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ 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 }}\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 }} \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 }} 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 the authselect custom profile
+ changes are applied after module line changes'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-pam_pwhistory.so.backup
+ when:
+ - (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+{{ var_password_pam_remember_control_flag }}\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+{{ var_password_pam_remember_control_flag }}\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+{{ var_password_pam_remember_control_flag }}\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 the authselect custom profile
+ changes are applied'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ 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'
- - result_pam_pwhistory_present.found == 1
+ - result_pam_file_present.stat.exists
 tags:
 - CCE-83478-8
 - CJIS-5.6.2.1.1
@@ -77,245 +307,3 @@
 - medium_disruption
 - medium_severity
 - no_reboot_needed
-
-- name: Check if system relies on authselect
- ansible.builtin.stat:
- path: /usr/bin/authselect
- register: result_authselect_present
- when: '"pam" in ansible_facts.packages'
- tags:
- - CCE-83478-8
- - CJIS-5.6.2.1.1
- - DISA-STIG-RHEL-08-020220
- - 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
- - accounts_password_pam_pwhistory_remember_password_auth
- - configure_strategy
- - low_complexity
- - medium_disruption
- - medium_severity
- - no_reboot_needed
-
-- name: Remediation where authselect tool is present
- block:
-
- - name: Check the integrity of the current authselect profile
- ansible.builtin.command:
- cmd: authselect check
- register: result_authselect_check_cmd
- changed_when: false
- ignore_errors: true
-
- - name: Informative message based on the authselect integrity check result
- ansible.builtin.assert:
- that:
- - result_authselect_check_cmd is success
- fail_msg:
- - authselect integrity check failed. Remediation aborted!
- - This remediation could not be applied because the authselect profile is not
- intact.
- - It is not recommended to manually edit the PAM files when authselect 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: 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: 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: 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: 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: Check if any custom profile with the same name was already created in the
- past
- 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: Create a custom profile based on the current profile
- ansible.builtin.command:
- cmd: authselect create-profile hardening -b sssd
- when:
- - result_authselect_check_cmd is success
- - authselect_current_profile is not match("custom/")
- - not result_authselect_custom_profile_present.stat.exists
-
- - name: Ensure the desired remember value is updated in the custom profile
- ansible.builtin.replace:
- dest: /etc/authselect/{{ authselect_custom_profile }}/password-auth
- regexp: (.*pam_pwhistory.so.*remember=)(\S+)(.*)$
- replace: \g<1>{{ var_password_pam_remember }}\g<3>
- when:
- - result_authselect_profile is not skipped
- - result_pam_pwhistory_present.found == 1
- - result_pam_pwhistory_remember_present.found == 1
-
- - name: Ensure the remember parameter is included in the custom profile
- ansible.builtin.replace:
- dest: /etc/authselect/{{ authselect_custom_profile }}/password-auth
- regexp: (.*pam_pwhistory.so.*)(?! remember=\S+)(.*)$
- replace: \g<1> \g<2> remember={{ var_password_pam_remember }}
- when:
- - result_authselect_profile is not skipped
- - result_pam_pwhistory_present.found == 1
- - result_pam_pwhistory_remember_present.found == 0
-
- - name: Ensure the desired control value is updated in the custom profile
- ansible.builtin.replace:
- dest: /etc/authselect/{{ authselect_custom_profile }}/password-auth
- regexp: ^(password\s+)((?:(?:requisite)|(?:required)))(\s+pam_pwhistory\.so\s.*)$
- replace: \g<1>{{ var_password_pam_remember_control_flag }}\g<3>
- when:
- - result_authselect_profile is not skipped
- - result_pam_pwhistory_present.found == 1
-
- - name: Ensure the desired configuration is present in the custom profile
- ansible.builtin.lineinfile:
- dest: /etc/authselect/{{ authselect_custom_profile }}/password-auth
- insertafter: ^password.*requisite.*pam_pwquality.so.*
- line: password {{ var_password_pam_remember_control_flag }} pam_pwhistory.so
- remember={{ var_password_pam_remember }} use_authtok
- when:
- - result_authselect_profile is not skipped
- - result_pam_pwhistory_present.found == 0
-
- - name: Ensure a backup of current authselect profile before selecting the custom
- profile
- ansible.builtin.command:
- cmd: authselect apply-changes -b --backup=before-pwhistory-hardening.backup
- register: result_authselect_backup
- 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: Ensure the custom profile is selected
- ansible.builtin.command:
- cmd: authselect select {{ authselect_custom_profile }} --force
- 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: Restore the authselect features in the custom profile
- ansible.builtin.command:
- cmd: authselect enable-feature {{ item }}
- loop: '{{ result_authselect_features.stdout_lines }}'
- when:
- - result_authselect_profile is not skipped
- - result_authselect_features is not skipped
- - result_pam_authselect_select_profile is not skipped
-
- - name: Ensure the custom profile changes are applied
- ansible.builtin.command:
- cmd: authselect apply-changes -b --backup=after-pwhistory-hardening.backup
- when:
- - result_authselect_check_cmd is success
- - result_authselect_profile is not skipped
- when:
- - '"pam" in ansible_facts.packages'
- - result_authselect_present.stat.exists
- tags:
- - CCE-83478-8
- - CJIS-5.6.2.1.1
- - DISA-STIG-RHEL-08-020220
- - 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
- - accounts_password_pam_pwhistory_remember_password_auth
- - configure_strategy
- - low_complexity
- - medium_disruption
- - medium_severity
- - no_reboot_needed
-
-- name: Remediation where authselect tool is not present and PAM files are directly
- edited
- block:
-
- - name: Ensure the desired remember value is updated in the custom profile
- ansible.builtin.replace:
- dest: /etc/pam.d/password-auth
- regexp: (.*pam_pwhistory.so.*remember=)(\S+)(.*)$
- replace: \g<1>{{ var_password_pam_remember }}\g<3>
- when:
- - result_pam_pwhistory_present.found == 1
-
- - name: Ensure the remember parameter is included in the custom profile
- ansible.builtin.replace:
- dest: /etc/pam.d/password-auth
- regexp: (.*pam_pwhistory.so.*)(?! remember=\S+)(.*)$
- replace: \g<1> \g<2> remember={{ var_password_pam_remember }}
- when:
- - result_pam_pwhistory_present.found == 1
- - result_pam_pwhistory_remember_present.found == 0
-
- - name: Ensure the desired control value is updated in the custom profile
- ansible.builtin.replace:
- dest: /etc/pam.d/password-auth
- regexp: ^(password\s+)((?:(?:requisite)|(?:required)))(\s+pam_pwhistory\.so\s.*)$
- replace: \g<1>{{ var_password_pam_remember_control_flag }}\g<3>
- when:
- - result_pam_pwhistory_present.found == 1
-
- - name: Ensure the desired configuration is present in the custom profile
- ansible.builtin.lineinfile:
- dest: /etc/pam.d/password-auth
- insertafter: ^password.*requisite.*pam_pwquality.so.*
- line: password {{ var_password_pam_remember_control_flag }} pam_pwhistory.so
- remember={{ var_password_pam_remember }} use_authtok
- when:
- - result_pam_pwhistory_present.found == 0
- when:
- - '"pam" in ansible_facts.packages'
- - not result_authselect_present.stat.exists
- tags:
- - CCE-83478-8
- - CJIS-5.6.2.1.1
- - DISA-STIG-RHEL-08-020220
- - 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
- - accounts_password_pam_pwhistory_remember_password_auth
- - configure_strategy
- - low_complexity
- - medium_disruption
- - medium_severity
- - no_reboot_needed

ansible remediation for rule 'xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth' differs:
--- old datastream
+++ new datastream
@@ -26,15 +26,11 @@
 tags:
 - always
 
-- name: Check for existing pam_pwhistory.so entry
- ansible.builtin.lineinfile:
+- name: 'Limit Password Reuse: system-auth - Check if /etc/pam.d/system-auth file
+ is present'
+ ansible.builtin.stat:
 path: /etc/pam.d/system-auth
- create: false
- regexp: ^password.*pam_pwhistory.so.*
- state: absent
- check_mode: true
- changed_when: false
- register: result_pam_pwhistory_present
+ register: result_pam_file_present
 when: '"pam" in ansible_facts.packages'
 tags:
 - CCE-83480-4
@@ -51,18 +47,252 @@
 - medium_severity
 - no_reboot_needed
 
-- name: Check for existing remember parameter entry
- ansible.builtin.lineinfile:
- path: /etc/pam.d/system-auth
- create: false
- regexp: ^password.*pam_pwhistory.so.*remember=
- state: absent
- check_mode: true
- changed_when: false
- register: result_pam_pwhistory_remember_present
+- name: 'Limit Password Reuse: system-auth - Check the proper remediation for the
+ system'
+ block:
+
+ - name: 'Limit Password Reuse: system-auth - Define the PAM file to be edited as
+ a local fact'
+ ansible.builtin.set_fact:
+ pam_file_path: /etc/pam.d/system-auth
+
+ - name: 'Limit Password Reuse: system-auth - Check if system relies on authselect'
+ ansible.builtin.stat:
+ path: /usr/bin/authselect
+ register: result_authselect_present
+
+ - name: 'Limit Password Reuse: system-auth - Remediate using authselect'
+ block:
+
+ - name: 'Limit Password Reuse: system-auth - Check integrity of authselect current
+ profile'
+ ansible.builtin.command:
+ cmd: authselect check
+ register: result_authselect_check_cmd
+ changed_when: false
+ ignore_errors: true
+
+ - name: 'Limit Password Reuse: system-auth - Informative message based on the
+ authselect integrity check result'
+ ansible.builtin.assert:
+ that:
+ - result_authselect_check_cmd is success
+ 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: system-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: system-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: system-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: system-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: system-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: system-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: system-auth - Ensure a backup of current authselect
+ profile before selecting the custom profile'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=before-hardening-custom-profile.backup
+ register: result_authselect_backup
+ 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: system-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: system-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: system-auth - Ensure the authselect custom profile
+ changes are applied'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ 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: system-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: system-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 }}\s+pam_pwhistory.so\s*.*
+ state: absent
+ check_mode: true
+ changed_when: false
+ register: result_pam_line_present
+
+ - name: 'Limit Password Reuse: system-auth - Include or update the PAM module line
+ in {{ pam_file_path }}'
+ block:
+
+ - name: 'Limit Password Reuse: system-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: system-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 }} \2
+ register: result_pam_module_edit
+ when:
+ - result_pam_line_other_control_present.found == 1
+
+ - name: 'Limit Password Reuse: system-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 }} 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: system-auth - Ensure the authselect custom profile
+ changes are applied after module line changes'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-pam_pwhistory.so.backup
+ when:
+ - (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: system-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+{{ var_password_pam_remember_control_flag }}\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: system-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+{{ var_password_pam_remember_control_flag }}\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: system-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+{{ var_password_pam_remember_control_flag }}\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: system-auth - Ensure the authselect custom profile
+ changes are applied'
+ ansible.builtin.command:
+ cmd: authselect apply-changes -b --backup=after-hardening-custom-profile.backup
+ 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'
- - result_pam_pwhistory_present.found == 1
+ - result_pam_file_present.stat.exists
 tags:
 - CCE-83480-4
 - CJIS-5.6.2.1.1
@@ -77,245 +307,3 @@
 - medium_disruption
 - medium_severity
 - no_reboot_needed
-
-- name: Check if system relies on authselect
- ansible.builtin.stat:
- path: /usr/bin/authselect
- register: result_authselect_present
- when: '"pam" in ansible_facts.packages'
- tags:
- - CCE-83480-4
- - CJIS-5.6.2.1.1
- - DISA-STIG-RHEL-08-020221
- - 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
- - accounts_password_pam_pwhistory_remember_system_auth
- - configure_strategy
- - low_complexity
- - medium_disruption
- - medium_severity
- - no_reboot_needed
-
-- name: Remediation where authselect tool is present
- block:
-
- - name: Check the integrity of the current authselect profile
- ansible.builtin.command:
- cmd: authselect check
- register: result_authselect_check_cmd
- changed_when: false
- ignore_errors: true
-
- - name: Informative message based on the authselect integrity check result
- ansible.builtin.assert:
- that:
- - result_authselect_check_cmd is success
- fail_msg:
- - authselect integrity check failed. Remediation aborted!
- - This remediation could not be applied because the authselect profile is not
- intact.
- - It is not recommended to manually edit the PAM files when authselect 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: 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: 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: 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: Get authselect current features to also enable them in the custom prof

... The diff is trimmed here ...

@marcusburghardt
Copy link
Member Author

I am checking the error from Automatus CS8 / Run Tests.

@yuumasato yuumasato self-assigned this Jul 6, 2022
@marcusburghardt
Copy link
Member Author

/retest

shared/macros/10-ansible.jinja Show resolved Hide resolved
shared/macros/10-ansible.jinja Outdated Show resolved Hide resolved
shared/macros/10-ansible.jinja Show resolved Hide resolved
- result_pam_module_{{{ option }}}_option_present.found == 0

{{%- if value != '' %}}
- name: '{{{ rule_title }}} - Ensure the required value for "{{{ option }}}" PAM option from "{{{ module }}}" in {{{ pam_file }}}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this task idempotent? It seems to me that it will always replace the option's value.
The Bash macro will always replace the option's value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. I executed some consecutive times and the task only "changes" when the value is different. However, during these tests I saw another problem where a breaking line was removed. So, I will replace the replace module by the lineinfile, which better manage this case.

Copy link
Member

@yuumasato yuumasato Jul 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something weird going on and this task is being executed on the second run.
At the same time, no changes are actually made.

The following command needs to be run 3 times before the changes converge.
ansible-playbook -i "rhel8," -v --tags accounts_password_pam_pwhistory_remember_system_auth build/ansible/rhel8-playbook-stig.yml

 TASK [Limit Password Reuse: password-auth - Check if the required PAM module option is present in /etc/authselect/custom/hardening/password-auth] ***
 Thursday 07 July 2022  15:27:43 +0200 (0:00:00.060)       0:02:12.494 *********
 ok: [rhel87n]
 
 TASK [Limit Password Reuse: password-auth - Ensure the "remember" PAM option for "pam_pwhistory.so" is included in /etc/authselect/custom/hardening/password-auth] ***
 Thursday 07 July 2022  15:27:43 +0200 (0:00:00.548)       0:02:13.042 *********
 skipping: [rhel87n]
 
 TASK [Limit Password Reuse: password-auth - Ensure the required value for "remember" PAM option from "pam_pwhistory.so" in /etc/authselect/custom/hardening/password-auth] ***
 Thursday 07 July 2022  15:27:43 +0200 (0:00:00.039)       0:02:13.082 *********
 changed: [rhel87n]
 
 TASK [Limit Password Reuse: password-auth - Ensure the authselect custom profile changes are applied] ***
 Thursday 07 July 2022  15:27:44 +0200 (0:00:00.494)       0:02:13.576 *********
 changed: [rhel87n]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yuumasato , it is indeed weird. I analysed relatively deep this situation and also didn't find any pattern. However, I concluded the remediation is working as expected already in the first round, so I consider this weird situation as safe to be waived. I don't discard a bug outside the scope of this PR. I will file an issue to make sure this situation is on our radar for further analysis.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the task brings the system to compliant state in the first run.
Please file an issue so we can keep track of this behaviour.

shared/macros/10-ansible.jinja Show resolved Hide resolved
shared/macros/10-ansible.jinja Outdated Show resolved Hide resolved
The regex used in after_match parameter to match the beginning of the
file is diffent between Bash and Ansible. Adjusted the Ansible one with
the proper value.
During the review and CI tests some issues were detected whe fixed in
this commit:
- Ensured the authselect changes are applied after enabling a feature.
- Used the correct Ansible variable to identify the custom profile.
- Used the lineinfile module to more safely ensure the parameter value
  since it was noticed some cases where the replace module could merge
  two lines based on the regex and processed content.
- Extended the conditionals for ansible_ensure_pam_module_line macro,
  making it more conservative by avoiding the control change when more
  than one line matches.
Copy link
Member

@yuumasato yuumasato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good, I have one comment about idempotency.

shared/macros/10-ansible.jinja Show resolved Hide resolved
Conditionals were included to ensure the authselect apply-changes
command only executes when a changed actually happened. Otherwise, the
task can be skipped. Also removed the apply-changes command from the
ansible_ensure_pam_facts_and_authselect_profile macro since this macro
doesn't change PAM content and the backups are ensured in other relevant
macros.
@codeclimate
Copy link

codeclimate bot commented Jul 7, 2022

Code Climate has analyzed commit e8d01dd and detected 0 issues on this pull request.

The test coverage on the diff in this pull request is 100.0% (50% is the threshold).

This pull request will bring the total coverage in the repository to 42.7% (0.0% change).

View more on Code Climate.

@marcusburghardt
Copy link
Member Author

/retest

@openshift-ci
Copy link

openshift-ci bot commented Jul 7, 2022

@marcusburghardt: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-aws-ocp4-moderate e8d01dd link true /test e2e-aws-ocp4-moderate
ci/prow/e2e-aws-ocp4-stig e8d01dd link true /test e2e-aws-ocp4-stig
ci/prow/e2e-aws-rhcos4-high e8d01dd link true /test e2e-aws-rhcos4-high
ci/prow/e2e-aws-rhcos4-moderate e8d01dd link true /test e2e-aws-rhcos4-moderate
ci/prow/e2e-aws-ocp4-cis-node e8d01dd link true /test e2e-aws-ocp4-cis-node

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

Copy link
Member

@yuumasato yuumasato left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcusburghardt Nice work on the macros, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ansible Ansible remediation update. enhancement General enhancements to the project. refactoring Improvement which, once completed, will enable the project to progress faster.
Projects
None yet
2 participants