Guide to the Secure Configuration of Red Hat Enterprise Linux 10

with profile PCI-DSS v4.0 Control Baseline for Red Hat Enterprise Linux 10
Payment Card Industry - Data Security Standard (PCI-DSS) is a set of security standards designed to ensure the secure handling of payment card data, with the goal of preventing data breaches and protecting sensitive financial information. This profile ensures Red Hat Enterprise Linux 10 is configured in alignment with PCI-DSS v4.0 requirements.
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 10. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the scap-security-guide package which is developed at https://www.open-scap.org/security-policies/scap-security-guide.

Providing system administrators with such guidance informs them how to securely configure systems under their control in a variety of network roles. Policy makers and baseline creators can use this catalog of settings, with its associated references to higher-level security control catalogs, in order to assist them in security baseline creation. This guide is a catalog, not a checklist, and satisfaction of every item is not likely to be possible or sensible in many operational scenarios. However, the XCCDF format enables granular selection and adjustment of settings, and their association with OVAL and OCIL content provides an automated checking capability. Transformations of this document, and its associated automated checking content, are capable of providing baselines that meet a diverse set of policy objectives. Some example XCCDF Profiles, which are selections of items that form checklists and can be used as baselines, are available with this guide. They can be processed, in an automated fashion, with tools that support the Security Content Automation Protocol (SCAP). The DISA STIG, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.
Do not attempt to implement any of the settings in this guide without first testing them in a non-operational environment. The creators of this guidance assume no responsibility whatsoever for its use by other parties, and makes no guarantees, expressed or implied, about its quality, reliability, or any other characteristic.

Profile Information

Profile TitlePCI-DSS v4.0 Control Baseline for Red Hat Enterprise Linux 10
Profile IDxccdf_org.ssgproject.content_profile_pci-dss

CPE Platforms

  • cpe:/o:redhat:enterprise_linux:10

Revision History

Current version: 0.1.74

  • draft (as of 2024-06-20)

Table of Contents

  1. System Settings
    1. Installing and Maintaining Software
    2. Account and Access Control
    3. GRUB2 bootloader configuration
    4. Configure Syslog
    5. Network Configuration and Firewalls
    6. File Permissions and Masks
    7. SELinux
  2. Services
    1. Avahi Server
    2. Cron and At Daemons
    3. DHCP
    4. FTP Server
    5. Mail Server Software
    6. NFS and RPC
    7. Network Time Protocol
    8. Obsolete Services
    9. SNMP Server
    10. SSH Server
  3. System Accounting with auditd
    1. Configure auditd Rules for Comprehensive Auditing
    2. Configure auditd Data Retention

Checklist

Group   Guide to the Secure Configuration of Red Hat Enterprise Linux 10   Group contains 84 groups and 249 rules
Group   System Settings   Group contains 55 groups and 138 rules
[ref]   Contains rules that check correct system settings.
Group   Installing and Maintaining Software   Group contains 12 groups and 27 rules
[ref]   The following sections contain information on security-relevant choices during the initial operating system installation process and the setup of software updates.
Group   System and Software Integrity   Group contains 4 groups and 7 rules
[ref]   System and software integrity can be gained by installing antivirus, increasing system encryption strength with FIPS, verifying installed software, enabling SELinux, installing an Intrusion Prevention System, etc. However, installing or enabling integrity checking tools cannot prevent intrusions, but they can detect that an intrusion may have occurred. Requirements for integrity checking may be highly dependent on the environment in which the system will be used. Snapshot-based approaches such as AIDE may induce considerable overhead in the presence of frequent software updates.
Group   Software Integrity Checking   Group contains 2 groups and 5 rules
[ref]   Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes.

The RPM package management system can conduct integrity checks by comparing information in its metadata database with files installed on the system.
Group   Verify Integrity with RPM   Group contains 2 rules
[ref]   The RPM package management system includes the ability to verify the integrity of installed packages by comparing the installed files with information about the files taken from the package metadata stored in the RPM database. Although an attacker could corrupt the RPM database (analogous to attacking the AIDE database as described above), this check can still reveal modification of important files. To list which files on the system differ from what is expected by the RPM database:
$ rpm -qVa
See the man page for rpm to see a complete explanation of each column.

Rule   Verify File Hashes with RPM   [ref]

Without cryptographic integrity protections, system executables and files can be altered by unauthorized users without detection. The RPM package management system can check the hashes of installed software packages, including many that are important to system security. To verify that the cryptographic hash of system files and commands matches vendor values, run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:
$ rpm -Va --noconfig | grep '^..5'
If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:
$ rpm -qf FILENAME
          
The package can be reinstalled from a dnf repository using the command:
$ sudo dnf reinstall PACKAGENAME
          
Alternatively, the package can be reinstalled from trusted media using the command:
$ sudo rpm -Uvh PACKAGENAME
          
Warning:  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of packages present on the system. It is not a problem in most cases, but especially systems with a large number of installed packages can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_rpm_verify_hashes
References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.3.8, 3.4.1
disaCCI-000366, CCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
pcidssReq-11.5
os-srgSRG-OS-000480-GPOS-00227
pcidss411.5.2

Complexity:high
Disruption:medium
Reboot:false
Strategy:restrict
- name: 'Set fact: Package manager reinstall command'
  set_fact:
    package_manager_reinstall_cmd: dnf reinstall -y
  when: ansible_distribution in [ "Fedora", "RedHat", "CentOS", "OracleLinux" ]
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_hashes

- name: 'Set fact: Package manager reinstall command (zypper)'
  set_fact:
    package_manager_reinstall_cmd: zypper in -f -y
  when: ansible_distribution == "SLES"
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_hashes

- name: Read files with incorrect hash
  command: rpm -Va --nodeps --nosize --nomtime --nordev --nocaps --nolinkto --nouser
    --nogroup --nomode --noghost --noconfig
  register: files_with_incorrect_hash
  changed_when: false
  failed_when: files_with_incorrect_hash.rc > 1
  check_mode: false
  when: (package_manager_reinstall_cmd is defined)
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_hashes

- name: Create list of packages
  command: rpm -qf "{{ item }}"
  with_items: '{{ files_with_incorrect_hash.stdout_lines | map(''regex_findall'',
    ''^[.]+[5]+.* (\/.*)'', ''\1'') | map(''join'') | select(''match'', ''(\/.*)'')
    | list | unique }}'
  register: list_of_packages
  changed_when: false
  check_mode: false
  when:
  - files_with_incorrect_hash.stdout_lines is defined
  - (files_with_incorrect_hash.stdout_lines | length > 0)
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_hashes

- name: Reinstall packages of files with incorrect hash
  command: '{{ package_manager_reinstall_cmd }} ''{{ item }}'''
  with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
    | unique }}'
  when:
  - files_with_incorrect_hash.stdout_lines is defined
  - (package_manager_reinstall_cmd is defined and (files_with_incorrect_hash.stdout_lines
    | length > 0))
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_hashes


# Find which files have incorrect hash (not in /etc, because of the system related config files) and then get files names
files_with_incorrect_hash="$(rpm -Va --noconfig | grep -E '^..5' | awk '{print $NF}' )"

if [ -n "$files_with_incorrect_hash" ]; then
    # From files names get package names and change newline to space, because rpm writes each package to new line
    packages_to_reinstall="$(rpm -qf $files_with_incorrect_hash | tr '\n' ' ')"

    
    dnf reinstall -y $packages_to_reinstall
    
fi

Rule   Verify and Correct Ownership with RPM   [ref]

The RPM package management system can check file ownership permissions of installed software packages, including many that are important to system security. After locating a file with incorrect permissions, which can be found with:
rpm -Va | awk '{ if (substr($0,6,1)=="U" || substr($0,7,1)=="G") print $NF }'
run the following command to determine which package owns it:
$ rpm -qf FILENAME
          
Next, run the following command to reset its permissions to the correct values:
$ sudo rpm --setugids PACKAGENAME
          
Warning:  Profiles may require that specific files be owned by root while the default owner defined by the vendor is different. Such files will be reported as a finding and need to be evaluated according to your policy and deployment environment.
Warning:  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of packages present on the system. It is not a problem in most cases, but especially systems with a large number of installed packages can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Ownership of binaries and configuration files that is incorrect could allow an unauthorized user to gain privileges that they should not have. The ownership set by the vendor should be maintained. Any deviations from this baseline should be investigated.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_rpm_verify_ownership
References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 5, 6, 9
cjis5.10.4.1
cobit5APO01.06, APO11.04, BAI03.05, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.07, DSS06.02, MEA02.01
cui3.3.8, 3.4.1
disaCCI-001494, CCI-001496
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.5.1, A.12.6.2, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2
nistCM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3)
nist-csfPR.AC-4, PR.DS-5, PR.IP-1, PR.PT-1
pcidssReq-11.5
os-srgSRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098, SRG-OS-000278-GPOS-00108
pcidss411.5.2

Complexity:high
Disruption:medium
Reboot:false
Strategy:restrict
- name: Read list of files with incorrect ownership
  command: rpm -Va --nodeps --nosignature --nofiledigest --nosize --nomtime --nordev
    --nocaps --nolinkto --nomode
  register: files_with_incorrect_ownership
  failed_when: files_with_incorrect_ownership.rc > 1
  changed_when: false
  check_mode: false
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_ownership

- name: Create list of packages
  command: rpm -qf "{{ item }}"
  with_items: '{{ files_with_incorrect_ownership.stdout_lines | map(''regex_findall'',
    ''^[.]+[U|G]+.* (\/.*)'', ''\1'') | map(''join'') | select(''match'', ''(\/.*)'')
    | list | unique }}'
  register: list_of_packages
  changed_when: false
  check_mode: false
  when: (files_with_incorrect_ownership.stdout_lines | length > 0)
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_ownership

- name: Correct file ownership with RPM
  command: rpm --setugids '{{ item }}'
  with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
    | unique }}'
  when: (files_with_incorrect_ownership.stdout_lines | length > 0)
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.3.8
  - NIST-800-171-3.4.1
  - NIST-800-53-AU-9(3)
  - NIST-800-53-CM-6(c)
  - NIST-800-53-CM-6(d)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - NIST-800-53-SI-7(6)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_ownership

Complexity:high
Disruption:medium
Reboot:false
Strategy:restrict

# Declare array to hold set of RPM packages we need to correct permissions for
declare -A SETPERMS_RPM_DICT

# Create a list of files on the system having permissions different from what
# is expected by the RPM database
readarray -t FILES_WITH_INCORRECT_PERMS < <(rpm -Va --nofiledigest | awk '{ if (substr($0,6,1)=="U" || substr($0,7,1)=="G") print $NF }')

for FILE_PATH in "${FILES_WITH_INCORRECT_PERMS[@]}"
do
        RPM_PACKAGE=$(rpm -qf "$FILE_PATH")
	# Use an associative array to store packages as it's keys, not having to care about duplicates.
	SETPERMS_RPM_DICT["$RPM_PACKAGE"]=1
done

# For each of the RPM packages left in the list -- reset its permissions to the
# correct values
for RPM_PACKAGE in "${!SETPERMS_RPM_DICT[@]}"
do
        rpm --setugids "${RPM_PACKAGE}"
done
Group   Verify Integrity with AIDE   Group contains 3 rules
[ref]   AIDE conducts integrity checks by comparing information about files with previously-gathered information. Ideally, the AIDE database is created immediately after initial system configuration, and then again after any software update. AIDE is highly configurable, with further configuration information located in /usr/share/doc/aide-VERSION .

Rule   Install AIDE   [ref]

The aide package can be installed with the following command:
$ sudo dnf install aide
Rationale:
The AIDE package must be installed if it is to be available for integrity checking.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_aide_installed
References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-002696, CCI-002699, CCI-001744
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
ism1034, 1288, 1341, 1417
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
anssiR76, R79
cis6.1.1
pcidss411.5.2


[[packages]]
name = "aide"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Ensure aide is installed
  package:
    name: aide
    state: present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_aide_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=aide

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_aide

class install_aide {
  package { 'aide':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

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

Rule   Build and Test AIDE Database   [ref]

Run the following command to generate a new database:
$ sudo /usr/sbin/aide --init
By default, the database will be written to the file /var/lib/aide/aide.db.new.gz. Storing the database, the configuration file /etc/aide.conf, and the binary /usr/sbin/aide (or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity. The newly-generated database can be installed as follows:
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
To initiate a manual check, run the following command:
$ sudo /usr/sbin/aide --check
If this check produces any unexpected output, investigate.
Rationale:
For AIDE to be effective, an initial database of "known-good" information about files must be captured and it should be able to be verified against the installed files.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_aide_build_database
References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
anssiR76, R79
cis6.1.1
pcidss411.5.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Build and Test AIDE Database - Ensure AIDE Is Installed
  ansible.builtin.package:
    name: '{{ item }}'
    state: present
  with_items:
  - aide
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Build and Test AIDE Database
  ansible.builtin.command: /usr/sbin/aide --init
  changed_when: true
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Check Whether the Stock AIDE Database Exists
  ansible.builtin.stat:
    path: /var/lib/aide/aide.db.new.gz
  register: aide_database_stat
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Stage AIDE Database
  ansible.builtin.copy:
    src: /var/lib/aide/aide.db.new.gz
    dest: /var/lib/aide/aide.db.gz
    backup: true
    remote_src: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - (aide_database_stat.stat.exists is defined and aide_database_stat.stat.exists)
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

/usr/sbin/aide --init
/bin/cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

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

Rule   Configure Periodic Execution of AIDE   [ref]

At a minimum, AIDE should be configured to run a weekly scan. To implement a daily execution of AIDE at 4:05am using cron, add the following line to /etc/crontab:
05 4 * * * root /usr/sbin/aide --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to /etc/crontab:
05 4 * * 0 root /usr/sbin/aide --check
AIDE can be executed periodically through other means; this is merely one example. The usage of cron's special time codes, such as @daily and @weekly is acceptable.
Rationale:
By default, AIDE does not install itself for periodic execution. Periodically running AIDE is necessary to reveal unexpected changes in installed files.

Unauthorized changes to the baseline configuration could make the system vulnerable to various attacks or allow unauthorized access to the operating system. Changes to operating system configurations can have unintended side effects, some of which may be relevant to security.

Detecting such changes and providing an automated response can help avoid unintended, negative consequences that could ultimately affect the security state of the operating system. The operating system's Information Management Officer (IMO)/Information System Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or monitoring system trap when there is an unauthorized modification of a configuration item.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_aide_periodic_cron_checking
References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-001744, CCI-002699, CCI-002702
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistSI-7, SI-7(1), CM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201
anssiR76
cis6.1.2
pcidss411.5.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Ensure AIDE is installed
  package:
    name: '{{ item }}'
    state: present
  with_items:
  - aide
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set cron package name - RedHat
  set_fact:
    cron_pkg_name: cronie
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_os_family == "RedHat" or ansible_os_family == "Suse"
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set cron package name - Debian
  set_fact:
    cron_pkg_name: cron
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_os_family == "Debian"
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Install cron
  package:
    name: '{{ cron_pkg_name }}'
    state: present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure Periodic Execution of AIDE
  cron:
    name: run AIDE check
    minute: 5
    hour: 4
    weekday: 0
    user: root
    job: /usr/sbin/aide --check
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.10.1.3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-7
  - NIST-800-53-SI-7(1)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_cron_checking
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

if ! rpm -q --quiet "aide" ; then
    dnf install -y "aide"
fi

if ! grep -q "/usr/sbin/aide --check" /etc/crontab ; then
    echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
else
    sed -i '\!^.* --check.*$!d' /etc/crontab
    echo "05 4 * * * root /usr/sbin/aide --check" >> /etc/crontab
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   System Cryptographic Policies   Group contains 2 rules
[ref]   Linux has the capability to centrally configure cryptographic polices. The command update-crypto-policies is used to set the policy applicable for the various cryptographic back-ends, such as SSL/TLS libraries. The configured cryptographic policies will be the default policy used by these backends unless the application user configures them otherwise. When the system has been configured to use the centralized cryptographic policies, the administrator is assured that any application that utilizes the supported backends will follow a policy that adheres to the configured profile. Currently the supported backends are:
  • GnuTLS library
  • OpenSSL library
  • NSS library
  • OpenJDK
  • Libkrb5
  • BIND
  • OpenSSH
Applications and languages which rely on any of these backends will follow the system policies as well. Examples are apache httpd, nginx, php, and others.

Rule   Configure System Cryptography Policy   [ref]

To configure the system cryptography policy to use ciphers only from the DEFAULT policy, run the following command:
$ sudo update-crypto-policies --set DEFAULT
         
The rule checks if settings for selected crypto policy are configured as expected. Configuration files in the /etc/crypto-policies/back-ends are either symlinks to correct files provided by Crypto-policies package or they are regular files in case crypto policy customizations are applied. Crypto policies may be customized by crypto policy modules, in which case it is delimited from the base policy using a colon.
Warning:  The system needs to be rebooted for these changes to take effect.
Warning:  System Crypto Modules must be provided by a vendor that undergoes FIPS-140 certifications. FIPS-140 is applicable to all Federal agencies that use cryptographic-based security systems to protect sensitive information in computer and telecommunication systems (including voice systems) as defined in Section 5131 of the Information Technology Management Reform Act of 1996, Public Law 104-106. This standard shall be used in designing and implementing cryptographic modules that Federal departments and agencies operate or are operated for them under contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf To meet this, the system has to have cryptographic software provided by a vendor that has undergone this certification. This means providing documentation, test results, design information, and independent third party review by an accredited lab. While open source software is capable of meeting this, it does not meet FIPS-140 unless the vendor submits to this process.
Rationale:
Centralized cryptographic policies simplify applying secure ciphers across an operating system and the applications that run on that operating system. Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_configure_crypto_policy
References:
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
ism1446
nerc-cipCIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nistAC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SC-12(2), SC-12(3)
osppFCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1
os-srgSRG-OS-000396-GPOS-00176, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174
cis1.6.1
pcidss42.2.7

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_system_crypto_policy # promote to variable
  set_fact:
    var_system_crypto_policy: !!str DEFAULT
  tags:
    - always

- name: Configure System Cryptography Policy
  lineinfile:
    path: /etc/crypto-policies/config
    regexp: ^(?!#)(\S+)$
    line: '{{ var_system_crypto_policy }}'
    create: true
  tags:
  - NIST-800-53-AC-17(2)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-MA-4(6)
  - NIST-800-53-SC-12(2)
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SC-13
  - PCI-DSSv4-2.2.7
  - configure_crypto_policy
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Verify that Crypto Policy is Set (runtime)
  command: /usr/bin/update-crypto-policies --set {{ var_system_crypto_policy }}
  tags:
  - NIST-800-53-AC-17(2)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-MA-4(6)
  - NIST-800-53-SC-12(2)
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SC-13
  - PCI-DSSv4-2.2.7
  - configure_crypto_policy
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
        - name: configure-crypto-policy.service
          enabled: true
          contents: |
            [Unit]
            Before=kubelet.service
            [Service]
            Type=oneshot
            ExecStart=update-crypto-policies --set {{.var_system_crypto_policy}}
            RemainAfterExit=yes
            [Install]
            WantedBy=multi-user.target


var_system_crypto_policy='DEFAULT'


stderr_of_call=$(update-crypto-policies --set ${var_system_crypto_policy} 2>&1 > /dev/null)
rc=$?

if test "$rc" = 127; then
	echo "$stderr_of_call" >&2
	echo "Make sure that the script is installed on the remediated system." >&2
	echo "See output of the 'dnf provides update-crypto-policies' command" >&2
	echo "to see what package to (re)install" >&2

	false  # end with an error code
elif test "$rc" != 0; then
	echo "Error invoking the update-crypto-policies script: $stderr_of_call" >&2
	false  # end with an error code
fi

Rule   Configure SSH to use System Crypto Policy   [ref]

Crypto Policies provide a centralized control over crypto algorithms usage of many packages. SSH is supported by crypto policy, but the SSH configuration may be set up to ignore it. To check that Crypto Policies settings are configured correctly, ensure that the CRYPTO_POLICY variable is either commented or not set at all in the /etc/sysconfig/sshd.
Rationale:
Overriding the system crypto policy makes the behavior of the SSH service violate expectations, and makes system configuration more fragmented.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_configure_ssh_crypto_policy
References:
disaCCI-001453
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
nerc-cipCIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nistAC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13
osppFCS_SSH_EXT.1, FCS_SSHS_EXT.1, FCS_SSHC_EXT.1
pcidssReq-2.2
os-srgSRG-OS-000250-GPOS-00093
cis1.6.2
pcidss42.2.7

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
- name: Configure SSH to use System Crypto Policy
  lineinfile:
    dest: /etc/sysconfig/sshd
    state: absent
    regexp: (?i)^\s*CRYPTO_POLICY.*$
  tags:
  - NIST-800-53-AC-17(2)
  - NIST-800-53-AC-17(a)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-MA-4(6)
  - NIST-800-53-SC-13
  - PCI-DSS-Req-2.2
  - PCI-DSSv4-2.2.7
  - configure_ssh_crypto_policy
  - disable_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - reboot_required


SSH_CONF="/etc/sysconfig/sshd"

sed -i "/^\s*CRYPTO_POLICY.*$/Id" $SSH_CONF
Group   GNOME Desktop Environment   Group contains 3 groups and 10 rules
[ref]   GNOME is a graphical desktop environment bundled with many Linux distributions that allow users to easily interact with the operating system graphically rather than textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user switching contexts as well as display server management.

GNOME is developed by the GNOME Project and is considered the default Red Hat Graphical environment.

For more information on GNOME and the GNOME Project, see https://www.gnome.org.
Group   Configure GNOME Login Screen   Group contains 1 rule
Group   GNOME Media Settings   Group contains 2 rules
[ref]   GNOME media settings that apply to the graphical interface.

Rule   Disable GNOME3 Automounting   [ref]

The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable automount within GNOME3, add or set automount to false in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/media-handling]
automount=false
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/media-handling/automount
After the settings have been set, run dconf update.
Rationale:
Disabling automatic mounting in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount
References:
cis-csc12, 16
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui3.1.7
disaCCI-000366, CCI-000778, CCI-001958
isa-62443-20094.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.AC-6
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis1.8.6, 1.8.7
pcidss43.4.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Disable GNOME3 Automounting - automount
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/media-handling
    option: automount
    value: 'false'
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME3 Automounting - automount
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/media-handling/automount$
    line: /org/gnome/desktop/media-handling/automount
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/media-handling\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*automount\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)automount(\s*=)/#\1automount\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/media-handling\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/media-handling]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "false")"
if grep -q "^\\s*automount\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*automount\\s*=\\s*.*/automount=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/media-handling\\]|a\\automount=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/media-handling/automount$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/media-handling/automount$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/media-handling/automount$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/media-handling/automount" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

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

Rule   Disable GNOME3 Automount Opening   [ref]

The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable automount-open within GNOME3, add or set automount-open to false in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/media-handling]
automount-open=false
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/media-handling/automount-open
After the settings have been set, run dconf update.
Rationale:
Automatically mounting file systems permits easy introduction of unknown devices, thereby facilitating malicious activity. Disabling automatic mounting in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_disable_automount_open
References:
cis-csc12, 16
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui3.1.7
disaCCI-000366, CCI-000778, CCI-001958
isa-62443-20094.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.AC-6
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis1.8.6, 1.8.7
pcidss43.4.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount_open
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Disable GNOME3 Automounting - automount-open
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/media-handling
    option: automount-open
    value: 'false'
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount_open
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME3 Automounting - automount-open
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/media-handling/automount-open$
    line: /org/gnome/desktop/media-handling/automount-open
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount_open
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-3.4.2
  - dconf_gnome_disable_automount_open
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/media-handling\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*automount-open\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)automount-open(\s*=)/#\1automount-open\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/media-handling\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/media-handling]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "false")"
if grep -q "^\\s*automount-open\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*automount-open\\s*=\\s*.*/automount-open=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/media-handling\\]|a\\automount-open=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/media-handling/automount-open$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/media-handling/automount-open$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/media-handling/automount-open$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/media-handling/automount-open" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Configure GNOME Screen Locking   Group contains 6 rules
[ref]   In the default GNOME3 desktop, the screen can be locked by selecting the user name in the far right corner of the main panel and selecting Lock.

