Guide to the Secure Configuration of SUSE Linux Enterprise 16

with profile ANSSI-BP-028 (minimal)
This profile contains configurations that align to ANSSI-BP-028 v2.0 at the minimal hardening level. ANSSI is the French National Information Security Agency, and stands for Agence nationale de la sécurité des systèmes d'information. ANSSI-BP-028 is a configuration recommendation for GNU/Linux systems. A copy of the ANSSI-BP-028 can be found at the ANSSI website: https://www.ssi.gouv.fr/administration/guide/recommandations-de-securite-relatives-a-un-systeme-gnulinux/ An English version of the ANSSI-BP-028 can also be found at the ANSSI website: https://messervices.cyber.gouv.fr/guides/en-configuration-recommendations-gnulinux-system
This guide presents a catalog of security-relevant configuration settings for SUSE Linux Enterprise 16. 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 TitleANSSI-BP-028 (minimal)
Profile IDxccdf_org.ssgproject.content_profile_anssi_bp28_minimal

CPE Platforms

  • cpe:/o:suse:linux_enterprise_server:16

Revision History

Current version: 0.1.81

  • draft (as of 2026-02-26)

Table of Contents

  1. System Settings
    1. Installing and Maintaining Software
    2. Account and Access Control
    3. File Permissions and Masks
  2. Services
    1. DHCP
    2. Obsolete Services

Checklist

Group   Guide to the Secure Configuration of SUSE Linux Enterprise 16   Group contains 19 groups and 25 rules
Group   System Settings   Group contains 12 groups and 20 rules
[ref]   Contains rules that check correct system settings.
Group   Installing and Maintaining Software   Group contains 1 group and 4 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   Updating Software   Group contains 4 rules
[ref]   The zypper 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.

SUSE Linux Enterprise 16 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using zypper 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 zypper Configuration   [ref]

The gpgcheck option controls whether RPM packages' signatures are always checked prior to installation. To configure zypper to check package signatures before installing them, ensure the following line appears in /etc/zypp/zypp.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
Identifiers:

CCE-95809-0

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
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
ism1493
pcidss46.3.3, 6.3

# Remediation is applicable only in certain platforms
if rpm --quiet -q zypper; 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/zypp/zypp.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/zypp/zypp.conf"
else
    if [[ -s "/etc/zypp/zypp.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/zypp/zypp.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/zypp/zypp.conf"
    fi
    cce="CCE-95809-0"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/zypp/zypp.conf" >> "/etc/zypp/zypp.conf"
    printf '%s\n' "$formatted_output" >> "/etc/zypp/zypp.conf"
fi

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

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-95809-0
  - 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
  - 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
  community.general.ini_file:
    dest: /etc/zypp/zypp.conf
    section: main
    option: gpgcheck
    value: 1
    no_extra_spaces: true
    create: false
  when: '"zypper" in ansible_facts.packages'
  tags:
  - CCE-95809-0
  - 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
  - PCI-DSSv4-6.3.3
  - configure_strategy
  - ensure_gpgcheck_globally_activated
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

Rule   Ensure gpgcheck Enabled for Local Packages   [ref]

zypper should be configured to verify the signature(s) of local packages prior to installation. To configure zypper to verify signatures of local packages, set the localpkg_gpgcheck to 1 in /etc/zypp/zypp.conf.
Rationale:
Changes to any software components can have significant effects to the overall security of the operating system. This requirement ensures the software has not been tampered and 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.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages
Identifiers:

CCE-95830-6

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui3.4.8
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
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-11(a), CM-11(b), CM-6(a), CM-5(3), SA-12, SA-12(10)
nist-csfPR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
os-srgSRG-OS-000366-GPOS-00153
anssiR59
ism1493

# Remediation is applicable only in certain platforms
if rpm --quiet -q zypper; 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' <<< "^localpkg_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 "^localpkg_gpgcheck\\>" "/etc/zypp/zypp.conf"; then
    escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
    LC_ALL=C sed -i --follow-symlinks "s/^localpkg_gpgcheck\\>.*/$escaped_formatted_output/gi" "/etc/zypp/zypp.conf"
else
    if [[ -s "/etc/zypp/zypp.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/zypp/zypp.conf" || true)" ]]; then
        LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/zypp/zypp.conf"
    fi
    cce="CCE-95830-6"
    printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/zypp/zypp.conf" >> "/etc/zypp/zypp.conf"
    printf '%s\n' "$formatted_output" >> "/etc/zypp/zypp.conf"
fi

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

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-95830-6
  - 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)
  - ensure_gpgcheck_local_packages
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

- name: Ensure GPG check Enabled for Local Packages (zypper)
  block:

  - name: Check stats of zypper
    ansible.builtin.stat:
      path: /etc/zypp/zypp.conf
    register: pkg

  - name: Check if config file of zypper is a symlink
    ansible.builtin.set_fact:
      pkg_config_file_symlink: '{{ pkg.stat.lnk_target if pkg.stat.lnk_target is match("^/.*")
        else "/etc/zypp/zypp.conf" | dirname ~ "/" ~ pkg.stat.lnk_target }}'
    when: pkg.stat.lnk_target is defined

  - name: Ensure GPG check Enabled for Local Packages (zypper)
    community.general.ini_file:
      dest: '{{ pkg_config_file_symlink |  default("/etc/zypp/zypp.conf") }}'
      section: main
      option: localpkg_gpgcheck
      value: 1
      no_extra_spaces: true
      create: true
  when: '"zypper" in ansible_facts.packages'
  tags:
  - CCE-95830-6
  - 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)
  - ensure_gpgcheck_local_packages
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

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

To ensure signature checking is not disabled for any repos, remove any lines from files in /etc/zypp/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
Identifiers:

CCE-96678-8

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
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
ism1493
pcidss46.3.3, 6.3


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