The following sections detail commands to enforce idle activation of the screensaver, screen locking, a blank-screen screensaver, and an idle activation time.

Because users should be trained to lock the screen when they step away from the computer, the automatic locking feature is only meant as a backup.

The root account can be screen-locked; however, the root account should never be used to log into an X Windows environment and should only be used to for direct login via console in emergency circumstances.

For more information about enforcing preferences in the GNOME3 environment using the DConf configuration system, see http://wiki.gnome.org/dconf and the man page dconf(1).

Rule   Enable GNOME3 Screensaver Idle Activation   [ref]

To activate the screensaver in the GNOME3 desktop after a period of inactivity, add or set idle-activation-enabled to true in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/screensaver]
idle-activation-enabled=true
Once the setting has been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/screensaver/idle-activation-enabled
After the settings have been set, run dconf update.
Rationale:
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock.

Enabling idle activation of the screensaver ensures the screensaver will be activated after the idle delay. Applications requiring continuous, real-time screen display (such as network management products) require the login session does not have administrator rights and the display station is located in a controlled-access area.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_idle_activation_enabled
References:
cis-csc1, 12, 15, 16
cjis5.5.5
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000057
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-11(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_activation_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Enable GNOME3 Screensaver Idle Activation
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/screensaver
    option: idle-activation-enabled
    value: 'true'
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_activation_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME idle-activation-enabled
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/screensaver/idle-activation-enabled$
    line: /org/gnome/desktop/screensaver/idle-activation-enabled
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_activation_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_activation_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*idle-activation-enabled\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)idle-activation-enabled(\s*=)/#\1idle-activation-enabled\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*idle-activation-enabled\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*idle-activation-enabled\\s*=\\s*.*/idle-activation-enabled=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\idle-activation-enabled=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/idle-activation-enabled$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/screensaver/idle-activation-enabled$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/screensaver/idle-activation-enabled$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/screensaver/idle-activation-enabled" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

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

Rule   Set GNOME3 Screensaver Inactivity Timeout   [ref]

The idle time-out value for inactivity in the GNOME3 desktop is configured via the idle-delay setting must be set under an appropriate configuration file(s) in the /etc/dconf/db/local.d directory and locked in /etc/dconf/db/local.d/locks directory to prevent user modification.

For example, to configure the system for a 15 minute delay, add the following to /etc/dconf/db/local.d/00-security-settings:
[org/gnome/desktop/session]
idle-delay=uint32 900
Rationale:
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME3 can be configured to identify when a user's session has idled and take action to initiate a session lock.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_idle_delay
References:
cis-csc1, 12, 15, 16
cjis5.5.5
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000057, CCI-000060
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-11(a), CM-6(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.4
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy
- name: XCCDF Value inactivity_timeout_value # promote to variable
  set_fact:
    inactivity_timeout_value: !!str 900
  tags:
    - always

- name: Set GNOME3 Screensaver Inactivity Timeout
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/session
    option: idle-delay
    value: uint32 {{ inactivity_timeout_value }}
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_idle_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

inactivity_timeout_value='900'


# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/session\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*idle-delay\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)idle-delay(\s*=)/#\1idle-delay\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/session\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/session]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "uint32 ${inactivity_timeout_value}")"
if grep -q "^\\s*idle-delay\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*idle-delay\\s*=\\s*.*/idle-delay=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/session\\]|a\\idle-delay=${escaped_value}" "${DCONFFILE}"
fi
dconf update

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

Rule   Set GNOME3 Screensaver Lock Delay After Activation Period   [ref]

To activate the locking delay of the screensaver in the GNOME3 desktop when the screensaver is activated, add or set lock-delay to uint32 10 in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/screensaver]
lock-delay=uint32 10
         
After the settings have been set, run dconf update.
Rationale:
A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not want to logout because of the temporary nature of the absense.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_delay
References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000056, CCI-000057, CCI-000060
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-11(a), CM-6(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.4
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy
- name: XCCDF Value var_screensaver_lock_delay # promote to variable
  set_fact:
    var_screensaver_lock_delay: !!str 10
  tags:
    - always

- name: Set GNOME3 Screensaver Lock Delay After Activation Period
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/screensaver
    option: lock-delay
    value: uint32 {{ var_screensaver_lock_delay }}
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_delay
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

var_screensaver_lock_delay='10'


# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*lock-delay\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)lock-delay(\s*=)/#\1lock-delay\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "uint32 ${var_screensaver_lock_delay}")"
if grep -q "^\\s*lock-delay\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*lock-delay\\s*=\\s*.*/lock-delay=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\lock-delay=${escaped_value}" "${DCONFFILE}"
fi
dconf update

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

Rule   Enable GNOME3 Screensaver Lock After Idle Period   [ref]

To activate locking of the screensaver in the GNOME3 desktop when it is activated, add or set lock-enabled to true in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/screensaver]
lock-enabled=true
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/screensaver/lock-enabled
After the settings have been set, run dconf update.
Rationale:
A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not want to logout because of the temporary nature of the absense.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled
References:
cis-csc1, 12, 15, 16
cjis5.5.5
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000056, CCI-000058, CCI-000060
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000028-GPOS-00009, SRG-OS-000030-GPOS-00011
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution == 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Enable GNOME3 Screensaver Lock After Idle Period
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/screensaver
    option: lock-enabled
    value: 'true'
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution != 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME lock-enabled
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/screensaver/lock-enabled$
    line: /org/gnome/desktop/screensaver/lock-enabled
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution != 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Enable GNOME3 Screensaver Lock After Idle Period
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/lockdown
    option: disable-lock-screen
    value: 'false'
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution == 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME disable-lock-screen
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/lockdown/disable-lock-screen$
    line: /org/gnome/desktop/lockdown/disable-lock-screen
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution == 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Check GNOME3 screenserver disable-lock-screen false
  command: gsettings get org.gnome.desktop.lockdown disable-lock-screen
  register: cmd_out
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution == 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Update GNOME3 screenserver disable-lock-screen false
  command: gsettings set org.gnome.desktop.lockdown disable-lock-screen false
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_distribution == 'SLES'
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_lock_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*lock-enabled\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)lock-enabled(\s*=)/#\1lock-enabled\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "true")"
if grep -q "^\\s*lock-enabled\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*lock-enabled\\s*=\\s*.*/lock-enabled=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\lock-enabled=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/lock-enabled$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/screensaver/lock-enabled$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/screensaver/lock-enabled$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/screensaver/lock-enabled" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

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

Rule   Implement Blank Screensaver   [ref]

To set the screensaver mode in the GNOME3 desktop to a blank screen, add or set picture-uri to string '' in /etc/dconf/db/local.d/00-security-settings. For example:
[org/gnome/desktop/screensaver]
picture-uri=string ''
Once the settings have been added, add a lock to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/screensaver/picture-uri
After the settings have been set, run dconf update.
Rationale:
Setting the screensaver mode to blank-only conceals the contents of the display from passersby.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_mode_blank
References:
cis-csc1, 12, 15, 16
cjis5.5.5
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000060
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-11(1), CM-6(a), AC-11(1).1
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000031-GPOS-00012
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(1)
  - NIST-800-53-AC-11(1).1
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_mode_blank
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Implement Blank Screensaver
  ini_file:
    dest: /etc/dconf/db/local.d/00-security-settings
    section: org/gnome/desktop/screensaver
    option: picture-uri
    value: string ''
    create: true
    no_extra_spaces: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(1)
  - NIST-800-53-AC-11(1).1
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_mode_blank
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME picture-uri
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/screensaver/picture-uri$
    line: /org/gnome/desktop/screensaver/picture-uri
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(1)
  - NIST-800-53-AC-11(1).1
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_mode_blank
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.5
  - NIST-800-171-3.1.10
  - NIST-800-53-AC-11(1)
  - NIST-800-53-AC-11(1).1
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_screensaver_mode_blank
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
# If files contain ibus or distro, ignore them.
# The assignment assumes that individual filenames don't contain :
readarray -t SETTINGSFILES < <(grep -r "\\[org/gnome/desktop/screensaver\\]" "/etc/dconf/db/" \
                                | grep -v 'distro\|ibus\|local.d' | cut -d":" -f1)
DCONFFILE="/etc/dconf/db/local.d/00-security-settings"
DBDIR="/etc/dconf/db/local.d"

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*picture-uri\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)picture-uri(\s*=)/#\1picture-uri\2/g" "${SETTINGSFILES[@]}"
    fi
fi

[ ! -z "${DCONFFILE}" ] && echo "" >> "${DCONFFILE}"
if ! grep -q "\\[org/gnome/desktop/screensaver\\]" "${DCONFFILE}"
then
    printf '%s\n' "[org/gnome/desktop/screensaver]" >> ${DCONFFILE}
fi

escaped_value="$(sed -e 's/\\/\\\\/g' <<< "string ''")"
if grep -q "^\\s*picture-uri\\s*=" "${DCONFFILE}"
then
        sed -i "s/\\s*picture-uri\\s*=\\s*.*/picture-uri=${escaped_value}/g" "${DCONFFILE}"
    else
        sed -i "\\|\\[org/gnome/desktop/screensaver\\]|a\\picture-uri=${escaped_value}" "${DCONFFILE}"
fi
dconf update
# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/screensaver/picture-uri$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/screensaver/picture-uri$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/screensaver/picture-uri$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/screensaver/picture-uri" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

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

Rule   Ensure Users Cannot Change GNOME3 Session Idle Settings   [ref]

If not already configured, ensure that users cannot change GNOME3 session idle settings by adding /org/gnome/desktop/session/idle-delay to /etc/dconf/db/local.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/desktop/session/idle-delay
After the settings have been set, run dconf update.
Rationale:
A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. As such, users should not be allowed to change session settings.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_session_idle_user_locks
References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.10
disaCCI-000057, CCI-000060
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
pcidssReq-8.1.8
os-srgSRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis1.8.5
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_session_idle_user_locks
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Prevent user modification of GNOME Session idle-delay
  lineinfile:
    path: /etc/dconf/db/local.d/locks/00-security-settings-lock
    regexp: ^/org/gnome/desktop/session/idle-delay$
    line: /org/gnome/desktop/session/idle-delay
    create: true
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_session_idle_user_locks
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Dconf Update
  command: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.10
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.8
  - PCI-DSSv4-8.2.8
  - dconf_gnome_session_idle_user_locks
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

# Check for setting in any of the DConf db directories
LOCKFILES=$(grep -r "^/org/gnome/desktop/session/idle-delay$" "/etc/dconf/db/" \
            | grep -v 'distro\|ibus\|local.d' | grep ":" | cut -d":" -f1)
LOCKSFOLDER="/etc/dconf/db/local.d/locks"

mkdir -p "${LOCKSFOLDER}"

# Comment out the configurations in databases different from the target one
if [[ ! -z "${LOCKFILES}" ]]
then
    sed -i -E "s|^/org/gnome/desktop/session/idle-delay$|#&|" "${LOCKFILES[@]}"
fi

if ! grep -qr "^/org/gnome/desktop/session/idle-delay$" /etc/dconf/db/local.d/
then
    echo "/org/gnome/desktop/session/idle-delay" >> "/etc/dconf/db/local.d/locks/00-security-settings-lock"
fi
dconf update

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

Rule   Make sure that the dconf databases are up-to-date with regards to respective keyfiles   [ref]

By default, DConf uses a binary database as a data backend. The system-level database is compiled from keyfiles in the /etc/dconf/db/ directory by the
dconf update
command. More specifically, content present in the following directories:
/etc/dconf/db/distro.d
/etc/dconf/db/local.d
Rationale:
Unlike text-based keyfiles, the binary database is impossible to check by OVAL. Therefore, in order to evaluate dconf configuration, both have to be true at the same time - configuration files have to be compliant, and the database needs to be more recent than those keyfiles, which gives confidence that it reflects them.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_dconf_db_up_to_date
References:
hipaa164.308(a)(1)(ii)(B), 164.308(a)(5)(ii)(A)
pcidssReq-6.2
os-srgSRG-OS-000480-GPOS-00227
cisreload_dconf_db
pcidss48.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-8.2.8
  - dconf_db_up_to_date
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

- name: Run dconf update
  ansible.builtin.command:
    cmd: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-8.2.8
  - dconf_db_up_to_date
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

dconf update

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Sudo   Group contains 5 rules
[ref]   Sudo, which stands for "su 'do'", provides the ability to delegate authority to certain users, groups of users, or system administrators. When configured for system users and/or groups, Sudo can allow a user or group to execute privileged commands that normally only root is allowed to execute.

For more information on Sudo and addition Sudo configuration options, see https://www.sudo.ws.

Rule   Install sudo Package   [ref]

The sudo package can be installed with the following command:
$ sudo dnf install sudo
Rationale:
sudo is a program designed to allow a system administrator to give limited root privileges to users and log root activity. The basic philosophy is to give as few privileges as possible but still allow system users to get their work done.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_sudo_installed
Identifiers:

CCE-87100-4

References:
ism1382, 1384, 1386
nistCM-6(a)
osppFMT_MOF_EXT.1
os-srgSRG-OS-000324-GPOS-00125
anssiR33
cis5.2.1
pcidss42.2.6


[[packages]]
name = "sudo"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Ensure sudo is installed
  package:
    name: sudo
    state: present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-87100-4
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_sudo_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=sudo

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_sudo