Complexity:low
Disruption:medium
Reboot:false
Strategy:enable
- name: Grep for zypper repo section names
  ansible.builtin.shell: |
    set -o pipefail
    grep -HEr '^\[.+\]' -r /etc/zypp/repos.d/
  register: repo_grep_results
  failed_when: repo_grep_results.rc not in [0, 1]
  changed_when: false
  tags:
  - CCE-96678-8
  - 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
  - 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 zypper repo
  community.general.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:
  - CCE-96678-8
  - 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
  - PCI-DSSv4-6.3.3
  - enable_strategy
  - ensure_gpgcheck_never_disabled
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

Rule   Ensure Software Patches Installed   [ref]

If the system is configured for online updates, invoking the following command will list available security updates:
$ sudo zypper refresh && sudo zypper list-patches -g security


NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates.
Warning:  SUSE Linux Enterprise 16 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
Identifiers:

CCE-96484-1

References:
cis-csc18, 20, 4
cjis5.10.4.1
cobit5APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02
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
ism1409
pcidss46.3.3, 6.3

Complexity:low
Disruption:high
Reboot:true
Strategy:patch


zypper patch -g security -y

Complexity:low
Disruption:high
Reboot:true
Strategy:patch
- name: Security patches are up to date
  ansible.builtin.package:
    name: '*'
    state: latest
  tags:
  - CCE-96484-1
  - CJIS-5.10.4.1
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SI-2(5)
  - NIST-800-53-SI-2(c)
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3
  - PCI-DSSv4-6.3.3
  - high_disruption
  - low_complexity
  - medium_severity
  - patch_strategy
  - reboot_required
  - security_patches_up_to_date
  - skip_ansible_lint
Group   Account and Access Control   Group contains 7 groups and 9 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 SUSE Linux Enterprise 16.
Group   Protect Accounts by Configuring PAM   Group contains 4 groups and 7 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 1 rule
[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   [ref]

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

In the file /etc/pam.d/common-password, make sure the parameters remember and use_authtok are present, and that the value for the remember parameter is 5 or greater. For example:
password requisite pam_pwhistory.so ...existing_options... remember=5 use_authtok
The profile requirement is 5 passwords.
Rationale:
Preventing reuse of previous passwords helps ensure that a compromised password is not reused by a user.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember
Identifiers:

CCE-95993-2

References:
os-srgSRG-OS-000077-GPOS-00045

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel-default || rpm --quiet -q kernel-default-base && { rpm --quiet -q pam; }; then

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_remember='5'

VALUES+=("$var_password_pam_remember")
VALUE_NAMES+=("remember")
ARGS+=("")
NEW_ARGS+=("")

VALUES+=("")
VALUE_NAMES+=("")
ARGS+=("use_authtok")
NEW_ARGS+=("use_authtok")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_pwhistory.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_pwhistory.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_pwhistory.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_pwhistory.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_pwhistory.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_pwhistory.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_pwhistory.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_pwhistory.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_pwhistory.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_remember # promote to variable
  set_fact:
    var_password_pam_remember: !!str 5
  tags:
    - always

- name: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_pwhistory.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_pwhistory.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_pwhistory.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_pwhistory.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  - check_pam_module_result.stdout is defined and '"pam_pwhistory.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_pwhistory.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_pwhistory.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  - control_flag|length
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_pwhistory.so" module has argument "remember={{ var_password_pam_remember
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_pwhistory.so(?:\s+\S+)*\s+remember=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_remember }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "remember" argument in "pam_pwhistory.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_pwhistory.so.*\s+remember(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "remember" argument to "pam_pwhistory.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_pwhistory.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> remember={{ var_password_pam_remember }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  - check_pam_module_argument_result is not skipped and '"remember" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set argument_value fact
  ansible.builtin.set_fact:
    argument_value: ''
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_pwhistory.so" module has argument "use_authtok"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_pwhistory.so(?:\s+\S+)*\s+use_authtok=)(?!)\S*((\s+\S+)*\s*\\*\s*)$
    line: \g<1>\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  - argument_value|length
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "use_authtok" argument in "pam_pwhistory.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_pwhistory.so.*\s+use_authtok(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "use_authtok" argument to "pam_pwhistory.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_pwhistory.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> use_authtok\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - '"pam" in ansible_facts.packages'
  - check_pam_module_argument_result is not skipped and '"use_authtok" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-95993-2
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
Group   Set Password Quality Requirements   Group contains 1 group and 5 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, if using pam_cracklib   Group contains 5 rules
[ref]   The pam_cracklib PAM module can be configured to meet requirements for a variety of policies.

For example, to configure pam_cracklib to require at least one uppercase character, lowercase character, digit, and other (special) character, locate the following line in /etc/pam.d/system-auth:
password requisite pam_cracklib.so try_first_pass retry=3
and then alter it to read:
password required pam_cracklib.so try_first_pass retry=3 maxrepeat=3 minlen=14 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1 difok=4
If no such line exists, add one as the first line of the password section in /etc/pam.d/system-auth. The arguments can be modified to ensure compliance with your organization's security policy. Discussion of each parameter follows.
Warning:  Note that the password quality requirements are not enforced for the root account for some reason.

Rule   Set Password Strength Minimum Digit Characters   [ref]

The pam_cracklib 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_cracklib will grant +1 additional length credit for each digit. Add dcredit=-1 after pam_cracklib.so to require use of a digit in passwords.
Rationale:
Requiring digits makes password guessing attacks more difficult by ensuring a larger search space.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_cracklib_accounts_password_pam_dcredit
Identifiers:

CCE-96207-6

References:
pcidssReq-8.2.3
os-srgSRG-OS-000071-GPOS-00039
anssiR31
pcidss48.3.6, 8.3

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

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_dcredit='-1'

VALUES+=("$var_password_pam_dcredit")
VALUE_NAMES+=("dcredit")
ARGS+=("")
NEW_ARGS+=("")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_cracklib.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_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: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - control_flag|length
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "dcredit={{ var_password_pam_dcredit
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+dcredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_dcredit }}\g<2>
    backrefs: true
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "dcredit" argument in "pam_cracklib.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+dcredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "dcredit" argument to "pam_cracklib.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> dcredit={{ var_password_pam_dcredit }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_argument_result is not skipped and '"dcredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-96207-6
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Set Password Strength Minimum Lowercase Characters   [ref]

The pam_cracklib 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_cracklib will grant +1 additional length credit for each lowercase character. Add lcredit=-1 after pam_cracklib.so to require use of a lowercase character in passwords.
Rationale:
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_cracklib_accounts_password_pam_lcredit
Identifiers:

CCE-95874-4

References:
pcidssReq-8.2.3
os-srgSRG-OS-000070-GPOS-00038
anssiR31
pcidss48.3.6, 8.3

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

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_lcredit='-1'

VALUES+=("$var_password_pam_lcredit")
VALUE_NAMES+=("lcredit")
ARGS+=("")
NEW_ARGS+=("")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_cracklib.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_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: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - control_flag|length
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "lcredit={{ var_password_pam_lcredit
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+lcredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_lcredit }}\g<2>
    backrefs: true
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "lcredit" argument in "pam_cracklib.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+lcredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "lcredit" argument to "pam_cracklib.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> lcredit={{ var_password_pam_lcredit }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_argument_result is not skipped and '"lcredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-95874-4
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Set Password Minimum Length   [ref]

The pam_cracklib module's minlen parameter controls requirements for minimum characters required in a password. Add minlen=15 to set minimum password length requirements.
Rationale:
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_cracklib_accounts_password_pam_minlen
Identifiers:

CCE-96567-3

References:
pcidssReq-8.2.3
os-srgSRG-OS-000078-GPOS-00046
anssiR31
pcidss48.3.6, 8.3

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

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_minlen='15'

VALUES+=("$var_password_pam_minlen")
VALUE_NAMES+=("minlen")
ARGS+=("")
NEW_ARGS+=("")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_cracklib.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_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 15
  tags:
    - always

- name: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - control_flag|length
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "minlen={{ var_password_pam_minlen
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+minlen=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_minlen }}\g<2>
    backrefs: true
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "minlen" argument in "pam_cracklib.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+minlen(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "minlen" argument to "pam_cracklib.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> minlen={{ var_password_pam_minlen }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_argument_result is not skipped and '"minlen" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-96567-3
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Set Password Strength Minimum Special Characters   [ref]

The pam_cracklib module's ocredit= parameter controls requirements for usage of special (or ``other'') characters in a password. When set to a negative number, any password will be required to contain that many special characters. When set to a positive number, pam_cracklib will grant +1 additional length credit for each special character. Make sure the ocredit parameter for the pam_cracklib module is set to less than or equal to -1. For example, ocredit=-1 .
Rationale:
Requiring a minimum number of special characters makes password guessing attacks more difficult by ensuring a larger search space.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_cracklib_accounts_password_pam_ocredit
Identifiers:

CCE-96246-4

References:
nistIA-5(a), IA-5(v)
pcidssReq-8.2.3
os-srgSRG-OS-000266-GPOS-00101
anssiR31

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

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_ocredit='-1'

VALUES+=("$var_password_pam_ocredit")
VALUE_NAMES+=("ocredit")
ARGS+=("")
NEW_ARGS+=("")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_cracklib.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_ocredit # promote to variable
  set_fact:
    var_password_pam_ocredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - control_flag|length
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "ocredit={{ var_password_pam_ocredit
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+ocredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_ocredit }}\g<2>
    backrefs: true
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "ocredit" argument in "pam_cracklib.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+ocredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "ocredit" argument to "pam_cracklib.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> ocredit={{ var_password_pam_ocredit }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_argument_result is not skipped and '"ocredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-96246-4
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Set Password Strength Minimum Uppercase Characters   [ref]

The pam_cracklib module's ucredit= parameter controls requirements for usage of uppercase letters in a password. When set to a negative number, any password will be required to contain that many uppercase characters. When set to a positive number, pam_cracklib will grant +1 additional length credit for each uppercase character. Add ucredit=-1 after pam_cracklib.so to require use of an upper case character in passwords.
Rationale:
Requiring a minimum number of uppercase characters makes password guessing attacks more difficult by ensuring a larger search space.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_cracklib_accounts_password_pam_ucredit
Identifiers:

CCE-96294-4

References:
pcidssReq-8.2.3
os-srgSRG-OS-000069-GPOS-00037
anssiR31

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

declare -a VALUES=()
declare -a VALUE_NAMES=()
declare -a ARGS=()
declare -a NEW_ARGS=()
declare -a DEL_ARGS=()

var_password_pam_ucredit='-1'

VALUES+=("$var_password_pam_ucredit")
VALUE_NAMES+=("ucredit")
ARGS+=("")
NEW_ARGS+=("")


for idx in "${!VALUES[@]}"
do
    if [ -e "/etc/pam.d/common-password" ] ; then
        valueRegex="${VALUES[$idx]}" defaultValue="${VALUES[$idx]}"
        # non-empty values need to be preceded by an equals sign
        [ -n "${valueRegex}" ] && valueRegex="=${valueRegex}"
        # add an equals sign to non-empty values
        [ -n "${defaultValue}" ] && defaultValue="=${defaultValue}"

        # fix the value for 'option' if one exists but does not match 'valueRegex'
        if grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}(?"'!'"${valueRegex}(\\s|\$))" < "/etc/pam.d/common-password" ; then
            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s)${VALUE_NAMES[$idx]}=[^[:space:]]*/\\1${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"

        # add 'option=default' if option is not set
        elif grep -q -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" &&
                grep    -E "^\\s*password\\s+requisite\\s+pam_cracklib.so" < "/etc/pam.d/common-password" | grep -q -E -v "\\s${VALUE_NAMES[$idx]}(=|\\s|\$)" ; then

            sed --follow-symlinks -i -E -e "s/^(\\s*password\\s+requisite\\s+pam_cracklib.so[^\\n]*)/\\1 ${VALUE_NAMES[$idx]}${defaultValue}/" "/etc/pam.d/common-password"
        # add a new entry if none exists
        elif ! grep -q -P "^\\s*password\\s+requisite\\s+pam_cracklib.so(\\s.+)?\\s+${VALUE_NAMES[$idx]}${valueRegex}(\\s|\$)" < "/etc/pam.d/common-password" ; then
            echo "password requisite pam_cracklib.so ${VALUE_NAMES[$idx]}${defaultValue}" >> "/etc/pam.d/common-password"
        fi
    else
        echo "/etc/pam.d/common-password doesn't exist" >&2
    fi
done

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_ucredit # promote to variable
  set_fact:
    var_password_pam_ucredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  ansible.builtin.set_fact:
    control_flag: requisite
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - control_flag|length
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "ucredit={{ var_password_pam_ucredit
    }}"
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+ucredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_ucredit }}\g<2>
    backrefs: true
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "ucredit" argument in "pam_cracklib.so" module
  ansible.builtin.shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+ucredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "ucredit" argument to "pam_cracklib.so" module
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> ucredit={{ var_password_pam_ucredit }}\g<2>
    backrefs: true
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - check_pam_module_argument_result is not skipped and '"ucredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-96294-4
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
Group   Set Password Hashing Algorithm   Group contains 1 rule
[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 Rounds in /usr/etc/login.defs   [ref]

In /usr/etc/login.defs, ensure SHA_CRYPT_MIN_ROUNDS and SHA_CRYPT_MAX_ROUNDS has the minimum value of 5000. For example:
SHA_CRYPT_MIN_ROUNDS 5000
SHA_CRYPT_MAX_ROUNDS 5000
         
Notice that if neither are set, they already have the default value of 5000. If either is set, they must have the minimum value of 5000.
Rationale:
Passwords need to be protected at all times, and hashing is the standard method for protecting passwords. If passwords are not hashed, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are hashed with a weak algorithm are no more protected than if they are kept in plain text.

Using more hashing rounds makes password cracking attacks more difficult.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_set_password_hashing_min_rounds_logindefs
Identifiers:

CCE-95736-5

References:
os-srgSRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel-default || rpm --quiet -q kernel-default-base; then

var_password_hashing_min_rounds_login_defs='5000'


config_file=/usr/etc/login.defs
current_min_rounds=$(grep -Po '^\s*SHA_CRYPT_MIN_ROUNDS\s+\K\d+' "$config_file")
current_max_rounds=$(grep -Po '^\s*SHA_CRYPT_MAX_ROUNDS\s+\K\d+' "$config_file")

if [[ -z "$current_min_rounds" || "$current_min_rounds" -le "$var_password_hashing_min_rounds_login_defs" ]]; then
    if [ -e "/usr/etc/login.defs" ] ; then
        
        LC_ALL=C sed -i "/^\s*SHA_CRYPT_MIN_ROUNDS\s*/Id" "/usr/etc/login.defs"
    else
        printf '%s\n' "Path '/usr/etc/login.defs' wasn't found on this system. Refusing to continue." >&2
        return 1
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/usr/etc/login.defs"

    cp "/usr/etc/login.defs" "/usr/etc/login.defs.bak"
    # Insert at the end of the file
    printf '%s\n' "SHA_CRYPT_MIN_ROUNDS $var_password_hashing_min_rounds_login_defs" >> "/usr/etc/login.defs"
    # Clean up after ourselves.
    rm "/usr/etc/login.defs.bak"
fi

if [[ -n "$current_max_rounds" && "$current_max_rounds" -le "$var_password_hashing_min_rounds_login_defs" ]]; then
    if [ -e "/usr/etc/login.defs" ] ; then
        
        LC_ALL=C sed -i "/^\s*SHA_CRYPT_MAX_ROUNDS\s*/Id" "/usr/etc/login.defs"
    else
        printf '%s\n' "Path '/usr/etc/login.defs' wasn't found on this system. Refusing to continue." >&2
        return 1
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/usr/etc/login.defs"

    cp "/usr/etc/login.defs" "/usr/etc/login.defs.bak"
    # Insert at the end of the file
    printf '%s\n' "SHA_CRYPT_MAX_ROUNDS $var_password_hashing_min_rounds_login_defs" >> "/usr/etc/login.defs"
    # Clean up after ourselves.
    rm "/usr/etc/login.defs.bak"
fi

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs
- name: XCCDF Value var_password_hashing_min_rounds_login_defs # promote to variable
  set_fact:
    var_password_hashing_min_rounds_login_defs: !!str 5000
  tags:
    - always

- name: Set Password Hashing Rounds in /usr/etc/login.defs - extract contents of the
    file /usr/etc/login.defs
  ansible.builtin.slurp:
    src: /usr/etc/login.defs
  register: etc_login_defs
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - extract the value of
    SHA_CRYPT_MIN_ROUNDS if present
  ansible.builtin.set_fact:
    etc_login_defs_sha_crypt_min_rounds: '{{ etc_login_defs[''content''] | b64decode
      | regex_search(''^\s*SHA_CRYPT_MIN_ROUNDS\s+(\d+)'', ''\1'', multiline=True)
      | default([], true) }}'
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - extract the value of
    SHA_CRYPT_MAX_ROUNDS if present
  ansible.builtin.set_fact:
    etc_login_defs_sha_crypt_max_rounds: '{{ etc_login_defs[''content''] | b64decode
      | regex_search(''^\s*SHA_CRYPT_MAX_ROUNDS\s+(\d+)'', ''\1'', multiline=True)
      | default([], true) }}'
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - Ensure SHA_CRYPT_MIN_ROUNDS
    has Minimum Value of 5000
  ansible.builtin.replace:
    path: /usr/etc/login.defs
    regexp: (^\s*SHA_CRYPT_MIN_ROUNDS\s+)(?:\d+)(.*$)
    replace: \g<1>{{ var_password_hashing_min_rounds_login_defs }}\g<2>
    backup: false
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - etc_login_defs_sha_crypt_min_rounds is defined and etc_login_defs_sha_crypt_min_rounds
    | length > 0 and etc_login_defs_sha_crypt_min_rounds | first | int < var_password_hashing_min_rounds_login_defs
    | int
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - Ensure SHA_CRYPT_MAX_ROUNDS
    has Minimum Value of 5000
  ansible.builtin.replace:
    path: /usr/etc/login.defs
    regexp: (^\s*SHA_CRYPT_MAX_ROUNDS\s+)(?:\d+)(.*$)
    replace: \g<1>{{ var_password_hashing_min_rounds_login_defs }}\g<2>
    backup: false
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - etc_login_defs_sha_crypt_max_rounds is defined and etc_login_defs_sha_crypt_max_rounds
    | length > 0 and etc_login_defs_sha_crypt_max_rounds | first | int < var_password_hashing_min_rounds_login_defs
    | int
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - SHA_CRYPT_MIN_ROUNDS
    add configuration if not found
  ansible.builtin.lineinfile:
    line: SHA_CRYPT_MIN_ROUNDS {{ var_password_hashing_min_rounds_login_defs }}
    path: /usr/etc/login.defs
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - etc_login_defs_sha_crypt_min_rounds | length == 0
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs

- name: Set Password Hashing Rounds in /usr/etc/login.defs - SHA_CRYPT_MAX_ROUNDS
    add configuration if not found
  ansible.builtin.lineinfile:
    line: SHA_CRYPT_MAX_ROUNDS {{ var_password_hashing_min_rounds_login_defs }}
    path: /usr/etc/login.defs
    state: present
  when:
  - ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  - etc_login_defs_sha_crypt_max_rounds | length == 0
  tags:
  - CCE-95736-5
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_min_rounds_logindefs
Group   Protect Accounts by Restricting Password-Based Login   Group contains 1 group and 2 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 Password Expiration Parameters   Group contains 2 rules
[ref]   The file /usr/etc/login.defs controls several password-related settings. Programs such as passwd, su, and login consult /usr/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 Root Account Password Maximum Age   [ref]

Configure the root account to enforce a 365-day maximum password lifetime restriction by running the following command:
$ sudo chage -M 365 root
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_root
Identifiers:

CCE-96252-2

References:
anssiR31

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

var_accounts_maximum_age_root='365'

chage -M $var_accounts_maximum_age_root root

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

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-96252-2
  - accounts_password_set_max_life_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_maximum_age_root # promote to variable
  set_fact:
    var_accounts_maximum_age_root: !!str 365
  tags:
    - always