class install_sudo {
  package { 'sudo':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

if ! rpm -q --quiet "sudo" ; then
    dnf install -y "sudo"
fi

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

Rule   Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_pty   [ref]

The sudo use_pty tag, when specified, will only execute sudo commands from users logged in to a real tty. This should be enabled by making sure that the use_pty tag exists in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/.
Rationale:
Requiring that sudo commands be run in a pseudo-terminal can prevent an attacker from retaining access to the user's terminal after the main program has finished executing.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_sudo_add_use_pty
References:
pcidssReq-10.2.5
anssiR39
cis5.2.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty

- name: Ensure use_pty is enabled in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults.*\buse_pty\b.*$
    line: Defaults use_pty
    validate: /usr/sbin/visudo -cf %s
  when: '"sudo" in ansible_facts.packages'
  tags:
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults[\s]*\buse_pty\b.*$' /etc/sudoers; then
        # sudoers file doesn't define Option use_pty
        echo "Defaults use_pty" >> /etc/sudoers
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

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

Rule   Ensure Sudo Logfile Exists - sudo logfile   [ref]

A custom log sudo file can be configured with the 'logfile' tag. This rule configures a sudo custom logfile at the default location suggested by CIS, which uses /var/log/sudo.log.
Rationale:
A sudo log file simplifies auditing of sudo commands.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_sudo_custom_logfile
References:
pcidssReq-10.2.5
cis5.2.3
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile
- name: XCCDF Value var_sudo_logfile # promote to variable
  set_fact:
    var_sudo_logfile: !!str /var/log/sudo.log
  tags:
    - always

- name: Ensure logfile is enabled with the appropriate value in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s(.*)\blogfile=[-]?.+\b(.*)$
    line: Defaults \1logfile={{ var_sudo_logfile }}\2
    validate: /usr/sbin/visudo -cf %s
    backrefs: true
  register: edit_sudoers_logfile_option
  when: '"sudo" in ansible_facts.packages'
  tags:
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile

- name: Enable logfile option with appropriate value in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    line: Defaults logfile={{ var_sudo_logfile }}
    validate: /usr/sbin/visudo -cf %s
  when:
  - '"sudo" in ansible_facts.packages'
  - edit_sudoers_logfile_option is defined and not edit_sudoers_logfile_option.changed
  tags:
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

var_sudo_logfile='/var/log/sudo.log'


if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults[\s]*\blogfile\s*=\s*(?:"?([^",\s]+)"?)\b.*$' /etc/sudoers; then
        # sudoers file doesn't define Option logfile
        echo "Defaults logfile=${var_sudo_logfile}" >> /etc/sudoers
    else
        # sudoers file defines Option logfile, remediate if appropriate value is not set
        if ! grep -P "^[\s]*Defaults.*\blogfile=${var_sudo_logfile}\b.*$" /etc/sudoers; then
            