- name: Change the maximum time period between password changes
  ansible.builtin.user:
    user: root
    password_expire_max: '{{ var_accounts_maximum_age_root }}'
  when: ("kernel-default" in ansible_facts.packages or "kernel-default-base" in ansible_facts.packages)
  tags:
  - CCE-96252-2
  - accounts_password_set_max_life_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
Group   File Permissions and Masks   Group contains 1 group and 7 rules
[ref]   Traditional Unix security relies heavily on file and directory permissions to prevent unauthorized users from reading or modifying files to which they should not have access.

Several of the commands in this section search filesystems for files or directories with certain characteristics, and are intended to be run on every local partition on a given system. When the variable PART appears in one of the commands below, it means that the command is intended to be run repeatedly, with the name of each local partition substituted for PART in turn.

The following command prints a list of all xfs partitions on the local system, which is the default filesystem for SUSE Linux Enterprise 16 installations:
$ mount -t xfs | awk '{print $3}'
For any systems that use a different local filesystem type, modify this command as appropriate.
Group   Verify Permissions on Important Files and Directories   Group contains 7 rules
[ref]   Permissions for many files on a system must be set restrictively to ensure sensitive information is properly protected. This section discusses important permission restrictions which can be verified to ensure that no harmful discrepancies have arisen.

Rule   Ensure All World-Writable Directories Are Owned by root User   [ref]

All directories in local partitions which are world-writable should be owned by root. If any world-writable directories are not owned by root, this should be investigated. Following this, the files should be deleted or assigned to root user.
Rationale:
Allowing a user account to own a world-writable directory is undesirable because it allows the owner of that directory to remove or replace any files that may be placed in the directory by other users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dir_perms_world_writable_root_owned
Identifiers:

CCE-95897-5

References:
os-srgSRG-OS-000480-GPOS-00227, SRG-OS-000138-GPOS-00069
anssiR54

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

# At least under containerized env /proc can have files w/o possilibity to
# modify even as root. And touching /proc is not good idea anyways.
find / -path /proc -prune -o \
    -not -fstype afs -not -fstype autofs -not -fstype ceph -not -fstype cifs -not -fstype smb3 \
    -not -fstype smbfs -not -fstype sshfs -not -fstype ncpfs -not -fstype ncp -not -fstype nfs \
    -not -fstype nfs4 -not -fstype gfs -not -fstype gfs2 -not -fstype glusterfs -not -fstype gpfs \
    -not -fstype pvfs2 -not -fstype ocfs2 -not -fstype lustre -not -fstype davfs \
    -not -fstype fuse.sshfs -type d -perm -0002 -uid +0 -exec chown root {} \;

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure All World-Writable Directories Are Owned by root User - Define Excluded
    (Non-Local) File Systems and Paths
  ansible.builtin.set_fact:
    excluded_fstypes:
    - afs
    - autofs
    - ceph
    - cifs
    - smb3
    - smbfs
    - sshfs
    - ncpfs
    - ncp
    - nfs
    - nfs4
    - gfs
    - gfs2
    - glusterfs
    - gpfs
    - pvfs2
    - ocfs2
    - lustre
    - davfs
    - fuse.sshfs
    excluded_paths:
    - dev
    - proc
    - run
    - sys
    search_paths: []
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Find Relevant
    Root Directories Ignoring Pre-Defined Excluded Paths
  ansible.builtin.find:
    paths: /
    file_type: directory
    excludes: '{{ excluded_paths }}'
    hidden: true
    recurse: false
  register: result_relevant_root_dirs
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Include Relevant
    Root Directories in a List of Paths to be Searched
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.path]) }}'
  loop: '{{ result_relevant_root_dirs.files }}'
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Increment Search
    Paths List with Local Partitions Mount Points
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.mount]) }}'
  loop: '{{ ansible_mounts }}'
  when:
  - item.fstype not in excluded_fstypes
  - item.mount != '/'
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Increment Search
    Paths List with Local NFS File System Targets
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.device.split('':'')[1]]) }}'
  loop: '{{ ansible_mounts }}'
  when: item.device is search("localhost:")
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Define Rule
    Specific Facts
  ansible.builtin.set_fact:
    world_writable_dirs: []
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Find All Uncompliant
    Directories in Local File Systems
  ansible.builtin.command:
    cmd: find {{ item }} -xdev -type d -perm -0002 -uid +0
  loop: '{{ search_paths }}'
  changed_when: false
  register: result_found_dirs
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Create List
    of World Writable Directories Not Owned by root
  ansible.builtin.set_fact:
    world_writable_dirs: '{{ world_writable_dirs | union(item.stdout_lines) | list
      }}'
  loop: '{{ result_found_dirs.results }}'
  when: item is not skipped
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure All World-Writable Directories Are Owned by root User - Ensure root
    Ownership on Local World Writable Directories
  ansible.builtin.file:
    path: '{{ item }}'
    owner: root
  loop: '{{ world_writable_dirs }}'
  tags:
  - CCE-95897-5
  - dir_perms_world_writable_root_owned
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Verify that All World-Writable Directories Have Sticky Bits Set   [ref]