            escaped_variable=${var_sudo_logfile//$'/'/$'\/'}
            sed -Ei "s/(^[\s]*Defaults.*\blogfile=)[-]?.+(\b.*$)/\1$escaped_variable\2/" /etc/sudoers
        fi
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

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

Rule   Ensure Users Re-Authenticate for Privilege Escalation - sudo   [ref]

The sudo NOPASSWD and !authenticate option, when specified, allows a user to execute commands using sudo without having to authenticate. This should be disabled by making sure that NOPASSWD and/or !authenticate do not exist in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/."
Rationale:
Without re-authentication, users may access resources or perform tasks for which they do not have authorization.

When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_sudo_require_authentication
References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.10, DSS06.03, DSS06.10
disaCCI-002038
isa-62443-20094.3.3.5.1, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-11, CM-6(a)
nist-csfPR.AC-1, PR.AC-7
os-srgSRG-OS-000373-GPOS-00156
cis5.2.4
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Find /etc/sudoers.d/ files
  ansible.builtin.find:
    paths:
    - /etc/sudoers.d/
  register: sudoers
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Remove lines containing NOPASSWD from sudoers files
  ansible.builtin.replace:
    regexp: (^(?!#).*[\s]+NOPASSWD[\s]*\:.*$)
    replace: '# \g<1>'
    path: '{{ item.path }}'
    validate: /usr/sbin/visudo -cf %s
  with_items:
  - path: /etc/sudoers
  - '{{ sudoers.files }}'
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Find /etc/sudoers.d/ files
  ansible.builtin.find:
    paths:
    - /etc/sudoers.d/
  register: sudoers
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

- name: Remove lines containing !authenticate from sudoers files
  ansible.builtin.replace:
    regexp: (^(?!#).*[\s]+\!authenticate.*$)
    replace: '# \g<1>'
    path: '{{ item.path }}'
    validate: /usr/sbin/visudo -cf %s
  with_items:
  - path: /etc/sudoers
  - '{{ sudoers.files }}'
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_authentication

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

for f in /etc/sudoers /etc/sudoers.d/* ; do
  if [ ! -e "$f" ] ; then
    continue
  fi
  matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      # comment out "NOPASSWD" matches to preserve user data
      sed -i "s/^${entry}$/# &/g" $f
    done <<< "$matching_list"

    /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
  fi
done

for f in /etc/sudoers /etc/sudoers.d/* ; do
  if [ ! -e "$f" ] ; then
    continue
  fi
  matching_list=$(grep -P '^(?!#).*[\s]+\!authenticate.*$' $f | uniq )
  if ! test -z "$matching_list"; then
    while IFS= read -r entry; do
      # comment out "!authenticate" matches to preserve user data
      sed -i "s/^${entry}$/# &/g" $f
    done <<< "$matching_list"

    /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
  fi
done

Rule   Require Re-Authentication When Using the sudo Command   [ref]

The sudo timestamp_timeout tag sets the amount of time sudo password prompt waits. The default timestamp_timeout value is 5 minutes. The timestamp_timeout should be configured by making sure that the timestamp_timeout tag exists in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/. If the value is set to an integer less than 0, the user's time stamp will not expire and the user will not have to re-authenticate for privileged actions until the user's session is terminated.
Rationale:
Without re-authentication, users may access resources or perform tasks for which they do not have authorization.

When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_sudo_require_reauthentication
References:
disaCCI-002038
nistIA-11
os-srgSRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158
cis5.2.5, 5.2.6
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication
- name: XCCDF Value var_sudo_timestamp_timeout # promote to variable
  set_fact:
    var_sudo_timestamp_timeout: !!str 5
  tags:
    - always

- name: Require Re-Authentication When Using the sudo Command - Find /etc/sudoers.d/*
    files containing 'Defaults timestamp_timeout'
  ansible.builtin.find:
    path: /etc/sudoers.d
    patterns: '*'
    contains: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=.*
  register: sudoers_d_defaults_timestamp_timeout
  when: '"sudo" in ansible_facts.packages'
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Remove 'Defaults timestamp_timeout'
    from /etc/sudoers.d/* files
  ansible.builtin.lineinfile:
    path: '{{ item.path }}'
    regexp: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=.*
    state: absent
  with_items: '{{ sudoers_d_defaults_timestamp_timeout.files }}'
  when: '"sudo" in ansible_facts.packages'
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Ensure timestamp_timeout
    has the appropriate value in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s(.*)\btimestamp_timeout[\s]*=[\s]*[-]?\w+\b(.*)$
    line: Defaults \1timestamp_timeout={{ var_sudo_timestamp_timeout }}\2
    validate: /usr/sbin/visudo -cf %s
    backrefs: true
  register: edit_sudoers_timestamp_timeout_option
  when: '"sudo" in ansible_facts.packages'
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Enable timestamp_timeout
    option with correct value in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    line: Defaults timestamp_timeout={{ var_sudo_timestamp_timeout }}
    validate: /usr/sbin/visudo -cf %s
  when:
  - '"sudo" in ansible_facts.packages'
  - |
    edit_sudoers_timestamp_timeout_option is defined and not edit_sudoers_timestamp_timeout_option.changed
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

- name: Require Re-Authentication When Using the sudo Command - Remove timestamp_timeout
    wrong values in /etc/sudoers
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!{{ var_sudo_timestamp_timeout
      }}\b)[-]?\w+\b.*$
    state: absent
    validate: /usr/sbin/visudo -cf %s
  when: '"sudo" in ansible_facts.packages'
  tags:
  - NIST-800-53-IA-11
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_require_reauthentication

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q sudo; then

var_sudo_timestamp_timeout='5'


if grep -Px '^[\s]*Defaults.*timestamp_timeout[\s]*=.*' /etc/sudoers.d/*; then
    find /etc/sudoers.d/ -type f -exec sed -Ei "/^[[:blank:]]*Defaults.*timestamp_timeout[[:blank:]]*=.*/d" {} \;
fi

if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults.*timestamp_timeout[\s]*=[\s]*[-]?\w+.*$' /etc/sudoers; then
        # sudoers file doesn't define Option timestamp_timeout
        echo "Defaults timestamp_timeout=${var_sudo_timestamp_timeout}" >> /etc/sudoers
    else
        # sudoers file defines Option timestamp_timeout, remediate wrong values if present
        if grep -qP "^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!${var_sudo_timestamp_timeout}\b)[-]?\w+\b.*$" /etc/sudoers; then
            sed -Ei "s/(^[[:blank:]]*Defaults.*timestamp_timeout[[:blank:]]*=)[[:blank:]]*[-]?\w+(.*$)/\1${var_sudo_timestamp_timeout}\2/" /etc/sudoers
        fi
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   System Tooling / Utilities   Group contains 1 rule
[ref]   The following checks evaluate the system for recommended base packages -- both for installation and removal.

Rule   Install cryptsetup Package   [ref]

The cryptsetup package can be installed with the following command:
$ sudo dnf install cryptsetup
Rationale:
LUKS is the upcoming standard for Linux hard disk encryption. By providing a standard on-disk format, it does not only facilitate compatibility among distributions, but also provide secure management of multiple user passwords. In contrast to existing solution, LUKS stores all necessary setup information in the partition header, enabling the user to transport or migrate their data seamlessly. LUKS for dm-crypt is implemented in cryptsetup.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_cryptsetup-luks_installed
References:
pcidss43.5.1.2


[[packages]]
name = "cryptsetup"
version = "*"

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Ensure cryptsetup is installed
  package:
    name: cryptsetup
    state: present
  tags:
  - PCI-DSSv4-3.5.1.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_cryptsetup-luks_installed

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

package --add=cryptsetup

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
include install_cryptsetup

class install_cryptsetup {
  package { 'cryptsetup':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable

if ! rpm -q --quiet "cryptsetup" ; then
    dnf install -y "cryptsetup"
fi
Group   Updating Software   Group contains 4 rules
[ref]   The dnf command line tool is used to install and update software packages. The system also provides a graphical software update tool in the System menu, in the Administration submenu, called Software Update.

Red Hat Enterprise Linux 10 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using dnf or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.

Rule   Ensure gpgcheck Enabled In Main dnf Configuration   [ref]

The gpgcheck option controls whether RPM packages' signatures are always checked prior to installation. To configure dnf to check package signatures before installing them, ensure the following line appears in /etc/dnf/dnf.conf in the [main] section:
gpgcheck=1
Rationale:
Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
disaCCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
anssiR59
cis1.2.1.2
pcidss46.3.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - configure_strategy
  - ensure_gpgcheck_globally_activated
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

- name: Ensure GPG check is globally activated
  ini_file:
    dest: /etc/dnf/dnf.conf
    section: main
    option: gpgcheck
    value: 1
    no_extra_spaces: true
    create: false
  when: '"dnf" in ansible_facts.packages'
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - configure_strategy
  - ensure_gpgcheck_globally_activated
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

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

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^gpgcheck")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "1"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^gpgcheck\\>" "/etc/dnf/dnf.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^gpgcheck\\>.*/$escaped_formatted_output/gi" "/etc/dnf/dnf.conf"
else
    if [[ -s "/etc/dnf/dnf.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/dnf/dnf.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/dnf/dnf.conf"
    fi
    printf '%s\n' "$formatted_output" >> "/etc/dnf/dnf.conf"
fi

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

Rule   Ensure gpgcheck Enabled for All dnf Package Repositories   [ref]

To ensure signature checking is not disabled for any repos, remove any lines from files in /etc/yum.repos.d of the form:
gpgcheck=0
Rationale:
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA)."
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_gpgcheck_never_disabled
References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
disaCCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
anssiR59
pcidss46.3.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:enable
- name: Grep for dnf repo section names
  shell: |
    set -o pipefail
    grep -HEr '^\[.+\]' -r /etc/yum.repos.d/
  register: repo_grep_results
  failed_when: repo_grep_results.rc not in [0, 1]
  changed_when: false
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - enable_strategy
  - ensure_gpgcheck_never_disabled
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

- name: Set gpgcheck=1 for each dnf repo
  ini_file:
    path: '{{ item[0] }}'
    section: '{{ item[1] }}'
    option: gpgcheck
    value: '1'
    no_extra_spaces: true
  loop: '{{ repo_grep_results.stdout |regex_findall( ''(.+\.repo):\[(.+)\]\n?'' )
    if repo_grep_results is not skipped else [] }}'
  when: repo_grep_results is not skipped
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - enable_strategy
  - ensure_gpgcheck_never_disabled
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed


sed -i 's/gpgcheck\s*=.*/gpgcheck=1/g' /etc/yum.repos.d/*

Rule   Ensure Red Hat GPG Key Installed   [ref]

To ensure the system can cryptographically verify base software packages come from Red Hat (and to connect to the Red Hat Network to receive them), the Red Hat GPG key must properly be installed. To install the Red Hat GPG key, run:
$ sudo subscription-manager register
If the system is not connected to the Internet or an RHN Satellite, then install the Red Hat GPG key from trusted media such as the Red Hat installation CD-ROM or DVD. Assuming the disc is mounted in /media/cdrom, use the following command as the root user to import it into the keyring:
$ sudo rpm --import /media/cdrom/RPM-GPG-KEY
Alternatively, the key may be pre-loaded during the RHEL installation. In such cases, the key can be installed by running the following command:
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
Rationale:
Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_redhat_gpgkey_installed
References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
disaCCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nerc-cipCIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
anssiR59
pcidss46.3.3

Complexity:medium
Disruption:medium
Reboot:false
Strategy:restrict
- name: Read permission of GPG key directory
  stat:
    path: /etc/pki/rpm-gpg/
  register: gpg_key_directory_permission
  check_mode: false
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Read signatures in GPG key
  command: gpg --with-fingerprint --with-colons "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"
  changed_when: false
  register: gpg_fingerprints
  check_mode: false
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Set Fact - Installed GPG Fingerprints
  set_fact:
    gpg_installed_fingerprints: |-
      {{ gpg_fingerprints.stdout | regex_findall('^pub.*
      (?:^fpr[:]*)([0-9A-Fa-f]*)', '\1') | list }}
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Set Fact - Valid fingerprints
  set_fact:
    gpg_valid_fingerprints:
    - ''
    - ''
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Import RedHat GPG key
  rpm_key:
    state: present
    key: /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
  when:
  - gpg_key_directory_permission.stat.mode <= '0755'
  - (gpg_installed_fingerprints | difference(gpg_valid_fingerprints)) | length ==
    0
  - gpg_installed_fingerprints | length > 0
  - ansible_distribution == "RedHat"
  tags:
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

# The two fingerprints below are retrieved from https://access.redhat.com/security/team/key
readonly REDHAT_RELEASE_FINGERPRINT=""
readonly REDHAT_AUXILIARY_FINGERPRINT=""

# Location of the key we would like to import (once it's integrity verified)
readonly REDHAT_RELEASE_KEY="/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"

RPM_GPG_DIR_PERMS=$(stat -c %a "$(dirname "$REDHAT_RELEASE_KEY")")

# Verify /etc/pki/rpm-gpg directory permissions are safe
if [ "${RPM_GPG_DIR_PERMS}" -le "755" ]
then
  # If they are safe, try to obtain fingerprints from the key file
  # (to ensure there won't be e.g. CRC error).

  readarray -t GPG_OUT < <(gpg --with-fingerprint --with-colons "$REDHAT_RELEASE_KEY" | grep "^fpr" | cut -d ":" -f 10)

  GPG_RESULT=$?
  # No CRC error, safe to proceed
  if [ "${GPG_RESULT}" -eq "0" ]
  then
    echo "${GPG_OUT[*]}" | grep -vE "${REDHAT_RELEASE_FINGERPRINT}|${REDHAT_AUXILIARY_FINGERPRINT}" || {
      # If $REDHAT_RELEASE_KEY file doesn't contain any keys with unknown fingerprint, import it
      rpm --import "${REDHAT_RELEASE_KEY}"
    }
  fi
fi

Rule   Ensure Software Patches Installed   [ref]



NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates.
Warning:  Red Hat Enterprise Linux 10 does not have a corresponding OVAL CVE Feed. Therefore, this will result in a "not checked" result during a scan.
Rationale:
Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities. If the most recent security patches and updates are not installed, unauthorized users may take advantage of weaknesses in the unpatched software. The lack of prompt attention to patching could result in a system compromise.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_security_patches_up_to_date
References:
cis-csc18, 20, 4
cjis5.10.4.1
cobit5APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02
disaCCI-000366, CCI-001227
isa-62443-20094.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9
iso27001-2013A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3
nistSI-2(5), SI-2(c), CM-6(a)
nist-csfID.RA-1, PR.IP-12
osppFMT_MOF_EXT.1
pcidssReq-6.2
os-srgSRG-OS-000480-GPOS-00227
anssiR61
pcidss46.3.3
Group   Account and Access Control   Group contains 12 groups and 41 rules
[ref]   In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Red Hat Enterprise Linux 10.
Group   Warning Banners for System Accesses   Group contains 3 rules
[ref]   Each system should expose as little information about itself as possible.

System banners, which are typically displayed just before a login prompt, give out information about the service or the host's operating system. This might include the distribution name and the system kernel version, and the particular version of a network service. This information can assist intruders in gaining access to the system as it can reveal whether the system is running vulnerable software. Most network services can be configured to limit what information is displayed.

Many organizations implement security policies that require a system banner provide notice of the system's ownership, provide warning to unauthorized users, and remind authorized users of their consent to monitoring.

Rule   Verify Group Ownership of System Login Banner for Remote Connections   [ref]

To properly set the group owner of /etc/issue.net, run the command:
$ sudo chgrp root /etc/issue.net
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_issue_net
References:
cis1.7.6
pcidss41.2.8

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_groupowner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/issue.net
  file:
    path: /etc/issue.net
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_groupowner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chgrp 0 /etc/issue.net

Rule   Verify ownership of System Login Banner for Remote Connections   [ref]

To properly set the owner of /etc/issue.net, run the command:
$ sudo chown root /etc/issue.net 
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_issue_net
References:
cis1.7.6
pcidss41.2.8

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_owner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/issue.net
  file:
    path: /etc/issue.net
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_owner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chown 0 /etc/issue.net

Rule   Verify permissions on System Login Banner for Remote Connections   [ref]

To properly set the permissions of /etc/issue.net, run the command:
$ sudo chmod 0644 /etc/issue.net
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_issue_net
References:
cis1.7.6
pcidss41.2.8

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_permissions_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/issue.net
  file:
    path: /etc/issue.net
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_permissions_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure





chmod u-xs,g-xws,o-xwt /etc/issue.net
Group   Protect Accounts by Configuring PAM   Group contains 4 groups and 12 rules
[ref]   PAM, or Pluggable Authentication Modules, is a system which implements modular authentication for Linux programs. PAM provides a flexible and configurable architecture for authentication, and it should be configured to minimize exposure to unnecessary risk. This section contains guidance on how to accomplish that.

PAM is implemented as a set of shared objects which are loaded and invoked whenever an application wishes to authenticate a user. Typically, the application must be running as root in order to take advantage of PAM, because PAM's modules often need to be able to access sensitive stores of account information, such as /etc/shadow. Traditional privileged network listeners (e.g. sshd) or SUID programs (e.g. sudo) already meet this requirement. An SUID root application, userhelper, is provided so that programs which are not SUID or privileged themselves can still take advantage of PAM.

PAM looks in the directory /etc/pam.d for application-specific configuration information. For instance, if the program login attempts to authenticate a user, then PAM's libraries follow the instructions in the file /etc/pam.d/login to determine what actions should be taken.

One very important file in /etc/pam.d is /etc/pam.d/system-auth. This file, which is included by many other PAM configuration files, defines 'default' system authentication measures. Modifying this file is a good way to make far-reaching authentication changes, for instance when implementing a centralized authentication service.
Warning:  Be careful when making changes to PAM's configuration files. The syntax for these files is complex, and modifications can have unexpected consequences. The default configurations shipped with applications should be sufficient for most users.
Warning:  Running authconfig or system-config-authentication will re-write the PAM configuration files, destroying any manually made changes and replacing them with a series of system defaults. One reference to the configuration file syntax can be found at https://fossies.org/linux/Linux-PAM-docs/doc/sag/Linux-PAM_SAG.pdf.
Group   Set Lockouts for Failed Password Attempts   Group contains 5 rules
[ref]   The pam_faillock PAM module provides the capability to lock out user accounts after a number of failed login attempts. Its documentation is available in /usr/share/doc/pam-VERSION/txts/README.pam_faillock.

Warning:  Locking out user accounts presents the risk of a denial-of-service attack. The lockout policy must weigh whether the risk of such a denial-of-service attack outweighs the benefits of thwarting password guessing attacks.

Rule   Limit Password Reuse: password-auth   [ref]

Do not allow users to reuse recent passwords. This can be accomplished by using the remember option for the pam_pwhistory PAM module.

On systems with newer versions of authselect, the pam_pwhistory PAM module can be enabled via authselect feature:
authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile.

Newer systems also have the /etc/security/pwhistory.conf file for setting pam_pwhistory module options. This file should be used whenever available. Otherwise, the pam_pwhistory module options can be set in PAM files.

The value for remember option must be equal or greater than 4
Warning:  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report.
Warning:  Newer versions of authselect contain an authselect feature to easily and properly enable pam_pwhistory.so module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files. If a custom profile was created and used in the system before this authselect feature was available, the new feature can't be used with this custom profile and the remediation will fail. In this case, the custom profile should be recreated or manually updated.
Rationale:
Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.8
disaCCI-000200
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(f), IA-5(1)(e)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.5
os-srgSRG-OS-000077-GPOS-00045
cis5.3.3.3.1
pcidss48.3.7

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_remember # promote to variable
  set_fact:
    var_password_pam_remember: !!str 4
  tags:
    - always
- name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
  set_fact:
    var_password_pam_remember_control_flag: !!str requisite,required
  tags:
    - always

- name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
    tool'
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Collect the available authselect features'
  ansible.builtin.command:
    cmd: authselect list-features minimal
  register: result_authselect_available_features
  changed_when: false
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so using authselect
    feature'
  block:

  - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
      profile'
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: 'Limit Password Reuse: password-auth - Informative message based on the
      authselect integrity check result'
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: 'Limit Password Reuse: password-auth - Get authselect current features'
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: 'Limit Password Reuse: password-auth - Ensure "with-pwhistory" feature is
      enabled using authselect tool'
    ansible.builtin.command:
      cmd: authselect enable-feature with-pwhistory
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-pwhistory")

  - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  - result_authselect_available_features.stdout is search("with-pwhistory")
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Enable pam_pwhistory.so in appropriate
    PAM files'
  block:

  - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
      as a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/password-auth

  - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
      is used if authselect is present'
    block:

    - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: password-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: password-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Get authselect current features
        to also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("custom/")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Restore the authselect features
        in the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
      is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ '{{ var_password_pam_remember_control_flag.split(",")[0]
        }}' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: password-auth - Include or update the PAM module
      line in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: password-auth - Ensure the correct control for
        the required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1{{ var_password_pam_remember_control_flag.split(",")[0] }} \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
        line is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        insertafter: ^password.*requisite.*pam_pwquality\.so
        line: password    {{ var_password_pam_remember_control_flag.split(",")[0]
          }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - |
    (result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - Check the presence of /etc/security/pwhistory.conf
    file'
  ansible.builtin.stat:
    path: /etc/security/pwhistory.conf
  register: result_pwhistory_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
    in /etc/security/pwhistory.conf file'
  block:

  - name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
      parameter in /etc/security/pwhistory.conf'
    ansible.builtin.lineinfile:
      path: /etc/security/pwhistory.conf
      regexp: ^\s*remember\s*=
      line: remember = {{ var_password_pam_remember }}
      state: present

  - name: 'Limit Password Reuse: password-auth - Ensure the pam_pwhistory.so remember
      parameter is removed from PAM files'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if /etc/pam.d/password-auth
        file is present'
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_file_present

    - name: 'Limit Password Reuse: password-auth - Check the proper remediation for
        the system'
      block:

      - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
          as a local fact'
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
          tool'
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
          is used if authselect is present'
        block:

        - name: 'Limit Password Reuse: password-auth - Check integrity of authselect
            current profile'
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          failed_when: false

        - name: 'Limit Password Reuse: password-auth - Informative message based on
            the authselect integrity check result'
          ansible.builtin.assert:
            that:
            - result_authselect_check_cmd.rc == 0
            fail_msg:
            - authselect integrity check failed. Remediation aborted!
            - This remediation could not be applied because an authselect profile
              was not selected or the selected profile is not intact.
            - It is not recommended to manually edit the PAM files when authselect
              tool is available.
            - In cases where the default authselect profile does not cover a specific
              demand, a custom authselect profile is recommended.
            success_msg:
            - authselect integrity check passed

        - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: 'Limit Password Reuse: password-auth - Define the current authselect
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
            profile as a local fact'
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Get authselect current features
            to also enable them in the custom profile'
          ansible.builtin.shell:
            cmd: authselect current | tail -n+3 | awk '{ print $2 }'
          register: result_authselect_features
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Check if any custom profile
            with the same name was already created'
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - authselect_current_profile is not match("custom/")

        - name: 'Limit Password Reuse: password-auth - Create an authselect custom
            profile based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("custom/")
          - not result_authselect_custom_profile_present.stat.exists

        - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom
            profile is selected'
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: password-auth - Restore the authselect features
            in the custom profile'
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
            according to the custom authselect profile'
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
        when:
        - result_authselect_present.stat.exists

      - name: 'Limit Password Reuse: password-auth - Ensure the "remember" option
          from "pam_pwhistory.so" is not present in {{ pam_file_path }}'
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal

      - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are
          applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: password-auth - pam_pwhistory.so parameters are configured
    in PAM files'
  block:

  - name: 'Limit Password Reuse: password-auth - Define the PAM file to be edited
      as a local fact'
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/password-auth

  - name: 'Limit Password Reuse: password-auth - Check if system relies on authselect
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: password-auth - Ensure authselect custom profile
      is used if authselect is present'
    block:

    - name: 'Limit Password Reuse: password-auth - Check integrity of authselect current
        profile'
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: 'Limit Password Reuse: password-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: password-auth - Get authselect current profile'
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - result_authselect_check_cmd is success

    - name: 'Limit Password Reuse: password-auth - Define the current authselect profile
        as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")

    - name: 'Limit Password Reuse: password-auth - Define the new authselect custom
        profile as a local fact'
      ansible.builtin.set_fact:
        authselect_current_profile: '{{ result_authselect_profile.stdout }}'
        authselect_custom_profile: custom/hardening
      when:
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Get authselect current features
        to also enable them in the custom profile'
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Check if any custom profile with
        the same name was already created'
      ansible.builtin.stat:
        path: /etc/authselect/{{ authselect_custom_profile }}
      register: result_authselect_custom_profile_present
      changed_when: false
      when:
      - authselect_current_profile is not match("custom/")

    - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
        based on the current profile'
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile
          }}
      when:
      - result_authselect_check_cmd is success
      - authselect_current_profile is not match("custom/")
      - not result_authselect_custom_profile_present.stat.exists

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Ensure the authselect custom profile
        is selected'
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: password-auth - Restore the authselect features
        in the custom profile'
      ansible.builtin.command:
        cmd: authselect enable-feature {{ item }}
      loop: '{{ result_authselect_features.stdout_lines }}'
      register: result_pam_authselect_restore_features
      when:
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: password-auth - Change the PAM file to be edited
        according to the custom authselect profile'
      ansible.builtin.set_fact:
        pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
          | basename }}
    when:
    - result_authselect_present.stat.exists

  - name: 'Limit Password Reuse: password-auth - Check if expected PAM module line
      is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: password-auth - Include or update the PAM module
      line in {{ pam_file_path }}'
    block:

    - name: 'Limit Password Reuse: password-auth - Check if required PAM module line
        is present in {{ pam_file_path }} with different control'
      ansible.builtin.lineinfile:
        path: '{{ pam_file_path }}'
        regexp: ^\s*password\s+.*\s+pam_pwhistory.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: 'Limit Password Reuse: password-auth - Ensure the correct control for
        the required PAM module line in {{ pam_file_path }}'
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1requisite \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: 'Limit Password Reuse: password-auth - Ensure the required PAM module
        line is included in {{ pam_file_path }}'
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        line: password    requisite    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: 'Limit Password Reuse: password-auth - Check if the required PAM module
      option is present in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_remember_option_present

  - name: 'Limit Password Reuse: password-auth - Ensure the "remember" PAM option
      for "pam_pwhistory.so" is included in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so.*)
      line: \1 remember={{ var_password_pam_remember }}
      state: present
    register: result_pam_remember_add
    when:
    - result_pam_module_remember_option_present.found == 0

  - name: 'Limit Password Reuse: password-auth - Ensure the required value for "remember"
      PAM option from "pam_pwhistory.so" in {{ pam_file_path }}'
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]+\s*(.*)
      line: \1\2={{ var_password_pam_remember }} \3
    register: result_pam_remember_edit
    when:
    - result_pam_module_remember_option_present.found > 0

  - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
      (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - not result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_password_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

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

var_password_pam_remember='4'
var_password_pam_remember_control_flag='requisite,required'


var_password_pam_remember_control_flag="$(echo $var_password_pam_remember_control_flag | cut -d \, -f 1)"

if [ -f /usr/bin/authselect ]; then
    if authselect list-features minimal | grep -q with-pwhistory; then
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi
        authselect enable-feature with-pwhistory

        authselect apply-changes -b
    else
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
        if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "$PAM_FILE_PATH"
            else
                LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
                if [ ! -z $LAST_MATCH_LINE ]; then
                    sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' "$PAM_FILE_PATH"
                else
                    echo 'password    '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
                fi
            fi
        fi
    fi
else
    if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "/etc/pam.d/password-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/password-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "/etc/pam.d/password-auth"
        else
            LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/password-auth" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' "/etc/pam.d/password-auth"
            else
                echo 'password    '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' >> "/etc/pam.d/password-auth"
            fi
        fi
    fi
fi

PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
    regex="^\s*remember\s*="
    line="remember = $var_password_pam_remember"
    if ! grep -q $regex $PWHISTORY_CONF; then
        echo $line >> $PWHISTORY_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then
            
            if ! authselect check; then
            echo "
            authselect integrity check failed. Remediation aborted!
            This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
            It is not recommended to manually edit the PAM files when authselect tool is available.
            In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            exit 1
            fi

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"
                
                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done
                
                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi
        
    if grep -qP '^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks 's/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then
            
            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi
else
    PAM_FILE_PATH="/etc/pam.d/password-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "$PAM_FILE_PATH"
        else
            echo 'password    '"requisite"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
        fi
    fi
    # Check the option
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*\sremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks '/\s*password\s+'"requisite"'\s+pam_pwhistory.so.*/ s/$/ remember='"$var_password_pam_remember"'/' "$PAM_FILE_PATH"
    else
        sed -i -E --follow-symlinks 's/(\s*password\s+'"requisite"'\s+pam_pwhistory.so\s+.*)('"remember"'=)[[:alnum:]]+\s*(.*)/\1\2'"$var_password_pam_remember"' \3/' "$PAM_FILE_PATH"
    fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
fi

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

Rule   Limit Password Reuse: system-auth   [ref]

Do not allow users to reuse recent passwords. This can be accomplished by using the remember option for the pam_pwhistory PAM module.

On systems with newer versions of authselect, the pam_pwhistory PAM module can be enabled via authselect feature:
authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile.

Newer systems also have the /etc/security/pwhistory.conf file for setting pam_pwhistory module options. This file should be used whenever available. Otherwise, the pam_pwhistory module options can be set in PAM files.

The value for remember option must be equal or greater than 4
Warning:  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report.
Warning:  Newer versions of authselect contain an authselect feature to easily and properly enable pam_pwhistory.so module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files.
Rationale:
Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.8
disaCCI-000200
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(f), IA-5(1)(e)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.5
os-srgSRG-OS-000077-GPOS-00045
cis5.3.3.3.1
pcidss48.3.7

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_remember # promote to variable
  set_fact:
    var_password_pam_remember: !!str 4
  tags:
    - always
- name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
  set_fact:
    var_password_pam_remember_control_flag: !!str requisite,required
  tags:
    - always

- name: 'Limit Password Reuse: system-auth - Check if system relies on authselect
    tool'
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Collect the available authselect features'
  ansible.builtin.command:
    cmd: authselect list-features minimal
  register: result_authselect_available_features
  changed_when: false
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Enable pam_pwhistory.so using authselect
    feature'
  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
    failed_when: false

  - name: 'Limit Password Reuse: system-auth - Informative message based on the authselect
      integrity check result'
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: 'Limit Password Reuse: system-auth - Get authselect current features'
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: 'Limit Password Reuse: system-auth - Ensure "with-pwhistory" feature is
      enabled using authselect tool'
    ansible.builtin.command:
      cmd: authselect enable-feature with-pwhistory
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-pwhistory")

  - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  - result_authselect_available_features.stdout is search("with-pwhistory")
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Enable pam_pwhistory.so in appropriate
    PAM files'
  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
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile is
      used if authselect is present'
    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
      failed_when: false

    - name: 'Limit Password Reuse: system-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: 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 authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: 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 authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: 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.split(",")[0]
        }}' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: 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.split(",")[0] }} \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.split(",")[0]
          }}    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - |
    (result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - Check the presence of /etc/security/pwhistory.conf
    file'
  ansible.builtin.stat:
    path: /etc/security/pwhistory.conf
  register: result_pwhistory_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - pam_pwhistory.so parameters are configured
    in /etc/security/pwhistory.conf file'
  block:

  - name: 'Limit Password Reuse: system-auth - Ensure the pam_pwhistory.so remember
      parameter in /etc/security/pwhistory.conf'
    ansible.builtin.lineinfile:
      path: /etc/security/pwhistory.conf
      regexp: ^\s*remember\s*=
      line: remember = {{ var_password_pam_remember }}
      state: present

  - name: 'Limit Password Reuse: system-auth - Ensure the pam_pwhistory.so remember
      parameter is removed from PAM files'
    block:

    - 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
      register: result_pam_file_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
          tool'
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile
          is used if authselect is present'
        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
          failed_when: false

        - name: 'Limit Password Reuse: system-auth - Informative message based on
            the authselect integrity check result'
          ansible.builtin.assert:
            that:
            - result_authselect_check_cmd.rc == 0
            fail_msg:
            - authselect integrity check failed. Remediation aborted!
            - This remediation could not be applied because an authselect profile
              was not selected or the selected profile is not intact.
            - It is not recommended to manually edit the PAM files when authselect
              tool is available.
            - In cases where the default authselect profile does not cover a specific
              demand, a custom authselect profile is recommended.
            success_msg:
            - authselect integrity check passed

        - name: 'Limit Password Reuse: 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 authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: 'Limit Password Reuse: 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 authselect changes are
            applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: 'Limit Password Reuse: 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 - Ensure the "remember" option from
          "pam_pwhistory.so" is not present in {{ pam_file_path }}'
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal

      - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: 'Limit Password Reuse: system-auth - pam_pwhistory.so parameters are configured
    in PAM files'
  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
      tool'
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: 'Limit Password Reuse: system-auth - Ensure authselect custom profile is
      used if authselect is present'
    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
      failed_when: false

    - name: 'Limit Password Reuse: system-auth - Informative message based on the
        authselect integrity check result'
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: 'Limit Password Reuse: 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 authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: 'Limit Password Reuse: 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 authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: 'Limit Password Reuse: 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+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: 'Limit Password Reuse: 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: \1requisite \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 }}'
        line: password    requisite    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: 'Limit Password Reuse: 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+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_remember_option_present

  - name: 'Limit Password Reuse: 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+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so.*)
      line: \1 remember={{ var_password_pam_remember }}
      state: present
    register: result_pam_remember_add
    when:
    - result_pam_module_remember_option_present.found == 0

  - name: 'Limit Password Reuse: 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+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]+\s*(.*)
      line: \1\2={{ var_password_pam_remember }} \3
    register: result_pam_remember_edit
    when:
    - result_pam_module_remember_option_present.found > 0

  - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
      (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - not result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_pwhistory_remember_system_auth
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

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

var_password_pam_remember='4'
var_password_pam_remember_control_flag='requisite,required'


var_password_pam_remember_control_flag="$(echo $var_password_pam_remember_control_flag | cut -d \, -f 1)"

if [ -f /usr/bin/authselect ]; then
    if authselect list-features minimal | grep -q with-pwhistory; then
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi
        authselect enable-feature with-pwhistory

        authselect apply-changes -b
    else
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
        if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "$PAM_FILE_PATH"
            else
                LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
                if [ ! -z $LAST_MATCH_LINE ]; then
                    sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' "$PAM_FILE_PATH"
                else
                    echo 'password    '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
                fi
            fi
        fi
    fi
else
    if ! grep -qP '^\s*password\s+'"$var_password_pam_remember_control_flag"'\s+pam_pwhistory.so\s*.*' "/etc/pam.d/system-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/system-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"$var_password_pam_remember_control_flag"' \2/' "/etc/pam.d/system-auth"
        else
            LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/system-auth" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' "/etc/pam.d/system-auth"
            else
                echo 'password    '"$var_password_pam_remember_control_flag"'    pam_pwhistory.so' >> "/etc/pam.d/system-auth"
            fi
        fi
    fi
fi

PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
    regex="^\s*remember\s*="
    line="remember = $var_password_pam_remember"
    if ! grep -q $regex $PWHISTORY_CONF; then
        echo $line >> $PWHISTORY_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
    fi
    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then
            
            if ! authselect check; then
            echo "
            authselect integrity check failed. Remediation aborted!
            This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
            It is not recommended to manually edit the PAM files when authselect tool is available.
            In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            exit 1
            fi

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"
                
                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done
                
                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi
        
    if grep -qP '^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks 's/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then
            
            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
else
    PAM_FILE_PATH="/etc/pam.d/system-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "$PAM_FILE_PATH"
        else
            echo 'password    '"requisite"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
        fi
    fi
    # Check the option
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*\sremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks '/\s*password\s+'"requisite"'\s+pam_pwhistory.so.*/ s/$/ remember='"$var_password_pam_remember"'/' "$PAM_FILE_PATH"
    else
        sed -i -E --follow-symlinks 's/(\s*password\s+'"requisite"'\s+pam_pwhistory.so\s+.*)('"remember"'=)[[:alnum:]]+\s*(.*)/\1\2'"$var_password_pam_remember"' \3/' "$PAM_FILE_PATH"
    fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
fi

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

Rule   Limit Password Reuse   [ref]

Do not allow users to reuse recent passwords. This can be accomplished by using the remember option for the pam_unix or pam_pwhistory PAM modules.
Warning:  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report.
Warning:  Newer versions of authselect contain an authselect feature to easily and properly enable pam_pwhistory.so module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files.
Rationale:
Preventing re-use of previous passwords helps ensure that a compromised password is not re-used by a user.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.8
disaCCI-000200
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(f), IA-5(1)(e)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.5
os-srgSRG-OS-000077-GPOS-00045
anssiR31
pcidss48.3.7

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_unix_remember # promote to variable
  set_fact:
    var_password_pam_unix_remember: !!str 4
  tags:
    - always

- name: Limit Password Reuse - 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:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - Collect the available authselect features
  ansible.builtin.command:
    cmd: authselect list-features minimal
  register: result_authselect_available_features
  changed_when: false
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - Enable pam_pwhistory.so using authselect feature
  block:

  - name: Limit Password Reuse - Check integrity of authselect current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Limit Password Reuse - Informative message based on the authselect integrity
      check result
    ansible.builtin.assert:
      that:
      - result_authselect_check_cmd.rc == 0
      fail_msg:
      - authselect integrity check failed. Remediation aborted!
      - This remediation could not be applied because an authselect profile was not
        selected or the selected profile is not intact.
      - It is not recommended to manually edit the PAM files when authselect tool
        is available.
      - In cases where the default authselect profile does not cover a specific demand,
        a custom authselect profile is recommended.
      success_msg:
      - authselect integrity check passed

  - name: Limit Password Reuse - Get authselect current features
    ansible.builtin.shell:
      cmd: authselect current | tail -n+3 | awk '{ print $2 }'
    register: result_authselect_features
    changed_when: false
    when:
    - result_authselect_check_cmd is success

  - name: Limit Password Reuse - Ensure "with-pwhistory" feature is enabled using
      authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature with-pwhistory
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("with-pwhistory")

  - name: Limit Password Reuse - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - '"pam" in ansible_facts.packages'
  - result_authselect_present.stat.exists
  - result_authselect_available_features.stdout is search("with-pwhistory")
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - Enable pam_pwhistory.so in appropriate PAM files
  block:

  - name: Limit Password Reuse - 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 - Check if system relies on authselect tool
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: Limit Password Reuse - Ensure authselect custom profile is used if authselect
      is present
    block:

    - name: Limit Password Reuse - Check integrity of authselect current profile
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: Limit Password Reuse - Informative message based on the authselect integrity
        check result
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: Limit Password Reuse - 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 - 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 - 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 - 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 - 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 - 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 - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: Limit Password Reuse - 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 - 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 - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: Limit Password Reuse - 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 - Check if expected PAM module line is present in {{
      pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: Limit Password Reuse - Include or update the PAM module line in {{ pam_file_path
      }}
    block:

    - name: Limit Password Reuse - 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 - Ensure the correct control for the required PAM
        module line in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1requisite \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: Limit Password Reuse - 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    requisite    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: Limit Password Reuse - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0
  when:
  - '"pam" in ansible_facts.packages'
  - |
    (result_authselect_available_features.stdout is defined and result_authselect_available_features.stdout is not search("with-pwhistory")) or result_authselect_available_features is not defined
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - Check the presence of /etc/security/pwhistory.conf
    file
  ansible.builtin.stat:
    path: /etc/security/pwhistory.conf
  register: result_pwhistory_conf_check
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - pam_pwhistory.so parameters are configured in /etc/security/pwhistory.conf
    file
  block:

  - name: Limit Password Reuse - Ensure the pam_pwhistory.so remember parameter in
      /etc/security/pwhistory.conf
    ansible.builtin.lineinfile:
      path: /etc/security/pwhistory.conf
      regexp: ^\s*remember\s*=
      line: remember = {{ var_password_pam_unix_remember }}
      state: present

  - name: Limit Password Reuse - Ensure the pam_pwhistory.so remember parameter is
      removed from PAM files
    block:

    - name: Limit Password Reuse - 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: Limit Password Reuse - Check the proper remediation for the system
      block:

      - name: Limit Password Reuse - 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 - Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Limit Password Reuse - Ensure authselect custom profile is used if authselect
          is present
        block:

        - name: Limit Password Reuse - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          failed_when: false

        - name: Limit Password Reuse - Informative message based on the authselect
            integrity check result
          ansible.builtin.assert:
            that:
            - result_authselect_check_cmd.rc == 0
            fail_msg:
            - authselect integrity check failed. Remediation aborted!
            - This remediation could not be applied because an authselect profile
              was not selected or the selected profile is not intact.
            - It is not recommended to manually edit the PAM files when authselect
              tool is available.
            - In cases where the default authselect profile does not cover a specific
              demand, a custom authselect profile is recommended.
            success_msg:
            - authselect integrity check passed

        - name: Limit Password Reuse - 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 - 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 - 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 - 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 - 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 - 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 - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Limit Password Reuse - 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 - 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 - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Limit Password Reuse - 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 - Ensure the "remember" option from "pam_pwhistory.so"
          is not present in {{ pam_file_path }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwhistory.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal

      - name: Limit Password Reuse - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - result_pam_file_present.stat.exists
  when:
  - '"pam" in ansible_facts.packages'
  - result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

- name: Limit Password Reuse - pam_pwhistory.so parameters are configured in PAM files
  block:

  - name: Limit Password Reuse - 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 - Check if system relies on authselect tool
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: Limit Password Reuse - Ensure authselect custom profile is used if authselect
      is present
    block:

    - name: Limit Password Reuse - Check integrity of authselect current profile
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: Limit Password Reuse - Informative message based on the authselect integrity
        check result
      ansible.builtin.assert:
        that:
        - result_authselect_check_cmd.rc == 0
        fail_msg:
        - authselect integrity check failed. Remediation aborted!
        - This remediation could not be applied because an authselect profile was
          not selected or the selected profile is not intact.
        - It is not recommended to manually edit the PAM files when authselect tool
          is available.
        - In cases where the default authselect profile does not cover a specific
          demand, a custom authselect profile is recommended.
        success_msg:
        - authselect integrity check passed

    - name: Limit Password Reuse - 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 - 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 - 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 - 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 - 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 - 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 - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      - authselect_custom_profile is not match(authselect_current_profile)

    - name: Limit Password Reuse - 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 - 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 - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped

    - name: Limit Password Reuse - 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 - Check if expected PAM module line is present in {{
      pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: Limit Password Reuse - Include or update the PAM module line in {{ pam_file_path
      }}
    block:

    - name: Limit Password Reuse - 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 - Ensure the correct control for the required PAM
        module line in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: ^(\s*password\s+).*(\bpam_pwhistory.so.*)
        replace: \1requisite \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: Limit Password Reuse - Ensure the required PAM module line is included
        in {{ pam_file_path }}
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        line: password    requisite    pam_pwhistory.so
      register: result_pam_module_add
      when:
      - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
        > 1

    - name: Limit Password Reuse - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: Limit Password Reuse - Check if the required PAM module option is present
      in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s*.*\sremember\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_remember_option_present

  - name: Limit Password Reuse - Ensure the "remember" PAM option for "pam_pwhistory.so"
      is included in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so.*)
      line: \1 remember={{ var_password_pam_unix_remember }}
      state: present
    register: result_pam_remember_add
    when:
    - result_pam_module_remember_option_present.found == 0

  - name: Limit Password Reuse - Ensure the required value for "remember" PAM option
      from "pam_pwhistory.so" in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ 'requisite' | regex_escape() }}\s+pam_pwhistory.so\s+.*)(remember)=[0-9a-zA-Z]+\s*(.*)
      line: \1\2={{ var_password_pam_unix_remember }} \3
    register: result_pam_remember_edit
    when:
    - result_pam_module_remember_option_present.found > 0

  - name: Limit Password Reuse - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
      (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - not result_pwhistory_conf_check.stat.exists
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-171-3.5.8
  - NIST-800-53-IA-5(1)(e)
  - NIST-800-53-IA-5(f)
  - PCI-DSS-Req-8.2.5
  - PCI-DSSv4-8.3.7
  - accounts_password_pam_unix_remember
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed

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

var_password_pam_unix_remember='4'






if [ -f /usr/bin/authselect ]; then
    if authselect list-features minimal | grep -q with-pwhistory; then
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi
        authselect enable-feature with-pwhistory

        authselect apply-changes -b
    else
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
        if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "$PAM_FILE_PATH"
            else
                LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
                if [ ! -z $LAST_MATCH_LINE ]; then
                    sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"requisite"'    pam_pwhistory.so' "$PAM_FILE_PATH"
                else
                    echo 'password    '"requisite"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
                fi
            fi
        fi
    fi
else
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "/etc/pam.d/system-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "/etc/pam.d/system-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "/etc/pam.d/system-auth"
        else
            LAST_MATCH_LINE=$(grep -nP "^password.*requisite.*pam_pwquality\.so" "/etc/pam.d/system-auth" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE' a password     '"requisite"'    pam_pwhistory.so' "/etc/pam.d/system-auth"
            else
                echo 'password    '"requisite"'    pam_pwhistory.so' >> "/etc/pam.d/system-auth"
            fi
        fi
    fi
fi

PWHISTORY_CONF="/etc/security/pwhistory.conf"
if [ -f $PWHISTORY_CONF ]; then
    regex="^\s*remember\s*="
    line="remember = $var_password_pam_unix_remember"
    if ! grep -q $regex $PWHISTORY_CONF; then
        echo $line >> $PWHISTORY_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_unix_remember"'|g' $PWHISTORY_CONF
    fi
    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then
            
            if ! authselect check; then
            echo "
            authselect integrity check failed. Remediation aborted!
            This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
            It is not recommended to manually edit the PAM files when authselect tool is available.
            In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            exit 1
            fi

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"
                
                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done
                
                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi
        
    if grep -qP '^\s*password\s.*\bpam_pwhistory.so\s.*\bremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks 's/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g' "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then
            
            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
else
    PAM_FILE_PATH="/etc/pam.d/system-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*' "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1'"requisite"' \2/' "$PAM_FILE_PATH"
        else
            echo 'password    '"requisite"'    pam_pwhistory.so' >> "$PAM_FILE_PATH"
        fi
    fi
    # Check the option
    if ! grep -qP '^\s*password\s+'"requisite"'\s+pam_pwhistory.so\s*.*\sremember\b' "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks '/\s*password\s+'"requisite"'\s+pam_pwhistory.so.*/ s/$/ remember='"$var_password_pam_unix_remember"'/' "$PAM_FILE_PATH"
    else
        sed -i -E --follow-symlinks 's/(\s*password\s+'"requisite"'\s+pam_pwhistory.so\s+.*)('"remember"'=)[[:alnum:]]+\s*(.*)/\1\2'"$var_password_pam_unix_remember"' \3/' "$PAM_FILE_PATH"
    fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
fi

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

Rule   Lock Accounts After Failed Password Attempts   [ref]

This rule configures the system to lock out accounts after a number of incorrect login attempts using pam_faillock.so. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. Ensure that the file /etc/security/faillock.conf contains the following entry: deny = <count> Where count should be less than or equal to 10 and greater than 0. 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.
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:
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny
References:
cis-csc1, 12, 15, 16
cjis5.5.3
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.8
disaCCI-000044, CCI-002236, CCI-002237, CCI-002238
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(a)
nist-csfPR.AC-7
osppFIA_AFL.1
pcidssReq-8.1.6
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssiR31
cis5.3.3.1.1
pcidss48.3.4

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Remediation where authselect
    tool is present
  block:

  - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Remediation where authselect
    tool is not present
  block:

  - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
  set_fact:
    var_accounts_passwords_pam_faillock_deny: !!str 10
  tags:
    - always

- name: Lock Accounts After Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter in /etc/security/faillock.conf
  ansible.builtin.lineinfile:
    path: /etc/security/faillock.conf
    regexp: ^\s*deny\s*=
    line: deny = {{ var_accounts_passwords_pam_faillock_deny }}
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter not in PAM files
  block:

  - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - Ensure the "deny" option
        from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bdeny\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Lock Accounts After Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Lock Accounts After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - 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 After Failed Password Attempts - Ensure the "deny" option
        from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bdeny\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Lock Accounts After Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Lock Accounts After Failed Password Attempts - Ensure the pam_faillock.so
    deny parameter in PAM files
  block:

  - name: Lock Accounts After Failed Password Attempts - Check if pam_faillock.so
      deny parameter is already enabled in pam files
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail).*deny
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_deny_parameter_is_present

  - name: Lock Accounts After Failed Password Attempts - Ensure the inclusion of pam_faillock.so
      preauth deny 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 deny={{ var_accounts_passwords_pam_faillock_deny }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found == 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the inclusion of pam_faillock.so
      authfail deny 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 deny={{ var_accounts_passwords_pam_faillock_deny }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found == 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the desired value
      for pam_faillock.so preauth deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)(deny)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_deny }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found > 0

  - name: Lock Accounts After Failed Password Attempts - Ensure the desired value
      for pam_faillock.so authfail deny parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(deny)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_deny }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_deny_parameter_is_present.found > 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_faillock_conf_check.stat.exists
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(a)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_deny
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_passwords_pam_faillock_deny='10'


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*deny\s*="
    line="deny = $var_accounts_passwords_pam_faillock_deny"
    if ! grep -q $regex $FAILLOCK_CONF; then
        echo $line >> $FAILLOCK_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(deny\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_deny"'|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.*\bdeny\b' "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks 's/(.*auth.*pam_faillock.so.*)\bdeny\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).*deny' "$pam_file"; then
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
        else
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"deny"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
        fi
    done
fi

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

Rule   Set Lockout Time for Failed Password Attempts   [ref]

This rule configures the system to lock out accounts during a specified time period after a number of incorrect login attempts using pam_faillock.so. Ensure that the file /etc/security/faillock.conf contains the following entry: unlock_time=<interval-in-seconds> where interval-in-seconds is 1800 or greater. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. In order to avoid any errors when manually editing these files, it is recommended to use the appropriate tools, such as authselect or authconfig, depending on the OS version. If unlock_time is set to 0, manual intervention by an administrator is required to unlock a user. This should be done using the faillock tool.
Warning:  If the system supports the new /etc/security/faillock.conf file but the pam_faillock.so parameters are defined directly in /etc/pam.d/system-auth and /etc/pam.d/password-auth, the remediation will migrate the unlock_time parameter to /etc/security/faillock.conf to ensure compatibility with authselect tool. The parameters deny and fail_interval, if used, also have to be migrated by their respective remediation.
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:
By limiting the number of failed logon attempts the risk of unauthorized system access via user password guessing, otherwise known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time
References:
cis-csc1, 12, 15, 16
cjis5.5.3
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.8
disaCCI-000044, CCI-002236, CCI-002237, CCI-002238
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(b)
nist-csfPR.AC-7
osppFIA_AFL.1
pcidssReq-8.1.7
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssiR31
cis5.3.3.1.2
pcidss48.3.4

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Remediation where authselect
    tool is present
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
      current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Remediation where authselect
    tool is not present
  block:

  - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
  set_fact:
    var_accounts_passwords_pam_faillock_unlock_time: !!str 1800
  tags:
    - always

- name: Set Lockout Time for Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter in /etc/security/faillock.conf
  ansible.builtin.lineinfile:
    path: /etc/security/faillock.conf
    regexp: ^\s*unlock_time\s*=
    line: unlock_time = {{ var_accounts_passwords_pam_faillock_unlock_time }}
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - result_faillock_conf_check.stat.exists
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter not in PAM files
  block:

  - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Set Lockout Time for Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/system-auth

    - name: Set Lockout Time for Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - Ensure the "unlock_time"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bunlock_time\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - Check the proper remediation
      for the system
    block:

    - name: Set Lockout Time for Failed Password Attempts - Define the PAM file to
        be edited as a local fact
      ansible.builtin.set_fact:
        pam_file_path: /etc/pam.d/password-auth

    - name: Set Lockout Time for Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present

    - name: Set Lockout Time for Failed Password Attempts - Ensure authselect custom
        profile is used if authselect is present
      block:

      - name: Set Lockout Time for Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        failed_when: false

      - name: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - 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: Set Lockout Time for Failed Password Attempts - Ensure the "unlock_time"
        option from "pam_faillock.so" is not present in {{ pam_file_path }}
      ansible.builtin.replace:
        dest: '{{ pam_file_path }}'
        regexp: (.*auth.*pam_faillock.so.*)\bunlock_time\b=?[0-9a-zA-Z]*(.*)
        replace: \1\2
      register: result_pam_option_removal

    - name: Set Lockout Time for Failed Password Attempts - 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:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts - Ensure the pam_faillock.so
    unlock_time parameter in PAM files
  block:

  - name: Set Lockout Time for Failed Password Attempts - Check if pam_faillock.so
      unlock_time parameter is already enabled in pam files
    ansible.builtin.lineinfile:
      path: /etc/pam.d/system-auth
      regexp: .*auth.*pam_faillock\.so (preauth|authfail).*unlock_time
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_faillock_unlock_time_parameter_is_present

  - name: Set Lockout Time for Failed Password Attempts - Ensure the inclusion of
      pam_faillock.so preauth unlock_time 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 unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time
        }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the inclusion of
      pam_faillock.so authfail unlock_time 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 unlock_time={{ var_accounts_passwords_pam_faillock_unlock_time
        }}
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found == 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the desired value
      for pam_faillock.so preauth unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)(unlock_time)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_unlock_time }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found > 0

  - name: Set Lockout Time for Failed Password Attempts - Ensure the desired value
      for pam_faillock.so authfail unlock_time parameter in auth section
    ansible.builtin.lineinfile:
      path: '{{ item }}'
      backrefs: true
      regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(unlock_time)=[0-9]+(.*)
      line: \1required\3\4={{ var_accounts_passwords_pam_faillock_unlock_time }}\5
      state: present
    loop:
    - /etc/pam.d/system-auth
    - /etc/pam.d/password-auth
    when:
    - result_pam_faillock_unlock_time_parameter_is_present.found > 0
  when:
  - '"pam" in ansible_facts.packages'
  - not result_faillock_conf_check.stat.exists
  tags:
  - CJIS-5.5.3
  - NIST-800-171-3.1.8
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_faillock_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_passwords_pam_faillock_unlock_time='1800'


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*unlock_time\s*="
    line="unlock_time = $var_accounts_passwords_pam_faillock_unlock_time"
    if ! grep -q $regex $FAILLOCK_CONF; then
        echo $line >> $FAILLOCK_CONF
    else
        sed -i --follow-symlinks 's|^\s*\(unlock_time\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_unlock_time"'|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.*\bunlock_time\b' "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks 's/(.*auth.*pam_faillock.so.*)\bunlock_time\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).*unlock_time' "$pam_file"; then
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*silent.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
        else
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*silent.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
            sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"unlock_time"'=\)[0-9]\+\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
        fi
    done
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Set Password Quality Requirements   Group contains 1 group and 3 rules
[ref]   The default pam_pwquality PAM module provides strength checking for passwords. It performs a number of checks, such as making sure passwords are not similar to dictionary words, are of at least a certain length, are not the previous password reversed, and are not simply a change of case from the previous password. It can also require passwords to be in certain character classes. The pam_pwquality module is the preferred way of configuring password requirements.

The man pages pam_pwquality(8) provide information on the capabilities and configuration of each.
Group   Set Password Quality Requirements with pam_pwquality   Group contains 3 rules
[ref]   The pam_pwquality PAM module can be configured to meet requirements for a variety of policies.

For example, to configure pam_pwquality to require at least one uppercase character, lowercase character, digit, and other (special) character, make sure that pam_pwquality exists in /etc/pam.d/system-auth:
password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
If no such line exists, add one as the first line of the password section in /etc/pam.d/system-auth. Next, modify the settings in /etc/security/pwquality.conf to match the following:
difok = 4
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
maxrepeat = 3
The arguments can be modified to ensure compliance with your organization's security policy. Discussion of each parameter follows.

Rule   Ensure PAM Enforces Password Requirements - Minimum Digit Characters   [ref]

The pam_pwquality module's dcredit parameter controls requirements for usage of digits in a password. When set to a negative number, any password will be required to contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional length credit for each digit. Modify the dcredit setting in /etc/security/pwquality.conf to require the use of a digit in passwords.
Rationale:
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.

Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised. Requiring digits makes password guessing attacks more difficult by ensuring a larger search space.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit
References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
disaCCI-000194
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
osppFMT_SMF_EXT.1
pcidssReq-8.2.3
os-srgSRG-OS-000071-GPOS-00039
anssiR31
pcidss48.3.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_dcredit # promote to variable
  set_fact:
    var_password_pam_dcredit: !!str -1
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
    PAM variable dcredit is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*dcredit
    line: dcredit = {{ var_password_pam_dcredit }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_password_pam_dcredit='-1'






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^dcredit")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_dcredit"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^dcredit\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^dcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

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

Rule   Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters   [ref]

The pam_pwquality module's lcredit parameter controls requirements for usage of lowercase letters in a password. When set to a negative number, any password will be required to contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional length credit for each lowercase character. Modify the lcredit setting in /etc/security/pwquality.conf to require the use of a lowercase character in passwords.
Rationale:
Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks.
Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possble combinations that need to be tested before the password is compromised. Requiring a minimum number of lowercase characters makes password guessing attacks more difficult by ensuring a larger search space.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit
References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
disaCCI-000193
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
osppFMT_SMF_EXT.1
pcidssReq-8.2.3
os-srgSRG-OS-000070-GPOS-00038
anssiR31
pcidss48.3.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_lcredit # promote to variable
  set_fact:
    var_password_pam_lcredit: !!str -1
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
    Ensure PAM variable lcredit is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*lcredit
    line: lcredit = {{ var_password_pam_lcredit }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_password_pam_lcredit='-1'






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^lcredit")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_lcredit"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^lcredit\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^lcredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

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

Rule   Ensure PAM Enforces Password Requirements - Minimum Length   [ref]

The pam_pwquality module's minlen parameter controls requirements for minimum characters required in a password. Add minlen=12 after pam_pwquality to set minimum password length requirements.
Rationale:
The shorter the password, the lower the number of possible combinations that need to be tested before the password is compromised.
Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromise the password.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.1.1
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
disaCCI-000205
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
osppFMT_SMF_EXT.1
pcidssReq-8.2.3
os-srgSRG-OS-000078-GPOS-00046
anssiR31, R68
cis5.3.3.2.2
pcidss48.3.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_minlen # promote to variable
  set_fact:
    var_password_pam_minlen: !!str 12
  tags:
    - always

- name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure PAM variable
    minlen is set accordingly
  ansible.builtin.lineinfile:
    create: true
    dest: /etc/security/pwquality.conf
    regexp: ^#?\s*minlen
    line: minlen = {{ var_password_pam_minlen }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.1.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(4)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.6
  - accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q pam; then

var_password_pam_minlen='12'






# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^minlen")

# shellcheck disable=SC2059
printf -v formatted_output "%s = %s" "$stripped_key" "$var_password_pam_minlen"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^minlen\\>" "/etc/security/pwquality.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^minlen\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
else
    if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
    fi
    printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Set Password Hashing Algorithm   Group contains 3 rules
[ref]   The system's default algorithm for storing password hashes in /etc/shadow is SHA-512. This can be configured in several locations.

Rule   Set Password Hashing Algorithm in /etc/libuser.conf   [ref]

In /etc/libuser.conf, add or correct the following line in its [defaults] section to ensure the system will use the SHA-512 algorithm for password hashing:
crypt_style = sha512
Rationale:
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kepy in plain text.

This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the crypt_style configuration option ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_libuserconf
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
disaCCI-000196
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041
cis5.4.1.4
pcidss48.3.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_libuserconf

- name: Set Password Hashing Algorithm in /etc/libuser.conf
  lineinfile:
    dest: /etc/libuser.conf
    insertafter: ^\s*\[defaults]
    regexp: ^#?crypt_style
    line: crypt_style = sha512
    state: present
    create: true
  when: '"libuser" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_libuserconf

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

LIBUSER_CONF="/etc/libuser.conf"
CRYPT_STYLE_REGEX='[[:space:]]*\[defaults](.*(\n)+)+?[[:space:]]*crypt_style[[:space:]]*'

# Try find crypt_style in [defaults] section. If it is here, then change algorithm to sha512.
# If it isn't here, then add it to [defaults] section.
if grep -qzosP $CRYPT_STYLE_REGEX $LIBUSER_CONF ; then
        sed -i "s/\(crypt_style[[:space:]]*=[[:space:]]*\).*/\1sha512/g" $LIBUSER_CONF
elif grep -qs "\[defaults]" $LIBUSER_CONF ; then
        sed -i "/[[:space:]]*\[defaults]/a crypt_style = sha512" $LIBUSER_CONF
else
        echo -e "[defaults]\ncrypt_style = sha512" >> $LIBUSER_CONF
fi

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

Rule   Set Password Hashing Algorithm in /etc/login.defs   [ref]

In /etc/login.defs, add or correct the following line to ensure the system will use SHA512 as the hashing algorithm:
ENCRYPT_METHOD SHA512
         
Rationale:
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

Using a stronger hashing algorithm makes password cracking attacks more difficult.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_logindefs
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
disaCCI-000196
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041
cis5.4.1.4
pcidss48.3.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_logindefs
- name: XCCDF Value var_password_hashing_algorithm # promote to variable
  set_fact:
    var_password_hashing_algorithm: !!str SHA512
  tags:
    - always

- name: Set Password Hashing Algorithm in /etc/login.defs
  lineinfile:
    dest: /etc/login.defs
    regexp: ^#?ENCRYPT_METHOD
    line: ENCRYPT_METHOD {{ var_password_hashing_algorithm }}
    state: present
    create: true
  when: '"shadow-utils" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_logindefs

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

var_password_hashing_algorithm='SHA512'

# Strip any search characters in the key arg so that the key can be replaced without
# adding any search characters to the config file.
stripped_key=$(sed 's/[\^=\$,;+]*//g' <<< "^ENCRYPT_METHOD")

# shellcheck disable=SC2059
printf -v formatted_output "%s %s" "$stripped_key" "$var_password_hashing_algorithm"

# If the key exists, change it. Otherwise, add it to the config_file.
# We search for the key string followed by a word boundary (matched by \>),
# so if we search for 'setting', 'setting2' won't match.
if LC_ALL=C grep -q -m 1 -i -e "^ENCRYPT_METHOD\\>" "/etc/login.defs"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^ENCRYPT_METHOD\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
else
    if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
    fi
    printf '%s\n' "$formatted_output" >> "/etc/login.defs"
fi

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

Rule   Set PAM''s Password Hashing Algorithm   [ref]

The PAM system service can be configured to only store encrypted representations of passwords. In "/etc/pam.d/system-auth", the password section of the file controls which PAM modules execute during a password change. Set the pam_unix.so module in the password section to include the argument sha512, as shown below:
password    sufficient    pam_unix.so sha512 other arguments...
         

This will help ensure when local users change their passwords, hashes for the new passwords will be generated using the SHA-512 algorithm. This is the default.
Rationale:
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kepy in plain text.

This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the crypt_style configuration option ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_systemauth
References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
disaCCI-000196, CCI-000803
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism0418, 1055, 1402
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
anssiR68
cis5.3.3.4.3
pcidss48.3.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - set_password_hashing_algorithm_systemauth

- name: Set PAM's Password Hashing Algorithm - Check if /etc/pam.d/system-auth file
    is present
  ansible.builtin.stat:
    path: /etc/pam.d/system-auth
  register: result_pam_file_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - set_password_hashing_algorithm_systemauth

- name: Set PAM's Password Hashing Algorithm - Check the proper remediation for the
    system
  block:

  - name: Set PAM's Password Hashing Algorithm - Define the PAM file to be edited
      as a local fact
    ansible.builtin.set_fact:
      pam_file_path: /etc/pam.d/system-auth

  - name: Set PAM's Password Hashing Algorithm - Check if system relies on authselect
      tool
    ansible.builtin.stat:
      path: /usr/bin/authselect
    register: result_authselect_present

  - name: Set PAM's Password Hashing Algorithm - Ensure authselect custom profile
      is used if authselect is present
    block:

    - name: Set PAM's Password Hashing Algorithm - Check integrity of authselect current
        profile
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      failed_when: false

    - name: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - 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: Set PAM's Password Hashing Algorithm - Check if expected PAM module line
      is present in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'sufficient' | regex_escape() }}\s+pam_unix.so\s*.*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_present

  - name: Set PAM's Password Hashing Algorithm - Include or update the PAM module
      line in {{ pam_file_path }}
    block:

    - name: Set PAM's Password Hashing Algorithm - 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_unix.so\s*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_other_control_present

    - name: Set PAM's Password Hashing Algorithm - 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_unix.so.*)
        replace: \1sufficient \2
      register: result_pam_module_edit
      when:
      - result_pam_line_other_control_present.found == 1

    - name: Set PAM's Password Hashing Algorithm - Ensure the required PAM module
        line is included in {{ pam_file_path }}
      ansible.builtin.lineinfile:
        dest: '{{ pam_file_path }}'
        line: password    sufficient    pam_unix.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: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - result_authselect_present is defined
      - result_authselect_present.stat.exists
      - |-
        (result_pam_module_add is defined and result_pam_module_add.changed)
         or (result_pam_module_edit is defined and result_pam_module_edit.changed)
    when:
    - result_pam_line_present.found is defined
    - result_pam_line_present.found == 0

  - name: Set PAM's Password Hashing Algorithm - Check if the required PAM module
      option is present in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      regexp: ^\s*password\s+{{ 'sufficient' | regex_escape() }}\s+pam_unix.so\s*.*\ssha512\b
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_module_sha512_option_present

  - name: Set PAM's Password Hashing Algorithm - Ensure the "sha512" PAM option for
      "pam_unix.so" is included in {{ pam_file_path }}
    ansible.builtin.lineinfile:
      path: '{{ pam_file_path }}'
      backrefs: true
      regexp: ^(\s*password\s+{{ 'sufficient' | regex_escape() }}\s+pam_unix.so.*)
      line: \1 sha512
      state: present
    register: result_pam_sha512_add
    when:
    - result_pam_module_sha512_option_present.found == 0

  - name: Set PAM's Password Hashing Algorithm - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present.stat.exists
    - |-
      (result_pam_sha512_add is defined and result_pam_sha512_add.changed)
       or (result_pam_sha512_edit is defined and result_pam_sha512_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_file_present.stat.exists
  tags:
  - CJIS-5.6.2.2
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - set_password_hashing_algorithm_systemauth

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

if [ -e "/etc/pam.d/system-auth" ] ; then
    PAM_FILE_PATH="/etc/pam.d/system-auth"
    if [ -f /usr/bin/authselect ]; then
        
        if ! authselect check; then
        echo "
        authselect integrity check failed. Remediation aborted!
        This remediation could not be applied because an authselect profile was not selected or the selected profile is not intact.
        It is not recommended to manually edit the PAM files when authselect tool is available.
        In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
        exit 1
        fi

        CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
        # If not already in use, a custom profile is created preserving the enabled features.
        if [[ ! $CURRENT_PROFILE == custom/* ]]; then
            ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
            authselect create-profile hardening -b $CURRENT_PROFILE
            CURRENT_PROFILE="custom/hardening"
            
            authselect apply-changes -b --backup=before-hardening-custom-profile
            authselect select $CURRENT_PROFILE
            for feature in $ENABLED_FEATURES; do
                authselect enable-feature $feature;
            done
            
            authselect apply-changes -b --backup=after-hardening-custom-profile
        fi
        PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
        PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

        authselect apply-changes -b
    fi
    if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*' "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_unix.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks 's/^(\s*password\s+).*(\bpam_unix.so.*)/\1'"sufficient"' \2/' "$PAM_FILE_PATH"
            else
                echo 'password    '"sufficient"'    pam_unix.so' >> "$PAM_FILE_PATH"
            fi
        fi
        # Check the option
        if ! grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s*.*\ssha512\b' "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks '/\s*password\s+'"sufficient"'\s+pam_unix.so.*/ s/$/ sha512/' "$PAM_FILE_PATH"
        fi
    if [ -f /usr/bin/authselect ]; then
        
        authselect apply-changes -b
    fi
else
    echo "/etc/pam.d/system-auth was not found" >&2
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Protect Accounts by Restricting Password-Based Login   Group contains 4 groups and 24 rules
[ref]   Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the /etc/passwd and /etc/shadow files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.
Group   Set Account Expiration Parameters   Group contains 2 rules
Group   Set Password Expiration Parameters   Group contains 5 rules
[ref]   The file /etc/login.defs controls several password-related settings. Programs such as passwd, su, and login consult /etc/login.defs to determine behavior with regard to password aging, expiration warnings, and length. See the man page login.defs(5) for more information.

Users should be forced to change their passwords, in order to decrease the utility of compromised passwords. However, the need to change passwords often should be balanced against the risk that users will reuse or write down passwords if forced to change them too often. Forcing password changes every 90-360 days, depending on the environment, is recommended. Set the appropriate value as PASS_MAX_DAYS and apply it to existing accounts with the -M flag.

The PASS_MIN_DAYS (-m) setting prevents password changes for 7 days after the first change, to discourage password cycling. If you use this setting, train users to contact an administrator for an emergency password change in case a new password becomes compromised. The PASS_WARN_AGE (-W) setting gives users 7 days of warnings at login time that their passwords are about to expire.

For example, for each existing human user USER, expiration parameters could be adjusted to a 180 day maximum password age, 7 day minimum password age, and 7 day warning period with the following command:
$ sudo chage -M 180 -m 7 -W 7 USER

Rule   Set Existing Passwords Maximum Age   [ref]

Configure non-compliant accounts to enforce a 90-day maximum password lifetime restriction by running the following command:
$ sudo chage -M 90
          USER
         
Rationale:
Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_set_max_life_existing
References:
disaCCI-000199
nistIA-5(f), IA-5(1)(d), CM-6(a)
os-srgSRG-OS-000076-GPOS-00044
cis5.4.1.1
pcidss48.3.9

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
  set_fact:
    var_accounts_maximum_age_login_defs: !!str 90
  tags:
    - always

- name: Collect users with not correct maximum time period between password changes
  ansible.builtin.command:
    cmd: awk -F':' '(/^[^:]+:[^!*]/ && ($5 > {{ var_accounts_maximum_age_login_defs
      }} || $5 == "")) {print $1}' /etc/shadow
  register: user_names
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3.9
  - accounts_password_set_max_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Change the maximum time period between password changes
  ansible.builtin.user:
    user: '{{ item }}'
    password_expire_max: '{{ var_accounts_maximum_age_login_defs }}'
  with_items: '{{ user_names.stdout_lines }}'
  when: user_names.stdout_lines | length > 0
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3.9
  - accounts_password_set_max_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

var_accounts_maximum_age_login_defs='90'


while IFS= read -r i; do
    