When the so-called 'sticky bit' is set on a directory, only the owner of a given file may remove that file from the directory. Without the sticky bit, any user with write access to a directory may remove any file in the directory. Setting the sticky bit prevents users from removing each other's files. In cases where there is no reason for a directory to be world-writable, a better solution is to remove that permission rather than to set the sticky bit. However, if a directory is used by a particular application, consult that application's documentation instead of blindly changing modes.
To set the sticky bit on a world-writable directory DIR, run the following command:
$ sudo chmod +t DIR
        
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 directories present on the system. It is not a problem in most cases, but especially systems with a large number of directories can be affected. See https://access.redhat.com/articles/6999111.
Warning:  Please note that there might be cases where the rule remediation cannot fix directory permissions. This can happen for example when running on a system with some immutable parts. These immutable parts cannot be remediated because they are read-only. Example of such directories can be OStree deployments located at /sysroot/ostree/deploy. In such case, it is needed to make modifications to the underlying ostree snapshot and this is out of scope of regular rule remediation.
Rationale:
Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure.

The only authorized public directories are those temporary directories supplied with the system, or those designed to be temporary file repositories. The setting is normally reserved for directories used by the system, by users for temporary file storage (such as /tmp), and for directories requiring global read/write access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dir_perms_world_writable_sticky_bits
Identifiers:

CCE-95771-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
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
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
os-srgSRG-OS-000138-GPOS-00069
anssiR54
ism1409
pcidss42.2.6, 2.2

df --local -P | awk '{if (NR!=1) print $6}' \
| xargs -I '$6' find '$6' -xdev -type d \
\( -perm -0002 -a ! -perm -1000 \) 2>/dev/null \
-exec chmod a+t {} +

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Verify that All World-Writable Directories Have Sticky Bits Set - Define Excluded
    (Non-Local) File Systems and Paths
  ansible.builtin.set_fact:
    excluded_fstypes:
    - afs
    - autofs
    - ceph
    - cifs
    - smb3
    - smbfs
    - sshfs
    - ncpfs
    - ncp
    - nfs
    - nfs4
    - gfs
    - gfs2
    - glusterfs
    - gpfs
    - pvfs2
    - ocfs2
    - lustre
    - davfs
    - fuse.sshfs
    excluded_paths:
    - dev
    - proc
    - run
    - sys
    search_paths: []
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Find Relevant
    Root Directories Ignoring Pre-Defined Excluded Paths
  ansible.builtin.find:
    paths: /
    file_type: directory
    excludes: '{{ excluded_paths }}'
    hidden: true
    recurse: false
  register: result_relevant_root_dirs
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Include
    Relevant Root Directories in a List of Paths to be Searched
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.path]) }}'
  loop: '{{ result_relevant_root_dirs.files }}'
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Increment
    Search Paths List with Local Partitions Mount Points
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.mount]) }}'
  loop: '{{ ansible_mounts }}'
  when:
  - item.fstype not in excluded_fstypes
  - item.mount != '/'
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Increment
    Search Paths List with Local NFS File System Targets
  ansible.builtin.set_fact:
    search_paths: '{{ search_paths | union([item.device.split('':'')[1]]) }}'
  loop: '{{ ansible_mounts }}'
  when: item.device is search("localhost:")
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Define Rule
    Specific Facts
  ansible.builtin.set_fact:
    world_writable_dirs: []
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Find All
    Uncompliant Directories in Local File Systems
  ansible.builtin.command:
    cmd: find {{ item }} -xdev -type d ( -perm -0002 -a ! -perm -1000 )
  loop: '{{ search_paths }}'
  changed_when: false
  register: result_found_dirs
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Create List
    of World Writable Directories Without Sticky Bit
  ansible.builtin.set_fact:
    world_writable_dirs: '{{ world_writable_dirs | union(item.stdout_lines) | list
      }}'
  loop: '{{ result_found_dirs.results }}'
  when: result_found_dirs is not skipped and item is not skipped
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Verify that All World-Writable Directories Have Sticky Bits Set - Ensure Sticky
    Bit is Set on Local World Writable Directories
  ansible.builtin.file:
    path: '{{ item }}'
    mode: a+t
  loop: '{{ world_writable_dirs }}'
  tags:
  - CCE-95771-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - dir_perms_world_writable_sticky_bits
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Ensure All SGID Executables Are Authorized   [ref]