    chage -M $var_accounts_maximum_age_login_defs $i

done <   <(awk -v var="$var_accounts_maximum_age_login_defs" -F: '(/^[^:]+:[^!*]/ && ($5 > var || $5 == "")) {print $1}' /etc/shadow)

Rule   Set Existing Passwords Warning Age   [ref]

To configure how many days prior to password expiration that a warning will be issued to users, run the command:
$ sudo chage --warndays 7
          USER
         
The DoD requirement is 7, and CIS recommendation is no less than 7 days. This profile requirement is 7.
Rationale:
Providing an advance warning that a password will be expiring gives users time to think of a secure password. Users caught unaware may choose a simple password or write it down where it may be discovered.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_set_warn_age_existing
References:
disaCCI-000198
nistIA-5(f), IA-5(1)(d), CM-6(a)
cis5.4.1.3
pcidss48.3.9

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: XCCDF Value var_accounts_password_warn_age_login_defs # promote to variable
  set_fact:
    var_accounts_password_warn_age_login_defs: !!str 7
  tags:
    - always

- name: Set Existing Passwords Warning Age - Collect Users With Incorrect Number of
    Days of Warning Before Password Expires
  ansible.builtin.command:
    cmd: awk -F':' '(($6 < {{ var_accounts_password_warn_age_login_defs }} || $6 ==
      "") && $2 ~ /^\$/) {print $1}' /etc/shadow
  register: result_pass_warn_age_user_names
  changed_when: false
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3.9
  - accounts_password_set_warn_age_existing
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Existing Passwords Warning Age - Ensure the Number of Days of Warning
    Before Password Expires
  ansible.builtin.command:
    cmd: chage --warndays {{ var_accounts_password_warn_age_login_defs }} {{ item
      }}
  with_items: '{{ result_pass_warn_age_user_names.stdout_lines }}'
  when: result_pass_warn_age_user_names is not skipped and result_pass_warn_age_user_names.stdout_lines
    | length > 0
  tags:
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3.9
  - accounts_password_set_warn_age_existing
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure

var_accounts_password_warn_age_login_defs='7'


while IFS= read -r i; do
    chage --warndays $var_accounts_password_warn_age_login_defs $i
done <   <(awk -v var="$var_accounts_password_warn_age_login_defs" -F: '(($6 < var || $6 == "") && $2 ~ /^\$/) {print $1}' /etc/shadow)

Rule   Set existing passwords a period of inactivity before they been locked   [ref]

Configure user accounts that have been inactive for over a given period of time to be automatically disabled by running the following command:
$ sudo chage --inactive 30USER
         
Rationale:
Inactive accounts pose a threat to system security since the users are not logging in to notice failed login attempts or other anomalies.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_set_post_pw_existing
References:
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.6
disaCCI-000017, CCI-000795
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistIA-4(e), AC-2(3), CM-6(a)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidssReq-8.1.4
os-srgSRG-OS-000118-GPOS-00060
cis5.4.1.5
pcidss48.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
  set_fact:
    var_account_disable_post_pw_expiration: !!str 90
  tags:
    - always

- name: Collect users with not correct INACTIVE parameter set
  ansible.builtin.command:
    cmd: awk -F':' '(($7 > {{ var_account_disable_post_pw_expiration }} || $7 == "")
      && $2 ~ /^\$/) {print $1}' /etc/shadow
  register: user_names
  changed_when: false
  tags:
  - NIST-800-171-3.5.6
  - NIST-800-53-AC-2(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-4(e)
  - PCI-DSS-Req-8.1.4
  - PCI-DSSv4-8.2.6
  - accounts_set_post_pw_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Change the period of inactivity
  ansible.builtin.command:
    cmd: chage --inactive {{ var_account_disable_post_pw_expiration }} {{ item }}
  with_items: '{{ user_names.stdout_lines }}'
  when: user_names is not skipped and user_names.stdout_lines | length > 0
  tags:
  - NIST-800-171-3.5.6
  - NIST-800-53-AC-2(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-4(e)
  - PCI-DSS-Req-8.1.4
  - PCI-DSSv4-8.2.6
  - accounts_set_post_pw_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict

var_account_disable_post_pw_expiration='90'


while IFS= read -r i; do
    chage --inactive $var_account_disable_post_pw_expiration $i
done <   <(awk -v var="$var_account_disable_post_pw_expiration" -F: '(($7 > var || $7 == "") && $2 ~ /^\$/) {print $1}' /etc/shadow)
Group   Verify Proper Storage and Existence of Password Hashes   Group contains 5 rules
[ref]   By default, password hashes for local accounts are stored in the second field (colon-separated) in /etc/shadow. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as /etc/passwd, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.

Rule   Verify All Account Password Hashes are Shadowed   [ref]

If any password hashes are stored in /etc/passwd (in the second field, instead of an x or *), the cause of this misconfiguration should be investigated. The account should have its password reset and the hash should be properly stored, or the account should be deleted entirely.
Rationale:
The hashes for all user account passwords should be stored in the file /etc/shadow and never in /etc/passwd, which is readable by all users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed
References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
ism1410
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistIA-5(h), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
cis7.2.1
pcidss48.3.2

Rule   Ensure all users last password change date is in the past   [ref]

All users should have a password change date in the past.
Warning:  Automatic remediation is not available, in order to avoid any system disruption.
Rationale:
If a user recorded password change date is in the future then they could bypass any set password expiration.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_last_change_is_in_past
References:
cis5.4.1.6
pcidss48.3.5

Rule   All GIDs referenced in /etc/passwd must be defined in /etc/group   [ref]

Add a group to the system for each GID referenced without a corresponding group.
Rationale:
If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group with the Group Identifier (GID) is subsequently created, the user may have unintended rights to any files associated with the group.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_gid_passwd_group_same
References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
disaCCI-000764
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.5.a
os-srgSRG-OS-000104-GPOS-00051
cis7.2.3
pcidss48.2.2

Rule   Prevent Login to Accounts With Empty Password   [ref]

If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the nullok in /etc/pam.d/system-auth and /etc/pam.d/password-auth to prevent logins with empty passwords.
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. Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
Rationale:
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_no_empty_passwords
References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
disaCCI-000366
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistIA-5(1)(a), IA-5(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
osppFIA_UAU.1
pcidssReq-8.2.3
os-srgSRG-OS-000480-GPOS-00227
cis5.3.3.4.1
pcidss48.3.1

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Prevent Login to Accounts With Empty Password - Check if system relies on
    authselect
  ansible.builtin.stat:
    path: /usr/bin/authselect
  register: result_authselect_present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

- name: Prevent Login to Accounts With Empty Password - Remediate using authselect
  block:

  - name: Prevent Login to Accounts With Empty Password - Check integrity of authselect
      current profile
    ansible.builtin.command:
      cmd: authselect check
    register: result_authselect_check_cmd
    changed_when: false
    failed_when: false

  - name: Prevent Login to Accounts With Empty Password - 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: Prevent Login to Accounts With Empty Password - 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: Prevent Login to Accounts With Empty Password - Ensure "without-nullok"
      feature is enabled using authselect tool
    ansible.builtin.command:
      cmd: authselect enable-feature without-nullok
    register: result_authselect_enable_feature_cmd
    when:
    - result_authselect_check_cmd is success
    - result_authselect_features.stdout is not search("without-nullok")

  - name: Prevent Login to Accounts With Empty Password - 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:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - result_authselect_present.stat.exists
  tags:
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

- name: Prevent Login to Accounts With Empty Password - Remediate directly editing
    PAM files
  ansible.builtin.replace:
    dest: '{{ item }}'
    regexp: nullok
  loop:
  - /etc/pam.d/system-auth
  - /etc/pam.d/password-auth
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not result_authselect_present.stat.exists
  tags:
  - CJIS-5.5.2
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
        mode: 0644
        path: /etc/pam.d/password-auth
        overwrite: true
      - contents:
          source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
        mode: 0644
        path: /etc/pam.d/system-auth
        overwrite: true

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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 without-nullok

authselect apply-changes -b
else
    
if grep -qP '^\s*auth\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/system-auth"; then
    sed -i -E --follow-symlinks 's/(.*auth.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/system-auth"
fi
    
if grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/system-auth"; then
    sed -i -E --follow-symlinks 's/(.*password.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/system-auth"
fi
    
if grep -qP '^\s*auth\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/password-auth"; then
    sed -i -E --follow-symlinks 's/(.*auth.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/password-auth"
fi
    
if grep -qP '^\s*password\s+'"sufficient"'\s+pam_unix.so\s.*\bnullok\b' "/etc/pam.d/password-auth"; then
    sed -i -E --follow-symlinks 's/(.*password.*'"sufficient"'.*pam_unix.so.*)\snullok=?[[:alnum:]]*(.*)/\1\2/g' "/etc/pam.d/password-auth"
fi
fi

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

Rule   Ensure There Are No Accounts With Blank or Null Passwords   [ref]

Check the "/etc/shadow" file for blank passwords with the following command:
$ sudo awk -F: '!$2 {print $1}' /etc/shadow
If the command returns any results, this is a finding. Configure all accounts on the system to have a password or lock the account with the following commands: Perform a password reset:
$ sudo passwd [username]
Lock an account:
$ sudo passwd -l [username]
Warning:  Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
Rationale:
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_no_empty_passwords_etc_shadow
References:
disaCCI-000366
nistCM-6(b), CM-6.1(iv)
os-srgSRG-OS-000480-GPOS-00227
cis7.2.2
pcidss42.2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Collect users with no password
  command: |
    awk -F: '!$2 {print $1}' /etc/shadow
  register: users_nopasswd
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-2.2.2
  - high_severity
  - low_complexity
  - low_disruption
  - no_empty_passwords_etc_shadow
  - no_reboot_needed
  - restrict_strategy

- name: Lock users with no password
  command: |
    passwd -l {{ item }}
  with_items: '{{ users_nopasswd.stdout_lines }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - users_nopasswd is not skipped and users_nopasswd.stdout_lines | length > 0
  tags:
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-2.2.2
  - high_severity
  - low_complexity
  - low_disruption
  - no_empty_passwords_etc_shadow
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

readarray -t users_with_empty_pass < <(sudo awk -F: '!$2 {print $1}' /etc/shadow)

for user_with_empty_pass in "${users_with_empty_pass[@]}"
do
    passwd -l $user_with_empty_pass
done

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Restrict Root Logins   Group contains 9 rules
[ref]   Direct root logins should be allowed only for emergency use. In normal situations, the administrator should access the system via a unique unprivileged account, and then use su or sudo to execute privileged commands. Discouraging administrators from accessing the root account directly ensures an audit trail in organizations with multiple administrators. Locking down the channels through which root can connect directly also reduces opportunities for password-guessing against the root account. The login program uses the file /etc/securetty to determine which interfaces should allow root logins. The virtual devices /dev/console and /dev/tty* represent the system consoles (accessible via the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default installation). The default securetty file also contains /dev/vc/*. These are likely to be deprecated in most environments, but may be retained for compatibility. Root should also be prohibited from connecting via network protocols. Other sections of this document include guidance describing how to prevent root from logging in via SSH.

Rule   Verify Only Root Has UID 0   [ref]

If any account other than root has a UID of 0, this misconfiguration should be investigated and the accounts other than root should be removed or have their UID changed.
If the account is associated with system commands or applications the UID should be changed to one greater than "0" but less than "1000." Otherwise assign a UID greater than "1000" that has not already been assigned.
Rationale:
An account has root authority if it has a UID of 0. Multiple accounts with a UID of 0 afford more opportunity for potential intruders to guess a password for a privileged account. Proper configuration of sudo is recommended to afford multiple system administrators access to root privileges in an accountable manner.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_accounts_no_uid_except_zero
References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
disaCCI-000366
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, AC-6(5), IA-4(b)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
pcidssReq-8.5
os-srgSRG-OS-000480-GPOS-00227
cis5.4.2.1
pcidss48.2.1

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all /etc/passwd file entries
  getent:
    database: passwd
    split: ':'
  tags:
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-AC-6(5)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-4(b)
  - PCI-DSS-Req-8.5
  - PCI-DSSv4-8.2.1
  - accounts_no_uid_except_zero
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Lock the password of the user accounts other than root with uid 0
  command: passwd -l {{ item.key }}
  loop: '{{ getent_passwd | dict2items | rejectattr(''key'', ''search'', ''root'')
    | list }}'
  when: item.value.1  == '0'
  tags:
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-AC-6(5)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-4(b)
  - PCI-DSS-Req-8.5
  - PCI-DSSv4-8.2.1
  - accounts_no_uid_except_zero
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

awk -F: '$3 == 0 && $1 != "root" { print $1 }' /etc/passwd | xargs --no-run-if-empty --max-lines=1 passwd -l

Rule   Verify Root Has A Primary GID 0   [ref]

The root user should have a primary group of 0.
Rationale:
To help ensure that root-owned files are not inadvertently exposed to other users.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_accounts_root_gid_zero
References:
pcidssReq-8.1.1
cis5.4.2.2
pcidss48.2.1

Rule   Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty   [ref]

Ensure that the group sugroup referenced by var_pam_wheel_group_for_su variable and used as value for the pam_wheel.so group option exists and has no members. This empty group used by pam_wheel.so in /etc/pam.d/su ensures that no user can run commands with altered privileges through the su command.
Warning:  Note that this rule just ensures the group exists and has no members. This rule does not configure pam_wheel.so module. The pam_wheel.so module configuration is accomplished by use_pam_wheel_group_for_su rule.
Rationale:
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_pam_wheel_group_empty
References:
cis5.2.7
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

- name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
    - Ensure {{ var_pam_wheel_group_for_su }} Group Exists
  ansible.builtin.group:
    name: '{{ var_pam_wheel_group_for_su }}'
    state: present
  when: '"pam" in ansible_facts.packages'
  tags:
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
    - Ensure {{ var_pam_wheel_group_for_su }} Group is Empty
  ansible.builtin.lineinfile:
    path: /etc/group
    regexp: ^({{ var_pam_wheel_group_for_su }}:[^:]+:[0-9]+:).*$
    line: \1
    backrefs: true
  when: '"pam" in ansible_facts.packages'
  tags:
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_pam_wheel_group_for_su='sugroup'


if ! grep -q "^${var_pam_wheel_group_for_su}:[^:]*:[^:]*:[^:]*" /etc/group; then
    groupadd ${var_pam_wheel_group_for_su}
fi

# group must be empty
gpasswd -M '' ${var_pam_wheel_group_for_su}

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

Rule   Ensure Authentication Required for Single User Mode   [ref]

Single user mode is used for recovery when the system detects an issue during boot or by manual selection from the bootloader.
Rationale:
Requiring authentication in single user mode prevents an unauthorized user from rebooting the system into single user to gain root privileges without credentials.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_root_password_configured
References:
cis5.4.2.4
pcidss42.2.2

Rule   Direct root Logins Not Allowed   [ref]

To further limit access to the root account, administrators can disable root logins at the console by editing the /etc/securetty file. This file lists all devices the root user is allowed to login to. If the file does not exist at all, the root user can login through any communication device on the system, whether via the console or via a raw network interface. This is dangerous as user can login to the system as root via Telnet, which sends the password in plain text over the network. By default, Red Hat Enterprise Linux 10's /etc/securetty file only allows the root user to login at the console physically attached to the system. To prevent root from logging in, remove the contents of this file. To prevent direct root logins, remove the contents of this file by typing the following command:
$ sudo echo > /etc/securetty
Warning:  This rule only checks the /etc/securetty file existence and its content. If you need to restrict user access using the /etc/securetty file, make sure the pam_securetty.so PAM module is properly enabled in relevant PAM files.
Rationale:
Disabling direct root logins ensures proper accountability and multifactor authentication to privileged accounts. Users will first login, then escalate to privileged (root) access via su / sudo. This is required for FISMA Low and FISMA Moderate systems.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_direct_root_logins
References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.1.1, 3.1.6
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
anssiR33
pcidss48.6.1

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Direct root Logins Not Allowed
  copy:
    dest: /etc/securetty
    content: ''
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - PCI-DSSv4-8.6.1
  - low_complexity
  - low_disruption
  - medium_severity
  - no_direct_root_logins
  - no_reboot_needed
  - restrict_strategy

---
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    storage:
      files:
      - contents:
          source: data:,
        mode: 0600
        path: /etc/securetty
        overwrite: true

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

echo > /etc/securetty

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

Rule   Ensure that System Accounts Are Locked   [ref]

Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. An attacker should not be able to log into these accounts.

System accounts are those user accounts with a user ID less than 1000. If any system account other than root, halt, sync, shutdown and nfsnobody has an unlocked password, disable it with the command:
$ sudo usermod -L account
         
Rationale:
Disabling authentication for default system accounts makes it more difficult for attackers to make use of them to compromise a system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_password_auth_for_systemaccounts
References:
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistAC-6, CM-6(a)
cis5.4.2.7
pcidss48.2.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure that System Accounts Are Locked - Get All Local Users From /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_password_auth_for_systemaccounts
  - no_reboot_needed
  - restrict_strategy

- name: Ensure that System Accounts Are Locked - Create local_users Variable From
    getent_passwd Facts
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd | dict2items }}'
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_password_auth_for_systemaccounts
  - no_reboot_needed
  - restrict_strategy

- name: Ensure that System Accounts Are Locked - Lock System Accounts
  ansible.builtin.user:
    name: '{{ item.key }}'
    password_lock: true
  loop: '{{ local_users }}'
  when:
  - item.value[1]|int < 1000
  - item.key not in ['root', 'halt', 'sync', 'shutdown', 'nfsnobody']
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_password_auth_for_systemaccounts
  - no_reboot_needed
  - restrict_strategy

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict

readarray -t systemaccounts < <(awk -F: \
  '($3 < 1000 && $3 != root && $3 != halt && $3 != sync && $3 != shutdown \
  && $3 != nfsnobody) { print $1 }' /etc/passwd)

for systemaccount in "${systemaccounts[@]}"; do
    usermod -L "$systemaccount"
done

Rule   Ensure that System Accounts Do Not Run a Shell Upon Login   [ref]

Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. Should an attacker be able to log into these accounts, they should not be granted access to a shell.

The login shell for each local account is stored in the last field of each line in /etc/passwd. System accounts are those user accounts with a user ID less than 1000. The user ID is stored in the third field. If any system account other than root has a login shell, disable it with the command:
$ sudo usermod -s /sbin/nologin account
         
Warning:  Do not perform the steps in this section on the root account. Doing so might cause the system to become inaccessible.
Rationale:
Ensuring shells are not given to system accounts upon login makes it more difficult for attackers to make use of system accounts.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_shelllogin_for_systemaccounts
References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03
disaCCI-000366
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
ism1491
iso27001-2013A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistAC-6, CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6
os-srgSRG-OS-000480-GPOS-00227
cis5.4.2.7
pcidss48.2.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure that System Accounts Do Not Run a Shell Upon Login - Get All Local
    Users From /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

- name: Ensure that System Accounts Do Not Run a Shell Upon Login - Create local_users
    Variable From getent_passwd Facts
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd | dict2items }}'
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

- name: Ensure that System Accounts Do Not Run a Shell Upon Login -  Disable Login
    Shell for System Accounts
  ansible.builtin.user:
    name: '{{ item.key }}'
    shell: /sbin/nologin
  loop: '{{ local_users }}'
  when:
  - item.key not in ['root']
  - item.value[1]|int < 1000
  - item.value[5] not in ['/sbin/shutdown', '/sbin/halt', '/bin/sync']
  tags:
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict

readarray -t systemaccounts < <(awk -F: '($3 < 1000 && $3 != root \
  && $7 != "\/sbin\/shutdown" && $7 != "\/sbin\/halt" && $7 != "\/bin\/sync") \
  { print $1 }' /etc/passwd)

for systemaccount in "${systemaccounts[@]}"; do
    usermod -s /sbin/nologin "$systemaccount"
done

Rule   Enforce Usage of pam_wheel with Group Parameter for su Authentication   [ref]

To ensure that only users who are members of the group set in the group option of pam_wheel.so module can run commands with altered privileges through the su command, make sure that the following line exists in the file /etc/pam.d/su:
auth required pam_wheel.so use_uid group=sugroup
         
Warning:  Note that ensure_pam_wheel_group_empty rule complements this requirement by ensuring the referenced group exists and has no members.
Rationale:
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_use_pam_wheel_group_for_su
References:
cis5.2.7
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

- name: Enforce Usage of pam_wheel with Group Parameter for su Authentication - Add
    the group to the /etc/pam.d/su file
  ansible.builtin.lineinfile:
    path: /etc/pam.d/su
    state: present
    regexp: ^[\s]*#[\s]*auth[\s]+required[\s]+pam_wheel\.so[\s]+use_uid group=$
    line: auth             required        pam_wheel.so use_uid group={{ var_pam_wheel_group_for_su
      }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su

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

var_pam_wheel_group_for_su='sugroup'


PAM_CONF=/etc/pam.d/su

pamstr=$(grep -P '^auth\s+required\s+pam_wheel\.so\s+(?=[^#]*\buse_uid\b)(?=[^#]*\bgroup=)' ${PAM_CONF})
if [ -z "$pamstr" ]; then
    sed -Ei '/^auth\b.*\brequired\b.*\bpam_wheel\.so/d' ${PAM_CONF} # remove any remaining uncommented pam_wheel.so line
    sed -Ei "/^auth\s+sufficient\s+pam_rootok\.so.*$/a auth             required        pam_wheel.so use_uid group=${var_pam_wheel_group_for_su}" ${PAM_CONF}
else
    group_val=$(echo -n "$pamstr" | grep -Eo '\bgroup=[_a-z][-0-9_a-z]*' | cut -d '=' -f 2)
    if [ -z "${group_val}" ] || [ ${group_val} != ${var_pam_wheel_group_for_su} ]; then
        sed -Ei "s/(^auth\s+required\s+pam_wheel.so\s+[^#]*group=)[_a-z][-0-9_a-z]*/\1${var_pam_wheel_group_for_su}/" ${PAM_CONF}
    fi
fi

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

Rule   Ensure All Groups on the System Have Unique Group ID   [ref]

Change the group name or delete groups, so each has a unique id.
Warning:  Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale:
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_group_unique_id
References:
disaCCI-000764
os-srgSRG-OS-000104-GPOS-00051
cis7.2.5
pcidss48.2.1

Rule   Ensure All Groups on the System Have Unique Group Names   [ref]

Change the group name or delete groups, so each has a unique name.
Warning:  Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale:
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_group_unique_name
References:
cis7.2.7
pcidss48.2.1
Group   Secure Session Configuration Files for Login Accounts   Group contains 1 rule
[ref]   When a user logs into a Unix account, the system configures the user's session by reading a number of files. Many of these files are located in the user's home directory, and may have weak permissions as a result of user error or misconfiguration. If an attacker can modify or even read certain types of account configuration information, they can often gain full access to the affected user's account. Therefore, it is important to test and correct configuration file permissions for interactive accounts, particularly those of privileged users such as root or system administrators.

Rule   Set Interactive Session Timeout   [ref]

Setting the TMOUT option in /etc/profile ensures that all user sessions will terminate based on inactivity. The value of TMOUT should be exported and read only. The TMOUT setting in a file loaded by /etc/profile, e.g. /etc/profile.d/tmout.sh should read as follows:
typeset -xr TMOUT=600
        
or
declare -xr TMOUT=600
        
Using the typeset keyword is preferred for wider compatibility with ksh and other shells.
Rationale:
Terminating an idle session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been left unattended.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_tmout
References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.11
disaCCI-000057, CCI-001133, CCI-002361
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-12, SC-10, AC-2(5), CM-6(a)
nist-csfPR.AC-7
osppFMT_MOF_EXT.1
os-srgSRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010
anssiR32
cis5.4.3.2
pcidss48.6.1

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_tmout # promote to variable
  set_fact:
    var_accounts_tmout: !!str 600
  tags:
    - always

- name: Correct any occurrence of TMOUT in /etc/profile
  replace:
    path: /etc/profile
    regexp: ^[^#].*TMOUT=.*
    replace: typeset -xr TMOUT={{ var_accounts_tmout }}
  register: profile_replaced
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Interactive Session Timeout
  lineinfile:
    path: /etc/profile.d/tmout.sh
    create: true
    regexp: TMOUT=
    line: typeset -xr TMOUT={{ var_accounts_tmout }}
    state: present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

var_accounts_tmout='600'


# if 0, no occurence of tmout found, if 1, occurence found
tmout_found=0


for f in /etc/profile /etc/profile.d/*.sh; do

    if grep --silent '^[^#].*TMOUT' $f; then
        sed -i -E "s/^(.*)TMOUT\s*=\s*(\w|\$)*(.*)$/typeset -xr TMOUT=$var_accounts_tmout\3/g" $f
        tmout_found=1
    fi
done

if [ $tmout_found -eq 0 ]; then
        echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile.d/tmout.sh
        echo "typeset -xr TMOUT=$var_accounts_tmout" >> /etc/profile.d/tmout.sh
fi

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

Rule   Enable authselect   [ref]

Configure user authentication setup to use the authselect tool. If authselect profile is selected, the rule will enable the minimal profile.
Warning:  If the sudo authselect select command returns an error informing that the chosen profile cannot be selected, it is probably because PAM files have already been modified by the administrator. If this is the case, in order to not overwrite the desired changes made by the administrator, the current PAM settings should be investigated before forcing the selection of the chosen authselect profile.
Rationale:
Authselect is a successor to authconfig. It is a tool to select system authentication and identity sources from a list of supported profiles instead of letting the administrator manually build the PAM stack. That way, it avoids potential breakage of configuration, as it ships several tested profiles that are well tested and supported to solve different use-cases.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_enable_authselect
References:
disaCCI-000213
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
nistAC-3
osppFIA_UAU.1, FIA_AFL.1
os-srgSRG-OS-000480-GPOS-00227
anssiR31
cisenable_authselect
pcidss48.3.4


var_authselect_profile='minimal'


authselect current

if test "$?" -ne 0; then
    authselect select "$var_authselect_profile"

    if test "$?" -ne 0; then
        if rpm --quiet --verify pam; then
            authselect select --force "$var_authselect_profile"
        else
	        echo "authselect is not used but files from the 'pam' package have been altered, so the authselect configuration won't be forced." >&2
        fi
    fi
fi
Group   GRUB2 bootloader configuration   Group contains 1 group and 6 rules
[ref]   During the boot process, the boot loader is responsible for starting the execution of the kernel and passing options to it. The boot loader allows for the selection of different kernels - possibly on different partitions or media. The default Red Hat Enterprise Linux 10 boot loader for x86 systems is called GRUB2. Options it can pass to the kernel include single-user mode, which provides root access without any authentication, and the ability to disable SELinux. To prevent local users from modifying the boot parameters and endangering security, protect the boot loader configuration with a password and ensure its configuration file's permissions are set properly.
Group   Non-UEFI GRUB2 bootloader configuration   Group contains 6 rules
[ref]   Non-UEFI GRUB2 bootloader configuration

Rule   Verify /boot/grub2/grub.cfg Group Ownership   [ref]

The file /boot/grub2/grub.cfg should be group-owned by the root group to prevent destruction or modification of the file. To properly set the group owner of /boot/grub2/grub.cfg, run the command:
$ sudo chgrp root /boot/grub2/grub.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_grub2_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/grub.cfg
  stat:
    path: /boot/grub2/grub.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /boot/grub2/grub.cfg
  file:
    path: /boot/grub2/grub.cfg
    group: '0'
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chgrp 0 /boot/grub2/grub.cfg

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

Rule   Verify /boot/grub2/user.cfg Group Ownership   [ref]

The file /boot/grub2/user.cfg should be group-owned by the root group to prevent reading or modification of the file. To properly set the group owner of /boot/grub2/user.cfg, run the command:
$ sudo chgrp root /boot/grub2/user.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway. Non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_user_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/user.cfg
  stat:
    path: /boot/grub2/user.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /boot/grub2/user.cfg
  file:
    path: /boot/grub2/user.cfg
    group: '0'
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chgrp 0 /boot/grub2/user.cfg

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

Rule   Verify /boot/grub2/grub.cfg User Ownership   [ref]

The file /boot/grub2/grub.cfg should be owned by the root user to prevent destruction or modification of the file. To properly set the owner of /boot/grub2/grub.cfg, run the command:
$ sudo chown root /boot/grub2/grub.cfg 
Rationale:
Only root should be able to modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_grub2_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/grub.cfg
  stat:
    path: /boot/grub2/grub.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /boot/grub2/grub.cfg
  file:
    path: /boot/grub2/grub.cfg
    owner: '0'
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chown 0 /boot/grub2/grub.cfg

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

Rule   Verify /boot/grub2/user.cfg User Ownership   [ref]

The file /boot/grub2/user.cfg should be owned by the root user to prevent reading or modification of the file. To properly set the owner of /boot/grub2/user.cfg, run the command:
$ sudo chown root /boot/grub2/user.cfg 
Rationale:
Only root should be able to modify important boot parameters. Also, non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_user_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/user.cfg
  stat:
    path: /boot/grub2/user.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /boot/grub2/user.cfg
  file:
    path: /boot/grub2/user.cfg
    owner: '0'
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CJIS-5.5.2.2
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-7.1
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chown 0 /boot/grub2/user.cfg

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

Rule   Verify /boot/grub2/grub.cfg Permissions   [ref]

File permissions for /boot/grub2/grub.cfg should be set to 600. To properly set the permissions of /boot/grub2/grub.cfg, run the command:
$ sudo chmod 600 /boot/grub2/grub.cfg
Rationale:
Proper permissions ensure that only the root user can modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_grub2_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/grub.cfg
  stat:
    path: /boot/grub2/grub.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xwrs,o-xwrt on /boot/grub2/grub.cfg
  file:
    path: /boot/grub2/grub.cfg
    mode: u-xs,g-xwrs,o-xwrt
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_grub2_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chmod u-xs,g-xwrs,o-xwrt /boot/grub2/grub.cfg

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

Rule   Verify /boot/grub2/user.cfg Permissions   [ref]

File permissions for /boot/grub2/user.cfg should be set to 600. To properly set the permissions of /boot/grub2/user.cfg, run the command:
$ sudo chmod 600 /boot/grub2/user.cfg
Rationale:
Proper permissions ensure that only the root user can read or modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_user_cfg
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.4.2
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Test for existence /boot/grub2/user.cfg
  stat:
    path: /boot/grub2/user.cfg
  register: file_exists
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xwrs,o-xwrt on /boot/grub2/user.cfg
  file:
    path: /boot/grub2/user.cfg
    mode: u-xs,g-xwrs,o-xwrt
  when:
  - '"/boot/efi" not in ansible_mounts | map(attribute="mount") | list'
  - '"grub2-common" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - file_exists.stat is defined and file_exists.stat.exists
  tags:
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_user_cfg
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
# Remediation is applicable only in certain platforms
if [ ! -d /sys/firmware/efi ] && rpm --quiet -q grub2-common && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

chmod u-xs,g-xwrs,o-xwrt /boot/grub2/user.cfg

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Configure Syslog   Group contains 2 groups and 5 rules
[ref]   The syslog service has been the default Unix logging mechanism for many years. It has a number of downsides, including inconsistent log format, lack of authentication for received messages, and lack of authentication, encryption, or reliable transport for messages sent over a network. However, due to its long history, syslog is a de facto standard which is supported by almost all Unix applications.

In Red Hat Enterprise Linux 10, rsyslog has replaced ksyslogd as the syslog daemon of choice, and it includes some additional security features such as reliable, connection-oriented (i.e. TCP) transmission of logs, the option to log to database formats, and the encryption of log data en route to a central logging server. This section discusses how to configure rsyslog for best effect, and how to use tools provided with the system to maintain and monitor logs.
Group   Ensure Proper Configuration of Log Files   Group contains 3 rules
[ref]   The file /etc/rsyslog.conf controls where log message are written. These are controlled by lines called rules, which consist of a selector and an action. These rules are often customized depending on the role of the system, the requirements of the environment, and whatever may enable the administrator to most effectively make use of log data. The default rules in Red Hat Enterprise Linux 10 are:
*.info;mail.none;authpriv.none;cron.none                /var/log/messages
authpriv.*                                              /var/log/secure
mail.*                                                  -/var/log/maillog
cron.*                                                  /var/log/cron
*.emerg                                                 *
uucp,news.crit                                          /var/log/spooler
local7.*                                                /var/log/boot.log
See the man page rsyslog.conf(5) for more information. Note that the rsyslog daemon can be configured to use a timestamp format that some log processing programs may not understand. If this occurs, edit the file /etc/rsyslog.conf and add or edit the following line:
$ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

Rule   Ensure Log Files Are Owned By Appropriate Group   [ref]

The group-owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's group owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chgrp root LOGFILE
        
Rationale:
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_groupownership
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-001314
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-10.5.1, Req-10.5.2
anssiR71
cis6.2.4.1
pcidss410.3.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Ensure Log Files Are Owned By Appropriate Group - Set rsyslog logfile configuration
    facts
  ansible.builtin.set_fact:
    rsyslog_etc_config: /etc/rsyslog.conf
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Get IncludeConfig directive
  ansible.builtin.shell: |
    set -o pipefail
    grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
  register: rsyslog_old_inc
  changed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Get include files directives
  ansible.builtin.shell: |
    set -o pipefail
    awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
  register: rsyslog_new_inc
  changed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Aggregate rsyslog includes
  ansible.builtin.set_fact:
    include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
      }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - List all config files
  ansible.builtin.find:
    paths: '{{ item | dirname }}'
    patterns: '{{ item | basename }}'
    hidden: false
    follow: true
  loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - include_config_output is defined
  register: rsyslog_config_files
  failed_when: false
  changed_when: false
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Extract log files old format
  ansible.builtin.shell: |
    set -o pipefail
    grep -oP '^[^(\s|#|\$)]+[\s]+.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
    awk '{print $NF}' | \
    sed -e 's/^-//' || true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_old
  changed_when: false
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_config_files is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Extract log files new format
  ansible.builtin.shell: |
    set -o pipefail
    grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
    grep -aoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
    grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
    tr -d "\""|| true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_new
  changed_when: false
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_config_files is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Sum all log files found
  ansible.builtin.set_fact:
    log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
      | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
      | list | flatten | unique }}'
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group -Setup log files attribute
  ansible.builtin.file:
    path: '{{ item }}'
    group: root
    state: file
  loop: '{{ log_files | list | flatten | unique }}'
  failed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

# List of log file paths to be inspected for correct permissions
# * Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
# * And also the log file paths listed after rsyslog's $IncludeConfig directive
#   (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

# Declare an array to hold the final list of different log file paths
declare -a LOG_FILE_PATHS

# Array to hold all rsyslog config entries
RSYSLOG_CONFIGS=()
RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

# Get full list of files to be checked
# RSYSLOG_CONFIGS may contain globs such as
# /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
# So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
RSYSLOG_CONFIG_FILES=()
for ENTRY in "${RSYSLOG_CONFIGS[@]}"
do
	# If directory, rsyslog will search for config files in recursively.
	# However, files in hidden sub-directories or hidden files will be ignored.
	if [ -d "${ENTRY}" ]
	then
		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
	elif [ -f "${ENTRY}" ]
	then
		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
	else
		echo "Invalid include object: ${ENTRY}"
	fi
done

# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	# From each of these files extract just particular log file path(s), thus:
	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
	# * Ignore empty lines,
	# * Strip quotes and closing brackets from paths.
	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
	# * From the remaining valid rows select only fields constituting a log file path
	# Text file column is understood to represent a log file path if and only if all of the
	# following are met:
	# * it contains at least one slash '/' character,
	# * it is preceded by space
	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
	# Search log file for path(s) only in case it exists!
	if [[ -f "${LOG_FILE}" ]]
	then
		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
		# Since above sed command might return more than one item (delimited by newline), split
		# the particular matches entries into new array specific for this log file
		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
		# items from newly created array for this log file
		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
		# Delete the temporary array
		unset ARRAY_FOR_LOG_FILE
	fi
done

# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")")
done

# Ensure the correct attribute if file exists
FILE_CMD="chgrp"
for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
do
	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
	if [ -z "$LOG_FILE_PATH" ]
	then
		continue
	fi
	$FILE_CMD "root" "$LOG_FILE_PATH"
done

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

Rule   Ensure Log Files Are Owned By Appropriate User   [ref]

The owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chown root LOGFILE
        
Rationale:
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_ownership
References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-001314
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-10.5.1, Req-10.5.2
anssiR71
cis6.2.4.1
pcidss410.3.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Ensure Log Files Are Owned By Appropriate User - Set rsyslog logfile configuration
    facts
  ansible.builtin.set_fact:
    rsyslog_etc_config: /etc/rsyslog.conf
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Get IncludeConfig directive
  ansible.builtin.shell: |
    set -o pipefail
    grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
  register: rsyslog_old_inc
  changed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Get include files directives
  ansible.builtin.shell: |
    set -o pipefail
    awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
  register: rsyslog_new_inc
  changed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Aggregate rsyslog includes
  ansible.builtin.set_fact:
    include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
      }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - List all config files
  ansible.builtin.find:
    paths: '{{ item | dirname }}'
    patterns: '{{ item | basename }}'
    hidden: false
    follow: true
  loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - include_config_output is defined
  register: rsyslog_config_files
  failed_when: false
  changed_when: false
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Extract log files old format
  ansible.builtin.shell: |
    set -o pipefail
    grep -oP '^[^(\s|#|\$)]+[\s]+.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
    awk '{print $NF}' | \
    sed -e 's/^-//' || true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_old
  changed_when: false
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_config_files is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Extract log files new format
  ansible.builtin.shell: |
    set -o pipefail
    grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
    grep -aoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
    grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
    tr -d "\""|| true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_new
  changed_when: false
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - rsyslog_config_files is not skipped
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Sum all log files found
  ansible.builtin.set_fact:
    log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
      | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
      | list | flatten | unique }}'
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User -Setup log files attribute
  ansible.builtin.file:
    path: '{{ item }}'
    owner: root
    state: file
  loop: '{{ log_files | list | flatten | unique }}'
  failed_when: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

# List of log file paths to be inspected for correct permissions
# * Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
# * And also the log file paths listed after rsyslog's $IncludeConfig directive
#   (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

# Declare an array to hold the final list of different log file paths
declare -a LOG_FILE_PATHS

# Array to hold all rsyslog config entries
RSYSLOG_CONFIGS=()
RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

# Get full list of files to be checked
# RSYSLOG_CONFIGS may contain globs such as
# /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
# So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
RSYSLOG_CONFIG_FILES=()
for ENTRY in "${RSYSLOG_CONFIGS[@]}"
do
	# If directory, rsyslog will search for config files in recursively.
	# However, files in hidden sub-directories or hidden files will be ignored.
	if [ -d "${ENTRY}" ]
	then
		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
	elif [ -f "${ENTRY}" ]
	then
		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
	else
		echo "Invalid include object: ${ENTRY}"
	fi
done

# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	# From each of these files extract just particular log file path(s), thus:
	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
	# * Ignore empty lines,
	# * Strip quotes and closing brackets from paths.
	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
	# * From the remaining valid rows select only fields constituting a log file path
	# Text file column is understood to represent a log file path if and only if all of the
	# following are met:
	# * it contains at least one slash '/' character,
	# * it is preceded by space
	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
	# Search log file for path(s) only in case it exists!
	if [[ -f "${LOG_FILE}" ]]
	then
		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
		# Since above sed command might return more than one item (delimited by newline), split
		# the particular matches entries into new array specific for this log file
		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
		# items from newly created array for this log file
		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
		# Delete the temporary array
		unset ARRAY_FOR_LOG_FILE
	fi
done

# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")")
done

# Ensure the correct attribute if file exists
FILE_CMD="chown"
for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
do
	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
	if [ -z "$LOG_FILE_PATH" ]
	then
		continue
	fi
	$FILE_CMD "root" "$LOG_FILE_PATH"
done

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

Rule   Ensure System Log Files Have Correct Permissions   [ref]

The file permissions for all log files written by rsyslog should be set to 640, or more restrictive. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's permissions:
$ ls -l LOGFILE
        
If the permissions are not 640 or more restrictive, run the following command to correct this:
$ sudo chmod 640 LOGFILE
        
"
Rationale:
Log files can contain valuable information regarding system configuration. If the system log files are not protected unauthorized users could change the logged data, eliminating their forensic value.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_permissions
References:<
disaCCI-001314
ism0988, 1405
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
pcidssReq-10.5.1, Req-10.5.2
anssi