The SGID (set group id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SGID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SGID files. This configuration check considers authorized SGID files those which were installed via RPM. It is assumed that when an individual has sudo access to install an RPM and all packages are signed with an organizationally-recognized GPG key, the software should be considered an approved package on the system. Any SGID file not deployed through an RPM will be flagged for further review.
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 files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Executable files with the SGID permission run with the privileges of the owner of the file. SGID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_sgid
Identifiers:

CCE-95910-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
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
anssiR56
ism1409

Rule   Ensure All SUID Executables Are Authorized   [ref]

The SUID (set user id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SUID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SUID files. This configuration check considers authorized SUID files those which were installed via RPM. It is assumed that when an individual has sudo access to install an RPM and all packages are signed with an organizationally-recognized GPG key, the software should be considered an approved package on the system. Any SUID file not deployed through an RPM will be flagged for further review.
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 files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Executable files with the SUID permission run with the privileges of the owner of the file. SUID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_suid
Identifiers:

CCE-96423-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
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
anssiR56
ism1409

Rule   Ensure No World-Writable Files Exist   [ref]

It is generally a good idea to remove global (other) write access to a file when it is discovered. However, check with documentation for specific applications before making changes. Also, monitor for recurring world-writable files, as these may be symptoms of a misconfigured application or user account. Finally, this applies to real files and not virtual files that are a part of pseudo file systems such as sysfs or procfs.
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 files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Data in world-writable files can be modified by any user on the system. In almost all circumstances, files can be configured using a combination of user and group permissions to support whatever legitimate access is needed without the risk caused by world-writable files.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_world_writable
Identifiers:

CCE-96142-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
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
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
anssiR54
ism1409
pcidss42.2.6, 2.2

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

FILTER_NODEV=$(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)

# Do not consider /sysroot partition because it contains only the physical
# read-only root on bootable containers.
PARTITIONS=$(findmnt -n -l -k -it $FILTER_NODEV | awk '{ print $1 }' | grep -v "/sysroot")

for PARTITION in $PARTITIONS; do
  find "${PARTITION}" -xdev -type f -perm -002 -exec chmod o-w {} \; 2>/dev/null
done

# Ensure /tmp is also fixed when tmpfs is used.
if grep "^tmpfs /tmp" /proc/mounts; then
  find /tmp -xdev -type f -perm -002 -exec chmod o-w {} \; 2>/dev/null
fi

Rule   Ensure All Files Are Owned by a Group   [ref]

If any file is not group-owned by a valid defined group, the cause of the lack of group-ownership must be investigated. Following this, those files should be deleted or assigned to an appropriate group. The groups need to be defined in /etc/group or in /usr/lib/group if nss-altfiles are configured to be used in /etc/nsswitch.conf. Locate the mount points related to local devices by the following command:
$ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files which do not belong to a valid group using the following command:
$ sudo find MOUNTPOINT -xdev -nogroup 2>/dev/null
Warning:  This rule only considers local groups as valid groups. If you have your groups defined outside /etc/group or /usr/lib/group, the rule won't consider those.
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 files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Unowned files do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_ungroupowned
Identifiers:

CCE-95705-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.06, DSS06.10
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 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.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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
nistCM-6(a), AC-6(1)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR53
pcidss42.2.6, 2.2
suse-base-sle16SLES-16-16016100

Rule   Ensure All Files Are Owned by a User   [ref]

If any files are not owned by a user, then the cause of their lack of ownership should be investigated. Following this, the files should be deleted or assigned to an appropriate user. Locate the mount points related to local devices by the following command:
$ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files which do not belong to a valid user using the following command:
$ sudo find MOUNTPOINT -xdev -nouser 2>/dev/null
Warning:  For this rule to evaluate centralized user accounts, getent must be working properly so that running the command
getent passwd
returns a list of all users in your organization. If using the System Security Services Daemon (SSSD),
enumerate = true
must be configured in your organization's domain to return a complete list of users
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 files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Unowned files do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_files_unowned_by_user
Identifiers:

CCE-95710-0

References:
cis-csc11, 12, 13, 14, 15, 16, 18, 3, 5, 9
cobit5APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, 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.5.1, A.12.6.2, 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.1, 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.AC-6, PR.DS-5, PR.IP-1, PR.PT-3
os-srgSRG-OS-000480-GPOS-00227
anssiR53
pcidss42.2.6, 2.2
suse-base-sle16SLES-16-16016105
Group   Services   Group contains 5 groups and 5 rules
[ref]   The best protection against vulnerable software is running less software. This section describes how to review the software which SUSE Linux Enterprise 16 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default SUSE Linux Enterprise 16 system and provides guidance about which ones can be safely disabled.

SUSE Linux Enterprise 16 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building SUSE Linux Enterprise 16 systems, it is highly recommended to select the minimal packages and then build up the system from there.
Group   DHCP   Group contains 1 group and 1 rule
[ref]   The Dynamic Host Configuration Protocol (DHCP) allows systems to request and obtain an IP address and other configuration parameters from a server.

This guide recommends configuring networking on clients by manually editing the appropriate files under /etc/sysconfig. Use of DHCP can make client systems vulnerable to compromise by rogue DHCP servers, and should be avoided unless necessary. If using DHCP is necessary, however, there are best practices that should be followed to minimize security risk.
Group   Disable DHCP Server   Group contains 1 rule
[ref]   The DHCP server dhcpd is not installed or activated by default. If the software was installed and activated, but the system does not need to act as a DHCP server, it should be disabled and removed.

Rule   Uninstall DHCP Server Package   [ref]

If the system does not need to act as a DHCP server, the dhcp package can be uninstalled. The dhcp package can be removed with the following command:
$ sudo zypper remove dhcp
Rationale:
Removing the DHCP server ensures that it cannot be easily or accidentally reactivated and disrupt network operation.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_dhcp_removed
Identifiers:

CCE-96301-7

References:
cis-csc11, 14, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.IP-1, PR.PT-3
anssiR62
pcidss42.2.4, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

# CAUTION: This remediation script will remove dhcp
# from the system, and may remove any packages
# that depend on dhcp. Execute this
# remediation AFTER testing on a non-production
# system!


if rpm -q --quiet "dhcp" ; then
   zypper remove -y "dhcp"
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: 'Uninstall DHCP Server Package: Ensure dhcp is removed'
  ansible.builtin.package:
    name: dhcp
    state: absent
  tags:
  - CCE-96301-7
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.4
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_dhcp_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

include remove_dhcp

class remove_dhcp {
  package { 'dhcp':
    ensure => 'purged',
  }
}
Group   Obsolete Services   Group contains 2 groups and 4 rules
[ref]   This section discusses a number of network-visible services which have historically caused problems for system security, and for which disabling or severely limiting the service has been the best available guidance for some time. As a result of this, many of these services are not installed as part of SUSE Linux Enterprise 16 by default.

Organizations which are running these services should switch to more secure equivalents as soon as possible. If it remains absolutely necessary to run one of these services for legacy reasons, care should be taken to restrict the service as much as possible, for instance by configuring host firewall software such as iptables to restrict access to the vulnerable service to only those remote hosts which have a known need to use it.
Group   Telnet   Group contains 2 rules
[ref]   The telnet protocol does not provide confidentiality or integrity for information transmitted on the network. This includes authentication information such as passwords. Organizations which use telnet should be actively working to migrate to a more secure protocol.

Rule   Uninstall telnet-server Package   [ref]

The telnet-server package can be removed with the following command:
$ sudo zypper remove telnet-server
Rationale:
It is detrimental for operating systems to provide, or install by default, functionality exceeding requirements or mission objectives. These unnecessary capabilities are often overlooked and therefore may remain insecure. They increase the risk to the platform by providing additional attack vectors.
The telnet service provides an unencrypted remote access service which does not provide for the confidentiality and integrity of user passwords or the remote session. If a privileged user were to login using this service, the privileged user password could be compromised.
Removing the telnet-server package decreases the risk of the telnet service's accidental (or intentional) activation.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_package_telnet-server_removed
Identifiers:

CCE-96043-5

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
pcidssReq-2.2.2
os-srgSRG-OS-000095-GPOS-00049
anssiR62
ism1409
pcidss42.2.4, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

# CAUTION: This remediation script will remove telnet-server
# from the system, and may remove any packages
# that depend on telnet-server. Execute this
# remediation AFTER testing on a non-production
# system!


if rpm -q --quiet "telnet-server" ; then
   zypper remove -y "telnet-server"
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: 'Uninstall telnet-server Package: Ensure telnet-server is removed'
  ansible.builtin.package:
    name: telnet-server
    state: absent
  tags:
  - CCE-96043-5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSS-Req-2.2.2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.4
  - disable_strategy
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - package_telnet-server_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

include remove_telnet-server

class remove_telnet-server {
  package { 'telnet-server':
    ensure => 'purged',
  }
}

Rule   Remove telnet Clients   [ref]

The telnet client allows users to start connections to other systems via the telnet protocol.
Rationale:
The telnet protocol is insecure and unencrypted. The use of an unencrypted transmission medium could allow an unauthorized user to steal credentials. The ssh package provides an encrypted session and stronger security and is included in SUSE Linux Enterprise 16.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_package_telnet_removed
Identifiers:

CCE-96102-9

References:
cui3.1.13
hipaa164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.310(b), 164.312(e)(1), 164.312(e)(2)(ii)
iso27001-2013A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3
anssiR62
ism1409
pcidss42.2.4, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

# CAUTION: This remediation script will remove telnet
# from the system, and may remove any packages
# that depend on telnet. Execute this
# remediation AFTER testing on a non-production
# system!


if rpm -q --quiet "telnet" ; then
   zypper remove -y "telnet"
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: 'Remove telnet Clients: Ensure telnet is removed'
  ansible.builtin.package:
    name: telnet
    state: absent
  tags:
  - CCE-96102-9
  - NIST-800-171-3.1.13
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.4
  - disable_strategy
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - package_telnet_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

include remove_telnet

class remove_telnet {
  package { 'telnet':
    ensure => 'purged',
  }
}
Group   TFTP Server   Group contains 2 rules
[ref]   TFTP is a lightweight version of the FTP protocol which has traditionally been used to configure networking equipment. However, TFTP provides little security, and modern versions of networking operating systems frequently support configuration via SSH or other more secure protocols. A TFTP server should be run only if no more secure method of supporting existing equipment can be found.

Rule   Uninstall tftp-server Package   [ref]

The tftp-server package can be removed with the following command:
 $ sudo zypper remove tftp-server
Rationale:
Removing the tftp-server package decreases the risk of the accidental (or intentional) activation of tftp services.

If TFTP is required for operational support (such as transmission of router configurations), its use must be documented with the Information Systems Security Manager (ISSM), restricted to only authorized personnel, and have access control rules established.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_package_tftp-server_removed
Identifiers:

CCE-96578-0

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 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.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR62
pcidss42.2.4, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

# CAUTION: This remediation script will remove tftp-server
# from the system, and may remove any packages
# that depend on tftp-server. Execute this
# remediation AFTER testing on a non-production
# system!


if rpm -q --quiet "tftp-server" ; then
   zypper remove -y "tftp-server"
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: 'Uninstall tftp-server Package: Ensure tftp-server is removed'
  ansible.builtin.package:
    name: tftp-server
    state: absent
  tags:
  - CCE-96578-0
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.4
  - disable_strategy
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - package_tftp-server_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

include remove_tftp-server

class remove_tftp-server {
  package { 'tftp-server':
    ensure => 'purged',
  }
}

Rule   Remove tftp Daemon   [ref]

Trivial File Transfer Protocol (TFTP) is a simple file transfer protocol, typically used to automatically transfer configuration or boot files between systems. TFTP does not support authentication and can be easily hacked. The package tftp is a client program that allows for connections to a tftp server.
Rationale:
It is recommended that TFTP be removed, unless there is a specific need for TFTP (such as a boot server). In that case, use extreme caution when configuring the services.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_package_tftp_removed
Identifiers:

CCE-96201-9

References:
os-srgSRG-OS-000074-GPOS-00042
anssiR62
pcidss42.2.4, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

# CAUTION: This remediation script will remove tftp
# from the system, and may remove any packages
# that depend on tftp. Execute this
# remediation AFTER testing on a non-production
# system!


if rpm -q --quiet "tftp" ; then
   zypper remove -y "tftp"
fi

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: 'Remove tftp Daemon: Ensure tftp is removed'
  ansible.builtin.package:
    name: tftp
    state: absent
  tags:
  - CCE-96201-9
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.4
  - disable_strategy
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - package_tftp_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable

include remove_tftp

class remove_tftp {
  package { 'tftp':
    ensure => 'purged',
  }
}
Red Hat and Red Hat Enterprise Linux are either registered trademarks or trademarks of Red Hat, Inc. in the United States and other countries. All other names are registered trademarks or trademarks of their respective companies.