Guide to the Secure Configuration of Oracle Linux 9 | OpenSCAP Security Guide

## Guide to the Secure Configuration of Oracle Linux 9

> with profile DRAFT - Unclassified Information in Non-federal Information Systems and Organizations (NIST 800-171)
> From NIST 800-171, Section 2.2: Security requirements for protecting the confidentiality of CUI in nonfederal information systems and organizations have a well-defined structure that consists of: (i) a basic security requirements section; (ii) a derived security requirements section. The basic security requirements are obtained from FIPS Publication 200, which provides the high-level and fundamental security requirements for federal information and information systems. The derived security requirements, which supplement the basic security requirements, are taken from the security controls in NIST Special Publication 800-53. This profile configures Oracle Linux 9 to the NIST Special Publication 800-53 controls identified for securing Controlled Unclassified Information (CUI)."

The SCAP Security Guide Project
https://www.open-scap.org/security-policies/scap-security-guide
This guide presents a catalog of security-relevant configuration settings for Oracle Linux 9. 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 Title | DRAFT - Unclassified Information in Non-federal Information Systems and Organizations (NIST 800-171)
Profile ID | xccdf_org.ssgproject.content_profile_cui

#### CPE Platforms

- cpe:/o:oracle:linux:9

## Revision History

Current version: 0.1.80

- draft (as of 2026-03-06)

## Table of Contents

1. System Settings
   1. Installing and Maintaining Software
   2. Account and Access Control
   3. GRUB2 bootloader configuration
   4. Network Configuration and Firewalls
   5. File Permissions and Masks
   6. SELinux
2. Services
   1. Base Services
   2. Application Whitelisting Daemon
   3. Network Time Protocol
   4. SSH Server
   5. USBGuard daemon
3. System Accounting with auditd
   1. Configure auditd Data Retention
   2. System Accounting with auditd

## Checklist

Group Guide to the Secure Configuration of Oracle Linux 9 Group contains 43 groups and 105 rules
Group System Settings Group contains 31 groups and 61 rules
[ref] Contains rules that check correct system settings.
Group Installing and Maintaining Software Group contains 7 groups and 18 rules
[ref] The following sections contain information on security-relevant choices during the initial operating system installation process and the setup of software updates.
Group System and Software Integrity Group contains 2 groups and 6 rules
[ref] System and software integrity can be gained by installing antivirus, increasing system encryption strength with FIPS, verifying installed software, enabling SELinux, installing an Intrusion Prevention System, etc. However, installing or enabling integrity checking tools cannot prevent intrusions, but they can detect that an intrusion may have occurred. Requirements for integrity checking may be highly dependent on the environment in which the system will be used. Snapshot-based approaches such as AIDE may induce considerable overhead in the presence of frequent software updates.
Group Federal Information Processing Standard (FIPS) Group contains 2 rules
[ref] The Federal Information Processing Standard (FIPS) is a computer security standard which is developed by the U.S. Government and industry working groups to validate the quality of cryptographic modules. The FIPS standard provides four security levels to ensure adequate coverage of different industries, implementation of cryptographic modules, and organizational sizes and requirements. FIPS 140-2 is the current standard for validating that mechanisms used to access cryptographic modules utilize authentication that meets industry and government requirements. For government systems, this allows Security Levels 1, 2, 3, or 4 for use on Oracle Linux 9. See http://csrc.nist.gov/publications/PubsFIPS.html for more information.
#### Rule Enable Dracut FIPS Module [ref]
To enable FIPS mode, run the following command:
    fips-mode-setup --enable
To enable FIPS, the system requires that the `fips` module is added in `dracut` configuration. Check if `/etc/dracut.conf.d/40-fips.conf` contain `add_dracutmodules+=" fips "` Warning: The system needs to be rebooted for these changes to take effect. Warning: System Crypto Modules must be provided by a vendor that undergoes FIPS-140 certifications. FIPS-140 is applicable to all Federal agencies that use cryptographic-based security systems to protect sensitive information in computer and telecommunication systems (including voice systems) as defined in Section 5131 of the Information Technology Management Reform Act of 1996, Public Law 104-106. This standard shall be used in designing and implementing cryptographic modules that Federal departments and agencies operate or are operated for them under contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf To meet this, the system has to have cryptographic software provided by a vendor that has undergone this certification. This means providing documentation, test results, design information, and independent third party review by an accredited lab. While open source software is capable of meeting this, it does not meet FIPS-140 unless the vendor submits to this process.
Rationale: | Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data. The operating system must implement cryptographic modules adhering to the higher standards approved by the federal government since this provides assurance they have been tested and validated.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_enable_dracut_fips_module
References: |
nerc-cip | CIP-003-8 R4.2, CIP-007-3 R5.1
nist | SC-12(2), SC-12(3), IA-7, SC-13, CM-6(a), SC-12
ospp | FCS_RBG_EXT.1
os-srg | SRG-OS-000478-GPOS-00223
ism | 1446
stigid | OL09-00-000070
stigref | SV-271454r1092458_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ "${container:-}" == "bwrap-osbuild" ] ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ) ); then

    fips-mode-setup --enable
    FIPS_CONF="/etc/dracut.conf.d/40-fips.conf"
    if ! grep "^add_dracutmodules+=\" fips \"" $FIPS_CONF; then
        echo "add_dracutmodules+=\" fips \"" >> $FIPS_CONF
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | medium
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000070
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-7
      - NIST-800-53-SC-12
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - enable_dracut_fips_module
      - high_severity
      - medium_complexity
      - medium_disruption
      - reboot_required
      - restrict_strategy

    - name: Check to see the current status of FIPS mode
      ansible.builtin.command: /usr/bin/fips-mode-setup --check
      register: is_fips_enabled
      changed_when: false
      failed_when: false
      when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( lookup("env", "container") ==
        "bwrap-osbuild" ) and ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
        )
      tags:
      - DISA-STIG-OL09-00-000070
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-7
      - NIST-800-53-SC-12
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - enable_dracut_fips_module
      - high_severity
      - medium_complexity
      - medium_disruption
      - reboot_required
      - restrict_strategy

    - name: Enable FIPS mode
      ansible.builtin.command: /usr/bin/fips-mode-setup --enable
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( lookup("env", "container") ==
        "bwrap-osbuild" ) and ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
        )
      - is_fips_enabled.stdout.find('FIPS mode is enabled.') == -1
      tags:
      - DISA-STIG-OL09-00-000070
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-7
      - NIST-800-53-SC-12
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - enable_dracut_fips_module
      - high_severity
      - medium_complexity
      - medium_disruption
      - reboot_required
      - restrict_strategy

    - name: Enable Dracut FIPS Module
      ansible.builtin.lineinfile:
        path: /etc/dracut.conf.d/40-fips.conf
        line: add_dracutmodules+=" fips "
      when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( lookup("env", "container") ==
        "bwrap-osbuild" ) and ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
        )
      tags:
      - DISA-STIG-OL09-00-000070
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-7
      - NIST-800-53-SC-12
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - enable_dracut_fips_module
      - high_severity
      - medium_complexity
      - medium_disruption
      - reboot_required
      - restrict_strategy
#### Rule Enable FIPS Mode [ref]
To enable FIPS mode, run the following command:
    fips-mode-setup --enable
The `fips-mode-setup` command will configure the system in FIPS mode by automatically configuring the following: Setting the kernel FIPS mode flag (`/proc/sys/crypto/fips_enabled`) to `1` Creating `/etc/system-fips` Setting the system crypto policy in `/etc/crypto-policies/config` to `FIPS` Loading the Dracut `fips` module Warning: To configure Oracle Linux 9 to run in FIPS 140 mode, the kernel parameter "fips=1" needs to be added during its installation. Only enabling FIPS 140 mode during the Oracle Linux 9 installation ensures that the system generates all keys with FIPS-approved algorithms and continuous monitoring tests in place. Enabling FIPS mode on a preexisting system involves a number of modifications to it and therefore is not supported. Warning: This rule DOES NOT CHECK if the components of the operating system are FIPS certified. You can find the list of FIPS certified modules at https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules/search. This rule checks if the system is running in FIPS mode.
Rationale: | Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data. The operating system must implement cryptographic modules adhering to the higher standards approved by the federal government since this provides assurance they have been tested and validated.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_enable_fips_mode
References: |
nerc-cip | CIP-003-8 R4.2, CIP-007-3 R5.1
nist | CM-3(6), SC-12(2), SC-12(3), IA-7, SC-13, CM-6(a), SC-12
ospp | FCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1, FCS_RBG_EXT.1
os-srg | SRG-OS-000478-GPOS-00223, SRG-OS-000396-GPOS-00176
ism | 1446
stigid | OL09-00-000070
stigref | SV-271454r1092458_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations]
    fips = true
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( ( ! ( [ "${container:-}" == "bwrap-osbuild" ] ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ) ); then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; }; then
    	cat > /usr/lib/bootc/kargs.d/01-fips.toml << EOF
    kargs = ["fips=1"]
    EOF
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Group System Cryptographic Policies Group contains 4 rules
[ref] Linux has the capability to centrally configure cryptographic polices. The command `update-crypto-policies` is used to set the policy applicable for the various cryptographic back-ends, such as SSL/TLS libraries. The configured cryptographic policies will be the default policy used by these backends unless the application user configures them otherwise. When the system has been configured to use the centralized cryptographic policies, the administrator is assured that any application that utilizes the supported backends will follow a policy that adheres to the configured profile. Currently the supported backends are: GnuTLS library OpenSSL library NSS library OpenJDK Libkrb5 BIND OpenSSH Applications and languages which rely on any of these backends will follow the system policies as well. Examples are apache httpd, nginx, php, and others.
#### Rule Install crypto-policies package [ref]
The `crypto-policies` package can be installed with the following command:
    $ sudo yum install crypto-policies
Rationale: | Centralized cryptographic policies simplify applying secure ciphers across an operating system and the applications that run on that operating system. Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_crypto-policies_installed
References: |
ospp | FCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1
os-srg | SRG-OS-000396-GPOS-00176, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174
stigid | OL09-00-000240
stigref | SV-271477r1091143_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "crypto-policies"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    if ! rpm -q --quiet "crypto-policies" ; then
        yum install -y "crypto-policies"
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=crypto-policies
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Ensure crypto-policies is installed
      ansible.builtin.package:
        name: crypto-policies
        state: present
      tags:
      - DISA-STIG-OL09-00-000240
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_crypto-policies_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_crypto-policies

    class install_crypto-policies {
      package { 'crypto-policies':
        ensure => 'installed',
      }
    }
#### Rule Configure System Cryptography Policy [ref]
To configure the system cryptography policy to use ciphers only from the `FIPS` policy, run the following command:
    $ sudo update-crypto-policies --set FIPS
The rule checks if settings for selected crypto policy are configured as expected. Configuration files in the `/etc/crypto-policies/back-ends` are either symlinks to correct files provided by Crypto-policies package or they are regular files in case crypto policy customizations are applied. Crypto policies may be customized by crypto policy modules, in which case it is delimited from the base policy using a colon. Warning: The system needs to be rebooted for these changes to take effect. Warning: System Crypto Modules must be provided by a vendor that undergoes FIPS-140 certifications. FIPS-140 is applicable to all Federal agencies that use cryptographic-based security systems to protect sensitive information in computer and telecommunication systems (including voice systems) as defined in Section 5131 of the Information Technology Management Reform Act of 1996, Public Law 104-106. This standard shall be used in designing and implementing cryptographic modules that Federal departments and agencies operate or are operated for them under contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf To meet this, the system has to have cryptographic software provided by a vendor that has undergone this certification. This means providing documentation, test results, design information, and independent third party review by an accredited lab. While open source software is capable of meeting this, it does not meet FIPS-140 unless the vendor submits to this process.
Rationale: | Centralized cryptographic policies simplify applying secure ciphers across an operating system and the applications that run on that operating system. Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_configure_crypto_policy
References: |
hipaa | 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
nerc-cip | CIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nist | AC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SC-12(2), SC-12(3)
ospp | FCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1
os-srg | SRG-OS-000396-GPOS-00176, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174
ccn | A.5.SEC-OL4
ism | 1446
pcidss4 | 2.2.7, 2.2
stigid | OL09-00-000070, OL09-00-000241
stigref | SV-271454r1092458_rule, SV-271478r1092620_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    var_system_crypto_policy='FIPS'

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

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

    	false  # end with an error code
    elif test "$rc" != 0; then
    	echo "Error invoking the update-crypto-policies script: $stderr_of_call" >&2
    	false  # end with an error code
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: XCCDF Value var_system_crypto_policy # promote to variable
      set_fact:
        var_system_crypto_policy: !!str FIPS
      tags:
        - always

    - name: Configure System Cryptography Policy - Check current crypto policy (runtime)
      ansible.builtin.command: /usr/bin/update-crypto-policies --show
      register: current_crypto_policy
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000070
      - DISA-STIG-OL09-00-000241
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_crypto_policy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Configure System Cryptography Policy - Get mtime of /etc/crypto-policies/config
      ansible.builtin.stat:
        path: /etc/crypto-policies/config
      register: config_file_stat
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000070
      - DISA-STIG-OL09-00-000241
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_crypto_policy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Configure System Cryptography Policy - Get mtime of /etc/crypto-policies/state/current
      ansible.builtin.stat:
        path: /etc/crypto-policies/state/current
      register: current_file_stat
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000070
      - DISA-STIG-OL09-00-000241
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_crypto_policy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Configure System Cryptography Policy - Check existence of /etc/crypto-policies/back-ends/nss.config
      ansible.builtin.stat:
        path: /etc/crypto-policies/back-ends/nss.config
      register: nss_config_stat
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000070
      - DISA-STIG-OL09-00-000241
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_crypto_policy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Configure System Cryptography Policy - Verify that Crypto Policy is Set (runtime)
      ansible.builtin.command: /usr/bin/update-crypto-policies --set {{ var_system_crypto_policy
        }}
      when: (current_crypto_policy.stdout.strip() != var_system_crypto_policy) or (config_file_stat.stat.exists
        and current_file_stat.stat.exists and config_file_stat.stat.mtime > current_file_stat.stat.mtime)
        or (not nss_config_stat.stat.exists)
      tags:
      - DISA-STIG-OL09-00-000070
      - DISA-STIG-OL09-00-000241
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_crypto_policy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure OpenSSL library to use System Crypto Policy [ref]
Crypto Policies provide a centralized control over crypto algorithms usage of many packages. OpenSSL is supported by crypto policy, but the OpenSSL configuration may be set up to ignore it. To check that Crypto Policies settings are configured correctly, you have to examine the OpenSSL config file available under `/etc/pki/tls/openssl.cnf`. This file has the `ini` format, and it enables crypto policy support if there is a `[ crypto_policy ]` section that contains the `.include = /etc/crypto-policies/back-ends/opensslcnf.config` directive.
Rationale: | Overriding the system crypto policy makes the behavior of the Java runtime violates expectations, and makes system configuration more fragmented.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy
References: |
nerc-cip | CIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nist | AC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SC-12(2), SC-12(3)
ospp | FCS_CKM.1, FCS_CKM.1.1, FCS_CKM.2, FCS_COP.1/ENCRYPT, FCS_COP.1/HASH, FCS_COP.1/SIGN, FCS_COP.1/KEYHMAC, FCS_TLSC_EXT.1, FCS_TLSC_EXT.1.1
pcidss | Req-2.2
os-srg | SRG-OS-000250-GPOS-00093
Remediation Shell script ⇲
    OPENSSL_CRYPTO_POLICY_SECTION='[ crypto_policy ]'
    OPENSSL_CRYPTO_POLICY_SECTION_REGEX='\[\s*crypto_policy\s*\]'

    OPENSSL_CRYPTO_POLICY_INCLUSION='.include = /etc/crypto-policies/back-ends/opensslcnf.config'

    OPENSSL_CRYPTO_POLICY_INCLUSION_REGEX='^\s*\.include\s*(?:=\s*)?/etc/crypto-policies/back-ends/opensslcnf.config$'

    function remediate_openssl_crypto_policy() {
    	CONFIG_FILE=/etc/pki/tls/openssl.cnf
    	if test -f "$CONFIG_FILE"; then
    		if ! grep -q "^\\s*$OPENSSL_CRYPTO_POLICY_SECTION_REGEX" "$CONFIG_FILE"; then
    			printf '\n%s\n\n%s' "$OPENSSL_CRYPTO_POLICY_SECTION" "$OPENSSL_CRYPTO_POLICY_INCLUSION" >> "$CONFIG_FILE"
    			return 0
    		elif ! grep -q "^\\s*$OPENSSL_CRYPTO_POLICY_INCLUSION_REGEX" "$CONFIG_FILE"; then
    			sed -i "s|$OPENSSL_CRYPTO_POLICY_SECTION_REGEX|&\\n\\n$OPENSSL_CRYPTO_POLICY_INCLUSION\\n|" "$CONFIG_FILE"
    			return 0
    		fi
    	else
    		echo "Aborting remediation as '$CONFIG_FILE' was not even found." >&2
    		return 1
    	fi
    }

    remediate_openssl_crypto_policy
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Configure OpenSSL library to use System Crypto Policy - Search for crypto_policy
        Section
      ansible.builtin.find:
        paths: /etc/pki/tls
        patterns: openssl.cnf
        contains: ^\s*\[\s*crypto_policy\s*]
      register: test_crypto_policy_group
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - configure_openssl_crypto_policy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Configure OpenSSL library to use System Crypto Policy - Search for crypto_policy
        Section Together With .include Directive
      ansible.builtin.find:
        paths: /etc/pki/tls
        patterns: openssl.cnf
        contains: ^\s*\.include\s*(?:=\s*)?/etc/crypto-policies/back-ends/opensslcnf.config$
      register: test_crypto_policy_include_directive
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - configure_openssl_crypto_policy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Configure OpenSSL library to use System Crypto Policy - Add .include Line
        for opensslcnf.config File in crypto_policy Section
      ansible.builtin.lineinfile:
        create: true
        insertafter: ^\s*\[\s*crypto_policy\s*]\s*
        line: .include = /etc/crypto-policies/back-ends/opensslcnf.config
        path: /etc/pki/tls/openssl.cnf
      when:
      - test_crypto_policy_group.matched > 0
      - test_crypto_policy_include_directive.matched == 0
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - configure_openssl_crypto_policy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Configure OpenSSL library to use System Crypto Policy - Add crypto_policy
        Section With .include for opensslcnf.config File
      ansible.builtin.lineinfile:
        create: true
        line: |-
          [crypto_policy]
          .include = /etc/crypto-policies/back-ends/opensslcnf.config
        path: /etc/pki/tls/openssl.cnf
      when: test_crypto_policy_group.matched == 0
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-12(2)
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - configure_openssl_crypto_policy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Configure SSH to use System Crypto Policy [ref]
Crypto Policies provide a centralized control over crypto algorithms usage of many packages. SSH is supported by crypto policy, but the SSH configuration may be set up to ignore it. To check that Crypto Policies settings are configured correctly, ensure that the `CRYPTO_POLICY` variable is either commented or not set at all in the `/etc/sysconfig/sshd`.
Rationale: | Overriding the system crypto policy makes the behavior of the SSH service violate expectations, and makes system configuration more fragmented.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_configure_ssh_crypto_policy
References: |
hipaa | 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii)
nerc-cip | CIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1
nist | AC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13
ospp | FCS_SSH_EXT.1, FCS_SSHS_EXT.1, FCS_SSHC_EXT.1
pcidss | Req-2.2
os-srg | SRG-OS-000250-GPOS-00093
ccn | A.5.SEC-OL6
ism | 0418
pcidss4 | 2.2.7, 2.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    SSH_CONF="/etc/sysconfig/sshd"

    sed -i "/^\s*CRYPTO_POLICY.*$/Id" $SSH_CONF

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_ssh_crypto_policy
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Configure SSH to use System Crypto Policy
      ansible.builtin.lineinfile:
        dest: /etc/sysconfig/sshd
        state: absent
        regexp: (?i)^\s*CRYPTO_POLICY.*$
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-17(2)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-MA-4(6)
      - NIST-800-53-SC-13
      - PCI-DSS-Req-2.2
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.7
      - configure_ssh_crypto_policy
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
Group Disk Partitioning Group contains 1 rule
[ref] To ensure separation and protection of data, there are top-level system directories which should be placed on their own physical partition or logical volume. The installer's default partitioning scheme creates separate logical volumes for `/`, `/boot`, and `swap`. If starting with any of the default layouts, check the box to \"Review and modify partitioning.\" This allows for the easy creation of additional logical volumes inside the volume group already created, though it may require making `/`'s logical volume smaller to create space. In general, using logical volumes is preferable to using partitions because they can be more easily adjusted later. If creating a custom layout, create the partitions mentioned in the previous paragraph (which the installer will require anyway), as well as separate ones described in the following sections. If a system has already been installed, and the default partitioning scheme was used, it is possible but nontrivial to modify it to create separate logical volumes for the directories listed above. The Logical Volume Manager (LVM) makes this possible.
#### Rule Ensure /var/log/audit Located On Separate Partition [ref]
Audit logs are stored in the `/var/log/audit` directory. Ensure that `/var/log/audit` has its own partition or logical volume at installation time, or migrate it using LVM. Make absolutely certain that it is large enough to store all audit logs that will be created by the auditing daemon.
Rationale: | Placing `/var/log/audit` in its own partition enables better separation between audit files and other files, and helps ensure that auditing cannot be halted due to the partition running out of space.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_var_log_audit
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 8
cobit5 | APO11.04, APO13.01, BAI03.05, BAI04.04, DSS05.02, DSS05.04, DSS05.07, MEA02.01
hipaa | 164.312(a)(2)(ii)
isa-62443-2009 | 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013 | SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, 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.2, SR 7.6
iso27001-2013 | A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.17.2.1
nerc-cip | CIP-007-3 R6.5
nist | CM-6(a), AU-4, SC-5(2)
nist-csf | PR.DS-4, PR.PT-1, PR.PT-4
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000341-GPOS-00132, SRG-OS-000480-GPOS-00227
app-srg-ctr | SRG-APP-000357-CTR-000800
anssi | R71
stigid | OL09-00-000002
stigref | SV-271432r1091008_rule
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | enable
    part /var/log/audit
Group Sudo Group contains 1 rule
[ref] `Sudo`, which stands for "su 'do'", provides the ability to delegate authority to certain users, groups of users, or system administrators. When configured for system users and/or groups, `Sudo` can allow a user or group to execute privileged commands that normally only `root` is allowed to execute. For more information on `Sudo` and addition `Sudo` configuration options, see https://www.sudo.ws.
#### Rule Install sudo Package [ref]
The `sudo` package can be installed with the following command:
    $ sudo yum install sudo
Rationale: | `sudo` is a program designed to allow a system administrator to give limited root privileges to users and log root activity. The basic philosophy is to give as few privileges as possible but still allow system users to get their work done.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_sudo_installed
References: |
nist | CM-6(a)
ospp | FMT_MOF_EXT.1
os-srg | SRG-OS-000324-GPOS-00125
anssi | R33
ism | 1386
pcidss4 | 2.2.6, 2.2
stigid | OL09-00-000230
stigref | SV-271474r1091134_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "sudo"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=sudo
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000230
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_sudo_installed

    - name: Ensure sudo is installed
      ansible.builtin.package:
        name: sudo
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000230
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_sudo_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_sudo

    class install_sudo {
      package { 'sudo':
        ensure => 'installed',
      }
    }
Group System Tooling / Utilities Group contains 3 rules
[ref] The following checks evaluate the system for recommended base packages -- both for installation and removal.
#### Rule Ensure gnutls-utils is installed [ref]
The `gnutls-utils` package can be installed with the following command:
    $ sudo yum install gnutls-utils
Rationale: | GnuTLS is a secure communications library implementing the SSL, TLS and DTLS protocols and technologies around them. It provides a simple C language application programming interface (API) to access the secure communications protocols as well as APIs to parse and write X.509, PKCS #12, OpenPGP and other required structures. This package contains command line TLS client and server and certificate manipulation tools.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_gnutls-utils_installed
References: |
ospp | FIA_X509_EXT.1, FIA_X509_EXT.1.1, FIA_X509_EXT.2
os-srg | SRG-OS-000480-GPOS-00227
stigid | OL09-00-000430
stigref | SV-271518r1091266_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "gnutls-utils"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    if ! rpm -q --quiet "gnutls-utils" ; then
        yum install -y "gnutls-utils"
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=gnutls-utils
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Ensure gnutls-utils is installed
      ansible.builtin.package:
        name: gnutls-utils
        state: present
      tags:
      - DISA-STIG-OL09-00-000430
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_gnutls-utils_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_gnutls-utils

    class install_gnutls-utils {
      package { 'gnutls-utils':
        ensure => 'installed',
      }
    }
#### Rule Install openscap-scanner Package [ref]
The `openscap-scanner` package can be installed with the following command:
    $ sudo yum install openscap-scanner
Rationale: | `openscap-scanner` contains the `oscap` command line tool. This tool is a configuration and vulnerability scanner, capable of performing compliance checking using SCAP content.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_openscap-scanner_installed
References: |
ospp | AGD_PRE.1, AGD_OPE.1
os-srg | SRG-OS-000480-GPOS-00227, SRG-OS-000191-GPOS-00080
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "openscap-scanner"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    if ! rpm -q --quiet "openscap-scanner" ; then
        yum install -y "openscap-scanner"
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=openscap-scanner
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Ensure openscap-scanner is installed
      ansible.builtin.package:
        name: openscap-scanner
        state: present
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_openscap-scanner_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_openscap-scanner

    class install_openscap-scanner {
      package { 'openscap-scanner':
        ensure => 'installed',
      }
    }
#### Rule Install scap-security-guide Package [ref]
The `scap-security-guide` package can be installed with the following command:
    $ sudo yum install scap-security-guide
Rationale: | The `scap-security-guide` package provides a guide for configuration of the system from the final system's security point of view. The guidance is specified in the Security Content Automation Protocol (SCAP) format and constitutes a catalog of practical hardening advice, linked to government requirements where applicable. The SCAP Security Guide project bridges the gap between generalized policy requirements and specific implementation guidelines. A system administrator can use the `oscap` CLI tool from the `openscap-scanner` package, or the SCAP Workbench GUI tool from the `scap-workbench` package, to verify that the system conforms to provided guidelines. Refer to the scap-security-guide(8) manual page for further information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_scap-security-guide_installed
References: |
ospp | AGD_PRE.1, AGD_OPE.1
os-srg | SRG-OS-000480-GPOS-00227
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "scap-security-guide"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    if ! rpm -q --quiet "scap-security-guide" ; then
        yum install -y "scap-security-guide"
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=scap-security-guide
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Ensure scap-security-guide is installed
      ansible.builtin.package:
        name: scap-security-guide
        state: present
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_scap-security-guide_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_scap-security-guide

    class install_scap-security-guide {
      package { 'scap-security-guide':
        ensure => 'installed',
      }
    }
Group Updating Software Group contains 7 rules
[ref] The `yum` 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. Oracle Linux 9 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `yum` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.
#### Rule Install dnf-automatic Package [ref]
The `dnf-automatic` package can be installed with the following command:
    $ sudo yum install dnf-automatic
Rationale: | `dnf-automatic` is an alternative command line interface (CLI) to `dnf upgrade` suitable for automatic, regular execution.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_dnf-automatic_installed
References: |
ospp | FPT_TUD_EXT.1, FPT_TUD_EXT.2
os-srg | SRG-OS-000191-GPOS-00080
anssi | R61
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "dnf-automatic"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if ( ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) ); then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=dnf-automatic
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_dnf-automatic_installed

    - name: Ensure dnf-automatic is installed
      ansible.builtin.package:
        name: dnf-automatic
        state: present
      when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_dnf-automatic_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_dnf-automatic

    class install_dnf-automatic {
      package { 'dnf-automatic':
        ensure => 'installed',
      }
    }
#### Rule Configure dnf-automatic to Install Available Updates Automatically [ref]
To ensure that the packages comprising the available updates will be automatically installed by `dnf-automatic`, set `apply_updates` to `yes` under `[commands]` section in `/etc/dnf/automatic.conf`.
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. The automated installation of updates ensures that recent security patches are applied in a timely manner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dnf-automatic_apply_updates
References: |
nist | SI-2(5), CM-6(a), SI-2(c)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000805-GPOS-00260
anssi | R61
ism | 1467, 1483, 1493
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) ); then

    found=false

    # set value in all files if they contain section or key
    for f in $(echo -n "/etc/dnf/automatic.conf"); do
        if [ ! -e "$f" ]; then
            continue
        fi

        # find key in section and change value
        if grep -qzosP "(?m)^[[:space:]]*\[commands\]([^\n\[]*\n+)+?[[:space:]]*apply_updates" "$f"; then
            if ! grep -qzosP "(?m)^[[:space:]]*apply_updates[[:space:]]*=[[:space:]]*yes" "$f"; then

                sed -i "/^[[:space:]]*apply_updates/s/\([[:blank:]]*=[[:blank:]]*\).*/\1yes/" "$f"

            fi

            found=true

        # find section and add key = value to it
        elif grep -qs "^[[:space:]]*\[commands\]" "$f"; then

                sed -i "/^[[:space:]]*\[commands\]/a apply_updates=yes" "$f"

                found=true
        fi
    done

    # if section not in any file, append section with key = value to FIRST file in files parameter
    if ! $found ; then
        file=$(echo "/etc/dnf/automatic.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[commands]\napply_updates=yes" >> "$file"

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
#### Rule Ensure gpgcheck Enabled In Main yum Configuration [ref]
The `gpgcheck` option controls whether RPM packages' signatures are always checked prior to installation. To configure yum to check package signatures before installing them, ensure the following line appears in `/etc/yum.conf` in the `[main]` section:
    gpgcheck=1
Rationale: | Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization. Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
References: |
cis-csc | 11, 2, 3, 9
cjis | 5.10.4.1
cobit5 | APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui | 3.4.8
hipaa | 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-2009 | 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013 | SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013 | A.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
nist | CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csf | PR.DS-6, PR.DS-8, PR.IP-1
ospp | FPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidss | Req-6.2
os-srg | SRG-OS-000366-GPOS-00153
anssi | R59
ism | 1493
pcidss4 | 6.3.3, 6.3
stigid | OL09-00-000497
stigref | SV-271524r1091284_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q yum; 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/yum.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/yum.conf"
    else
        if [[ -s "/etc/yum.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/yum.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/yum.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/yum.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.10.4.1
      - DISA-STIG-OL09-00-000497
      - 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/yum.conf
        section: main
        option: gpgcheck
        value: 1
        no_extra_spaces: true
        create: false
      when: '"yum" in ansible_facts.packages'
      tags:
      - CJIS-5.10.4.1
      - DISA-STIG-OL09-00-000497
      - 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]
`yum` should be configured to verify the signature(s) of local packages prior to installation. To configure `yum` to verify signatures of local packages, set the `localpkg_gpgcheck` to `1` in `/etc/yum.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
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui | 3.4.8
hipaa | 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-2009 | 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013 | SR 7.6
iso27001-2013 | A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nist | CM-11(a), CM-11(b), CM-6(a), CM-5(3), SA-12, SA-12(10)
nist-csf | PR.IP-1
ospp | FPT_TUD_EXT.1, FPT_TUD_EXT.2
os-srg | SRG-OS-000366-GPOS-00153
anssi | R59
ism | 1493
stigid | OL09-00-000496
stigref | SV-271523r1091281_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q yum; 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/yum.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/yum.conf"
    else
        if [[ -s "/etc/yum.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/yum.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/yum.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/yum.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000496
      - 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 (yum)
      block:

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

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

      - name: Ensure GPG check Enabled for Local Packages (yum)
        community.general.ini_file:
          dest: '{{ pkg_config_file_symlink |  default("/etc/yum.conf") }}'
          section: main
          option: localpkg_gpgcheck
          value: 1
          no_extra_spaces: true
          create: true
      when: '"yum" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000496
      - 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 yum Package Repositories [ref]
To ensure signature checking is not disabled for any repos, remove any lines from files in `/etc/yum.repos.d` of the form:
    gpgcheck=0
Rationale: | Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA)."
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_never_disabled
References: |
cis-csc | 11, 2, 3, 9
cjis | 5.10.4.1
cobit5 | APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui | 3.4.8
hipaa | 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-2009 | 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013 | SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013 | A.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
nist | CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csf | PR.DS-6, PR.DS-8, PR.IP-1
ospp | FPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidss | Req-6.2
os-srg | SRG-OS-000366-GPOS-00153
anssi | R59
ism | 1493
pcidss4 | 6.3.3, 6.3
stigid | OL09-00-000498
stigref | SV-271525r1091287_rule
Remediation Shell script ⇲
    sed -i 's/gpgcheck\s*=.*/gpgcheck=1/g' /etc/yum.repos.d/*
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | enable
    - name: Grep for yum repo section names
      ansible.builtin.shell: |
        set -o pipefail
        grep -HEr '^\[.+\]' -r /etc/yum.repos.d/
      register: repo_grep_results
      failed_when: repo_grep_results.rc not in [0, 1]
      changed_when: false
      tags:
      - CJIS-5.10.4.1
      - DISA-STIG-OL09-00-000498
      - 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 yum 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:
      - CJIS-5.10.4.1
      - DISA-STIG-OL09-00-000498
      - 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 Oracle Linux GPG Key Installed [ref]
To ensure the system can cryptographically verify base software packages come from Oracle (and to connect to the Unbreakable Linux Network to receive them), the Oracle GPG key must properly be installed. To install the Oracle GPG key, run:
    $ sudo uln_register
If the system is not connected to the Internet, then install the Oracle GPG key from trusted media such as the Oracle installation CD-ROM or DVD. Assuming the disc is mounted in `/media/cdrom`, use the following command as the root user to import it into the keyring:
    $ sudo rpm --import /media/cdrom/RPM-GPG-KEY-oracle
Alternatively, the key may be pre-loaded during the Oracle installation. In such cases, the key can be installed by running the following command:
    sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
Rationale: | Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Oracle GPG key is necessary to cryptographically verify packages are from Oracle.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_oracle_gpgkey_installed
References: |
cis-csc | 11, 2, 3, 9
cobit5 | APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
isa-62443-2009 | 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013 | SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013 | A.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
nist | CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), CM-11(a), CM-11(b)
nist-csf | PR.DS-6, PR.DS-8, PR.IP-1
pcidss | Req-6.2
anssi | R59
ism | 1493
stigid | OL09-00-000499
stigref | SV-271526r1092460_rule
Remediation Shell script ⇲
    # OL fingerprints below retrieved from: https://linux.oracle.com/security/gpg/#gpg
    readonly OL_RELEASE_FINGERPRINT="3E6D826D3FBAB389C2F38E34BC4D06A08D8B756F"
    readonly OL_AUXILIARY_FINGERPRINT="982231759C7467065D0CE9B2A7DD07088B4EFBE6"

    FINGERPRINTS_REGEX="${OL_RELEASE_FINGERPRINT}"

    if [[ -n "$OL_AUXILIARY_FINGERPRINT" ]]; then
        FINGERPRINTS_REGEX+="|${OL_AUXILIARY_FINGERPRINT}"
    fi

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

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

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

        readarray -t GPG_OUT < <(gpg --show-keys --with-fingerprint --with-colons "$OL_RELEASE_KEY" | grep -A1 "^pub" | grep "^fpr" | cut -d ":" -f 10)

      GPG_RESULT=$?
      # No CRC error, safe to proceed
      if [ "${GPG_RESULT}" -eq "0" ]
      then
        # Filter just hexadecimal fingerprints from gpg's output from
        # processing of a key file
        echo "${GPG_OUT[*]}" | grep -vE "$FINGERPRINTS_REGEX" || {
          # If $ OL_RELEASE_KEY file doesn't contain any keys with unknown fingerprint, import it
          rpm --import "${OL_RELEASE_KEY}"
        }
      fi
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | medium
Reboot: | false
Strategy: | restrict
    - name: Ensure Oracle Linux GPG Key Installed - Read GPG key directory permission
      ansible.builtin.stat:
        path: /etc/pki/rpm-gpg/
      register: gpg_key_directory_permission
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000499
      - 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-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - ensure_oracle_gpgkey_installed
      - high_severity
      - medium_complexity
      - medium_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Oracle Linux GPG Key Installed - Retrieve GPG key fingerprints information
      ansible.builtin.command: gpg --show-keys --with-fingerprint --with-colons "/etc/pki/rpm-gpg/RPM-GPG-KEY-oracle"
      changed_when: false
      register: gpg_fingerprints
      check_mode: false
      tags:
      - DISA-STIG-OL09-00-000499
      - 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-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - ensure_oracle_gpgkey_installed
      - high_severity
      - medium_complexity
      - medium_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Oracle Linux GPG Key Installed - Set fact for installed fingerprints
      ansible.builtin.set_fact:
        gpg_installed_fingerprints: |-
          {{ gpg_fingerprints.stdout | regex_findall('^pub.*
          (?:^fpr[:]*)([0-9A-Fa-f]*)', '\1') | list }}
      tags:
      - DISA-STIG-OL09-00-000499
      - 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-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - ensure_oracle_gpgkey_installed
      - high_severity
      - medium_complexity
      - medium_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Oracle Linux GPG Key Installed - Set fact for valid fingerprints
      ansible.builtin.set_fact:
        gpg_valid_fingerprints:
        - 3E6D826D3FBAB389C2F38E34BC4D06A08D8B756F
        - 982231759C7467065D0CE9B2A7DD07088B4EFBE6
      tags:
      - DISA-STIG-OL09-00-000499
      - 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-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - ensure_oracle_gpgkey_installed
      - high_severity
      - medium_complexity
      - medium_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Oracle Linux GPG Key Installed - Import Oracle GPG key securely
      ansible.builtin.rpm_key:
        state: present
        key: /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
      when:
      - gpg_key_directory_permission.stat.mode <= '0755'
      - (gpg_installed_fingerprints | difference(gpg_valid_fingerprints)) | length ==
        0
      - gpg_installed_fingerprints | length > 0
      tags:
      - DISA-STIG-OL09-00-000499
      - 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-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - ensure_oracle_gpgkey_installed
      - high_severity
      - medium_complexity
      - medium_disruption
      - no_reboot_needed
      - restrict_strategy
#### Rule Enable dnf-automatic Timer [ref]
The `dnf-automatic` timer can be enabled with the following command:
    $ sudo systemctl enable dnf-automatic.timer
Rationale: | The `dnf-automatic` is an alternative command line interface (CLI) to `dnf upgrade` with specific facilities to make it suitable to be executed automatically and regularly from systemd timers, cron jobs and similar. The tool is controlled by `dnf-automatic.timer` SystemD timer.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_timer_dnf-automatic_enabled
References: |
nist | SI-2(5), CM-6(a), SI-2(c)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000191-GPOS-00080
anssi | R61
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if ( ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) ); then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" start 'dnf-automatic.timer'
    fi
    "$SYSTEMCTL_EXEC" enable 'dnf-automatic.timer'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-2(5)
      - NIST-800-53-SI-2(c)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - timer_dnf-automatic_enabled

    - name: Enable timer dnf-automatic
      block:

      - name: Gather the package facts
        ansible.builtin.package_facts:
          manager: auto

      - name: Enable timer dnf-automatic
        ansible.builtin.systemd:
          name: dnf-automatic.timer
          enabled: 'yes'
          state: started
        when:
        - '"dnf-automatic" in ansible_facts.packages'
      when: ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-2(5)
      - NIST-800-53-SI-2(c)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - timer_dnf-automatic_enabled
Group Account and Access Control Group contains 8 groups and 16 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 Oracle Linux 9.
Group Protect Accounts by Configuring PAM Group contains 3 groups and 8 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 3 rules
[ref] The `pam_faillock` PAM module provides the capability to lock out user accounts after a number of failed login attempts. Its documentation is available in `/usr/share/doc/pam-VERSION/txts/README.pam_faillock`. Warning: Locking out user accounts presents the risk of a denial-of-service attack. The lockout policy must weigh whether the risk of such a denial-of-service attack outweighs the benefits of thwarting password guessing attacks.
#### Rule Lock Accounts After Failed Password Attempts [ref]
This rule configures the system to lock out accounts after a number of incorrect login attempts using `pam_faillock.so`. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. Ensure that the file `/etc/security/faillock.conf` contains the following entry: `deny = <count>` Where count should be less than or equal to 3 and greater than 0. In order to avoid errors when manually editing these files, it is recommended to use the appropriate tools, such as `authselect` or `authconfig`, depending on the OS version. Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the `/etc/security/faillock.conf` file, the pam_faillock parameters should be defined in `faillock.conf` file.
Rationale: | By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny
References: |
cis-csc | 1, 12, 15, 16
cjis | 5.5.3
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.8
isa-62443-2009 | 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013 | A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nist | CM-6(a), AC-7(a)
nist-csf | PR.AC-7
ospp | FIA_AFL.1
pcidss | Req-8.1.6
os-srg | SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssi | R31
ccn | A.30.SEC-OL1
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.4, 8.3
stigid | OL09-00-003020
stigref | SV-271839r1092229_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q pam; }; then

    var_accounts_passwords_pam_faillock_deny='3'

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

    authselect apply-changes -b
    else

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
            sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
            sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
        fi
        sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
    done

    fi

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    SKIP_FAILLOCK_CHECK=false

    FAILLOCK_CONF="/etc/security/faillock.conf"
    if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
        regex="^\s*deny\s*="
        line="deny = $var_accounts_passwords_pam_faillock_deny"
        if ! grep -q $regex $FAILLOCK_CONF; then
            echo $line >> $FAILLOCK_CONF
        else
            sed -i --follow-symlinks 's|^\s*\(deny\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_deny"'|g' $FAILLOCK_CONF
        fi

        for pam_file in "${AUTH_FILES[@]}"
        do
            if [ -e "$pam_file" ] ; then
                PAM_FILE_PATH="$pam_file"
                if [ -f /usr/bin/authselect ]; then

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

                    CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                    # If not already in use, a custom profile is created preserving the enabled features.
                    if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                        ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                        # The "local" profile does not contain essential security features required by multiple Benchmarks.
                        # If currently used, it is replaced by "sssd", which is the best option in this case.
                        if [[ $CURRENT_PROFILE == local ]]; then
                            CURRENT_PROFILE="sssd"
                        fi
                        authselect create-profile hardening -b $CURRENT_PROFILE
                        CURRENT_PROFILE="custom/hardening"

                        authselect apply-changes -b --backup=before-hardening-custom-profile
                        authselect select $CURRENT_PROFILE
                        for feature in $ENABLED_FEATURES; do
                            authselect enable-feature $feature;
                        done

                        authselect apply-changes -b --backup=after-hardening-custom-profile
                    fi
                    PAM_FILE_NAME=$(basename "$pam_file")
                    PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                    authselect apply-changes -b
                fi

            if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\bdeny\b" "$PAM_FILE_PATH"; then
                sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\bdeny\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
            fi
                if [ -f /usr/bin/authselect ]; then

                    authselect apply-changes -b
                fi
            else
                echo "$pam_file was not found" >&2
            fi
        done

    else
        for pam_file in "${AUTH_FILES[@]}"
        do
            if ! grep -qE '^\s*auth.*pam_faillock\.so\s+(preauth|authfail).*deny' "$pam_file"; then
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ deny='"$var_accounts_passwords_pam_faillock_deny"'/' "$pam_file"
            else
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*\)\('"deny"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"deny"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_deny"'\3/' "$pam_file"
            fi
        done
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Lock Accounts After Failed Password Attempts - Check if system relies on authselect
        tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

      - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
          current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        check_mode: false
        failed_when: false

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

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

      - name: Lock Accounts After Failed Password Attempts - Ensure "with-faillock" feature
          is enabled using authselect tool
        ansible.builtin.command:
          cmd: authselect enable-feature with-faillock
        register: result_authselect_enable_feature_cmd
        when:
        - result_authselect_check_cmd is success
        - result_authselect_features.stdout is not search("with-faillock")

      - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_enable_feature_cmd is not skipped
        - result_authselect_enable_feature_cmd is success
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

      - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so preauth
          editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so preauth
          insertbefore: ^auth.*sufficient.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so authfail
          editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so authfail
          insertbefore: ^auth.*required.*pam_deny\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Lock Accounts After Failed Password Attempts - Enable pam_faillock.so account
          section editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: account     required      pam_faillock.so
          insertbefore: ^account.*required.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_passwords_pam_faillock_deny # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_deny: !!str 3
      tags:
        - always

    - name: Lock Accounts After Failed Password Attempts - Check the presence of /etc/security/faillock.conf
        file
      ansible.builtin.stat:
        path: /etc/security/faillock.conf
      register: result_faillock_conf_check
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

      - name: Lock Accounts After Failed Password Attempts - Check if /etc/pam.d/system-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/system-auth
        register: result_pam_auth_file_present

      - name: Lock Accounts After Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

        - name: Lock Accounts After Failed Password Attempts - Check if system relies
            on authselect tool
          ansible.builtin.stat:
            path: /usr/bin/authselect
          register: result_authselect_present

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

          - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
              current profile
            ansible.builtin.command:
              cmd: authselect check
            register: result_authselect_check_cmd
            changed_when: false
            check_mode: false
            failed_when: false

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

          - name: Lock Accounts After Failed Password Attempts - Get authselect current
              profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Lock Accounts After Failed Password Attempts - Define the current authselect
              profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Lock Accounts After Failed Password Attempts - Define the new authselect
              custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Lock Accounts After Failed Password Attempts - Check if any custom profile
              with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Lock Accounts After Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Lock Accounts After Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Lock Accounts After Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Lock Accounts After Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Lock Accounts After Failed Password Attempts - Change the PAM file to
              be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Lock Accounts After Failed Password Attempts - Define a fact for control
            already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Lock Accounts After Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

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

        - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_auth_file_present.stat.exists

      - name: Lock Accounts After Failed Password Attempts - Check if /etc/pam.d/password-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/password-auth
        register: result_pam_password_auth_file_present

      - name: Lock Accounts After Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

        - name: Lock Accounts After Failed Password Attempts - Check if system relies
            on authselect tool
          ansible.builtin.stat:
            path: /usr/bin/authselect
          register: result_authselect_present

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

          - name: Lock Accounts After Failed Password Attempts - Check integrity of authselect
              current profile
            ansible.builtin.command:
              cmd: authselect check
            register: result_authselect_check_cmd
            changed_when: false
            check_mode: false
            failed_when: false

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

          - name: Lock Accounts After Failed Password Attempts - Get authselect current
              profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Lock Accounts After Failed Password Attempts - Define the current authselect
              profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Lock Accounts After Failed Password Attempts - Define the new authselect
              custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Lock Accounts After Failed Password Attempts - Check if any custom profile
              with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Lock Accounts After Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Lock Accounts After Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Lock Accounts After Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Lock Accounts After Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Lock Accounts After Failed Password Attempts - Change the PAM file to
              be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Lock Accounts After Failed Password Attempts - Define a fact for control
            already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Lock Accounts After Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

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

        - name: Lock Accounts After Failed Password Attempts - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_password_auth_file_present.stat.exists
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

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

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

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

      - name: Lock Accounts After Failed Password Attempts - Ensure the desired value
          for pam_faillock.so authfail deny parameter in auth section
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          backrefs: true
          regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(deny)=[0-9]+(.*)
          line: \1required\3\4={{ var_accounts_passwords_pam_faillock_deny }}\5
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_deny_parameter_is_present.found > 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-003020
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set Interval For Counting Failed Password Attempts [ref]
Utilizing `pam_faillock.so`, the `fail_interval` directive configures the system to lock out an account after a number of incorrect login attempts within a specified time period. Ensure that the file `/etc/security/faillock.conf` contains the following entry: `fail_interval = <interval-in-seconds>` where `interval-in-seconds` is `900` or greater. In order to avoid errors when manually editing these files, it is recommended to use the appropriate tools, such as `authselect` or `authconfig`, depending on the OS version. Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the `/etc/security/faillock.conf` file, the pam_faillock parameters should be defined in `faillock.conf` file.
Rationale: | By limiting the number of failed logon attempts the risk of unauthorized system access via user password guessing, otherwise known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
isa-62443-2009 | 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013 | A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nist | CM-6(a), AC-7(a)
nist-csf | PR.AC-7
ospp | FIA_AFL.1
os-srg | SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssi | R31
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
stigid | OL09-00-002416
stigref | SV-271754r1091974_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q pam; }; then

    var_accounts_passwords_pam_faillock_fail_interval='900'

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

    authselect apply-changes -b
    else

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
            sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
            sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
        fi
        sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
    done

    fi

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    SKIP_FAILLOCK_CHECK=false

    FAILLOCK_CONF="/etc/security/faillock.conf"
    if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
        regex="^\s*fail_interval\s*="
        line="fail_interval = $var_accounts_passwords_pam_faillock_fail_interval"
        if ! grep -q $regex $FAILLOCK_CONF; then
            echo $line >> $FAILLOCK_CONF
        else
            sed -i --follow-symlinks 's|^\s*\(fail_interval\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_fail_interval"'|g' $FAILLOCK_CONF
        fi

        for pam_file in "${AUTH_FILES[@]}"
        do
            if [ -e "$pam_file" ] ; then
                PAM_FILE_PATH="$pam_file"
                if [ -f /usr/bin/authselect ]; then

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

                    CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                    # If not already in use, a custom profile is created preserving the enabled features.
                    if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                        ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                        # The "local" profile does not contain essential security features required by multiple Benchmarks.
                        # If currently used, it is replaced by "sssd", which is the best option in this case.
                        if [[ $CURRENT_PROFILE == local ]]; then
                            CURRENT_PROFILE="sssd"
                        fi
                        authselect create-profile hardening -b $CURRENT_PROFILE
                        CURRENT_PROFILE="custom/hardening"

                        authselect apply-changes -b --backup=before-hardening-custom-profile
                        authselect select $CURRENT_PROFILE
                        for feature in $ENABLED_FEATURES; do
                            authselect enable-feature $feature;
                        done

                        authselect apply-changes -b --backup=after-hardening-custom-profile
                    fi
                    PAM_FILE_NAME=$(basename "$pam_file")
                    PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                    authselect apply-changes -b
                fi

            if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\bfail_interval\b" "$PAM_FILE_PATH"; then
                sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\bfail_interval\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
            fi
                if [ -f /usr/bin/authselect ]; then

                    authselect apply-changes -b
                fi
            else
                echo "$pam_file was not found" >&2
            fi
        done

    else
        for pam_file in "${AUTH_FILES[@]}"
        do
            if ! grep -qE '^\s*auth.*pam_faillock\.so\s+(preauth|authfail).*fail_interval' "$pam_file"; then
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*/ s/$/ fail_interval='"$var_accounts_passwords_pam_faillock_fail_interval"'/' "$pam_file"
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ fail_interval='"$var_accounts_passwords_pam_faillock_fail_interval"'/' "$pam_file"
            else
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*\)\('"fail_interval"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_fail_interval"'\3/' "$pam_file"
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"fail_interval"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_fail_interval"'\3/' "$pam_file"
            fi
        done
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Check if system relies
        on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Remediation where authselect
        tool is present
      block:

      - name: Set Interval For Counting Failed Password Attempts - Check integrity of
          authselect current profile
        ansible.builtin.command:
          cmd: authselect check
        register: result_authselect_check_cmd
        changed_when: false
        check_mode: false
        failed_when: false

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

      - name: Set Interval For Counting Failed Password Attempts - Get authselect current
          features
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        check_mode: false
        when:
        - result_authselect_check_cmd is success

      - name: Set Interval For Counting Failed Password Attempts - Ensure "with-faillock"
          feature is enabled using authselect tool
        ansible.builtin.command:
          cmd: authselect enable-feature with-faillock
        register: result_authselect_enable_feature_cmd
        when:
        - result_authselect_check_cmd is success
        - result_authselect_features.stdout is not search("with-faillock")

      - name: Set Interval For Counting Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_enable_feature_cmd is not skipped
        - result_authselect_enable_feature_cmd is success
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Remediation where authselect
        tool is not present
      block:

      - name: Set Interval For Counting Failed Password Attempts - Check if pam_faillock.so
          is already enabled
        ansible.builtin.lineinfile:
          path: /etc/pam.d/system-auth
          regexp: .*auth.*pam_faillock\.so (preauth|authfail)
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_faillock_is_enabled

      - name: Set Interval For Counting Failed Password Attempts - Enable pam_faillock.so
          preauth editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so preauth
          insertbefore: ^auth.*sufficient.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Set Interval For Counting Failed Password Attempts - Enable pam_faillock.so
          authfail editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so authfail
          insertbefore: ^auth.*required.*pam_deny\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Set Interval For Counting Failed Password Attempts - Enable pam_faillock.so
          account section editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: account     required      pam_faillock.so
          insertbefore: ^account.*required.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_passwords_pam_faillock_fail_interval # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_fail_interval: !!str 900
      tags:
        - always

    - name: Set Interval For Counting Failed Password Attempts - Check the presence of
        /etc/security/faillock.conf file
      ansible.builtin.stat:
        path: /etc/security/faillock.conf
      register: result_faillock_conf_check
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Ensure the pam_faillock.so
        fail_interval parameter in /etc/security/faillock.conf
      ansible.builtin.lineinfile:
        path: /etc/security/faillock.conf
        regexp: ^\s*fail_interval\s*=
        line: fail_interval = {{ var_accounts_passwords_pam_faillock_fail_interval }}
        state: present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Ensure the pam_faillock.so
        fail_interval parameter not in PAM files
      block:

      - name: Set Interval For Counting Failed Password Attempts - Check if /etc/pam.d/system-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/system-auth
        register: result_pam_auth_file_present

      - name: Set Interval For Counting Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

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

        - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
            custom profile is used if authselect is present
          block:

          - name: Set Interval For Counting Failed Password Attempts - Check integrity
              of authselect current profile
            ansible.builtin.command:
              cmd: authselect check
            register: result_authselect_check_cmd
            changed_when: false
            check_mode: false
            failed_when: false

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

          - name: Set Interval For Counting Failed Password Attempts - Get authselect
              current profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Set Interval For Counting Failed Password Attempts - Define the current
              authselect profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Set Interval For Counting Failed Password Attempts - Define the new
              authselect custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Set Interval For Counting Failed Password Attempts - Check if any custom
              profile with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Set Interval For Counting Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Interval For Counting Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
              changes are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Interval For Counting Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Interval For Counting Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
              changes are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Set Interval For Counting Failed Password Attempts - Change the PAM
              file to be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Set Interval For Counting Failed Password Attempts - Define a fact for
            control already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Set Interval For Counting Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

        - name: Set Interval For Counting Failed Password Attempts - Ensure the "fail_interval"
            option from "pam_faillock.so" is not present in {{ pam_file_path }}
          ansible.builtin.replace:
            dest: '{{ pam_file_path }}'
            regexp: (.*auth.*pam_faillock.so.*)\bfail_interval\b=?[0-9a-zA-Z]*(.*)
            replace: \1\2
          register: result_pam_option_removal
          when:
          - result_pam_file_present.stat.exists

        - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_auth_file_present.stat.exists

      - name: Set Interval For Counting Failed Password Attempts - Check if /etc/pam.d/password-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/password-auth
        register: result_pam_password_auth_file_present

      - name: Set Interval For Counting Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

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

        - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
            custom profile is used if authselect is present
          block:

          - name: Set Interval For Counting Failed Password Attempts - Check integrity
              of authselect current profile
            ansible.builtin.command:
              cmd: authselect check
            register: result_authselect_check_cmd
            changed_when: false
            check_mode: false
            failed_when: false

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

          - name: Set Interval For Counting Failed Password Attempts - Get authselect
              current profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Set Interval For Counting Failed Password Attempts - Define the current
              authselect profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Set Interval For Counting Failed Password Attempts - Define the new
              authselect custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Set Interval For Counting Failed Password Attempts - Check if any custom
              profile with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Set Interval For Counting Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Interval For Counting Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
              changes are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Interval For Counting Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Interval For Counting Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
              changes are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Set Interval For Counting Failed Password Attempts - Change the PAM
              file to be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Set Interval For Counting Failed Password Attempts - Define a fact for
            control already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Set Interval For Counting Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

        - name: Set Interval For Counting Failed Password Attempts - Ensure the "fail_interval"
            option from "pam_faillock.so" is not present in {{ pam_file_path }}
          ansible.builtin.replace:
            dest: '{{ pam_file_path }}'
            regexp: (.*auth.*pam_faillock.so.*)\bfail_interval\b=?[0-9a-zA-Z]*(.*)
            replace: \1\2
          register: result_pam_option_removal
          when:
          - result_pam_file_present.stat.exists

        - name: Set Interval For Counting Failed Password Attempts - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_password_auth_file_present.stat.exists
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interval For Counting Failed Password Attempts - Ensure the pam_faillock.so
        fail_interval parameter in PAM files
      block:

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

      - name: Set Interval For Counting Failed Password Attempts - Ensure the inclusion
          of pam_faillock.so preauth fail_interval parameter in auth section
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          backrefs: true
          regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so preauth.*)
          line: \1required\3 fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval
            }}
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_fail_interval_parameter_is_present.found == 0

      - name: Set Interval For Counting Failed Password Attempts - Ensure the inclusion
          of pam_faillock.so authfail fail_interval parameter in auth section
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          backrefs: true
          regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)
          line: \1required\3 fail_interval={{ var_accounts_passwords_pam_faillock_fail_interval
            }}
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_fail_interval_parameter_is_present.found == 0

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

      - name: Set Interval For Counting Failed Password Attempts - Ensure the desired
          value for pam_faillock.so authfail fail_interval parameter in auth section
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          backrefs: true
          regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(fail_interval)=[0-9]+(.*)
          line: \1required\3\4={{ var_accounts_passwords_pam_faillock_fail_interval }}\5
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_fail_interval_parameter_is_present.found > 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_faillock_conf_check.stat.exists
      tags:
      - DISA-STIG-OL09-00-002416
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - accounts_passwords_pam_faillock_interval
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set Lockout Time for Failed Password Attempts [ref]
This rule configures the system to lock out accounts during a specified time period after a number of incorrect login attempts using `pam_faillock.so`. Ensure that the file `/etc/security/faillock.conf` contains the following entry: `unlock_time=<interval-in-seconds>` where `interval-in-seconds` is `0` or greater. pam_faillock.so module requires multiple entries in pam files. These entries must be carefully defined to work as expected. In order to avoid any errors when manually editing these files, it is recommended to use the appropriate tools, such as `authselect` or `authconfig`, depending on the OS version. If `unlock_time` is set to `0`, manual intervention by an administrator is required to unlock a user. This should be done using the `faillock` tool. Warning: If the system supports the new `/etc/security/faillock.conf` file but the pam_faillock.so parameters are defined directly in `/etc/pam.d/system-auth` and `/etc/pam.d/password-auth`, the remediation will migrate the `unlock_time` parameter to `/etc/security/faillock.conf` to ensure compatibility with `authselect` tool. The parameters `deny` and `fail_interval`, if used, also have to be migrated by their respective remediation. Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. If the system supports the `/etc/security/faillock.conf` file, the pam_faillock parameters should be defined in `faillock.conf` file.
Rationale: | By limiting the number of failed logon attempts the risk of unauthorized system access via user password guessing, otherwise known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time
References: |
cis-csc | 1, 12, 15, 16
cjis | 5.5.3
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.8
isa-62443-2009 | 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013 | A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nist | CM-6(a), AC-7(b)
nist-csf | PR.AC-7
ospp | FIA_AFL.1
pcidss | Req-8.1.7
os-srg | SRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
anssi | R31
ccn | A.30.SEC-OL1
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.4, 8.3
stigid | OL09-00-002417
stigref | SV-271755r1091977_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q pam; }; then

    var_accounts_passwords_pam_faillock_unlock_time='0'

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

    authselect apply-changes -b
    else

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    for pam_file in "${AUTH_FILES[@]}"
    do
        if ! grep -qE '^\s*auth\s+required\s+pam_faillock\.so\s+(preauth silent|authfail).*$' "$pam_file" ; then
            sed -i --follow-symlinks '/^auth.*sufficient.*pam_unix\.so.*/i auth        required      pam_faillock.so preauth silent' "$pam_file"
            sed -i --follow-symlinks '/^auth.*required.*pam_deny\.so.*/i auth        required      pam_faillock.so authfail' "$pam_file"
            sed -i --follow-symlinks '/^account.*required.*pam_unix\.so.*/i account     required      pam_faillock.so' "$pam_file"
        fi
        sed -Ei 's/(auth.*)(\[default=die\])(.*pam_faillock\.so)/\1required     \3/g' "$pam_file"
    done

    fi

    AUTH_FILES=("/etc/pam.d/system-auth" "/etc/pam.d/password-auth")
    SKIP_FAILLOCK_CHECK=false

    FAILLOCK_CONF="/etc/security/faillock.conf"
    if [ -f $FAILLOCK_CONF ] || [ "$SKIP_FAILLOCK_CHECK" = "true" ]; then
        regex="^\s*unlock_time\s*="
        line="unlock_time = $var_accounts_passwords_pam_faillock_unlock_time"
        if ! grep -q $regex $FAILLOCK_CONF; then
            echo $line >> $FAILLOCK_CONF
        else
            sed -i --follow-symlinks 's|^\s*\(unlock_time\s*=\s*\)\(\S\+\)|\1'"$var_accounts_passwords_pam_faillock_unlock_time"'|g' $FAILLOCK_CONF
        fi

        for pam_file in "${AUTH_FILES[@]}"
        do
            if [ -e "$pam_file" ] ; then
                PAM_FILE_PATH="$pam_file"
                if [ -f /usr/bin/authselect ]; then

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

                    CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                    # If not already in use, a custom profile is created preserving the enabled features.
                    if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                        ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                        # The "local" profile does not contain essential security features required by multiple Benchmarks.
                        # If currently used, it is replaced by "sssd", which is the best option in this case.
                        if [[ $CURRENT_PROFILE == local ]]; then
                            CURRENT_PROFILE="sssd"
                        fi
                        authselect create-profile hardening -b $CURRENT_PROFILE
                        CURRENT_PROFILE="custom/hardening"

                        authselect apply-changes -b --backup=before-hardening-custom-profile
                        authselect select $CURRENT_PROFILE
                        for feature in $ENABLED_FEATURES; do
                            authselect enable-feature $feature;
                        done

                        authselect apply-changes -b --backup=after-hardening-custom-profile
                    fi
                    PAM_FILE_NAME=$(basename "$pam_file")
                    PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

                    authselect apply-changes -b
                fi

            if grep -qP "^\s*auth\s.*\bpam_faillock.so\s.*\bunlock_time\b" "$PAM_FILE_PATH"; then
                sed -i -E --follow-symlinks "s/(.*auth.*pam_faillock.so.*)\bunlock_time\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
            fi
                if [ -f /usr/bin/authselect ]; then

                    authselect apply-changes -b
                fi
            else
                echo "$pam_file was not found" >&2
            fi
        done

    else
        for pam_file in "${AUTH_FILES[@]}"
        do
            if ! grep -qE '^\s*auth.*pam_faillock\.so\s+(preauth|authfail).*unlock_time' "$pam_file"; then
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*preauth.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
                sed -i --follow-symlinks '/^auth.*required.*pam_faillock\.so.*authfail.*/ s/$/ unlock_time='"$var_accounts_passwords_pam_faillock_unlock_time"'/' "$pam_file"
            else
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*preauth.*\)\('"unlock_time"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
                sed -i --follow-symlinks 's/\(^auth.*required.*pam_faillock\.so.*authfail.*\)\('"unlock_time"'=\)\S\+\b\(.*\)/\1\2'"$var_accounts_passwords_pam_faillock_unlock_time"'\3/' "$pam_file"
            fi
        done
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Lockout Time for Failed Password Attempts - Check if system relies on
        authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

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

      - name: Set Lockout Time for Failed Password Attempts - Get authselect current features
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        check_mode: false
        when:
        - result_authselect_check_cmd is success

      - name: Set Lockout Time for Failed Password Attempts - Ensure "with-faillock" feature
          is enabled using authselect tool
        ansible.builtin.command:
          cmd: authselect enable-feature with-faillock
        register: result_authselect_enable_feature_cmd
        when:
        - result_authselect_check_cmd is success
        - result_authselect_features.stdout is not search("with-faillock")

      - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_enable_feature_cmd is not skipped
        - result_authselect_enable_feature_cmd is success
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

      - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so preauth
          editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so preauth
          insertbefore: ^auth.*sufficient.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so authfail
          editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: auth        required      pam_faillock.so authfail
          insertbefore: ^auth.*required.*pam_deny\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0

      - name: Set Lockout Time for Failed Password Attempts - Enable pam_faillock.so account
          section editing PAM files
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          line: account     required      pam_faillock.so
          insertbefore: ^account.*required.*pam_unix\.so.*
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_is_enabled.found == 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_passwords_pam_faillock_unlock_time # promote to variable
      set_fact:
        var_accounts_passwords_pam_faillock_unlock_time: !!str 0
      tags:
        - always

    - name: Set Lockout Time for Failed Password Attempts - Check the presence of /etc/security/faillock.conf
        file
      ansible.builtin.stat:
        path: /etc/security/faillock.conf
      register: result_faillock_conf_check
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

      - name: Set Lockout Time for Failed Password Attempts - Check if /etc/pam.d/system-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/system-auth
        register: result_pam_auth_file_present

      - name: Set Lockout Time for Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

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

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

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

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

          - name: Set Lockout Time for Failed Password Attempts - Get authselect current
              profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Set Lockout Time for Failed Password Attempts - Define the current authselect
              profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Set Lockout Time for Failed Password Attempts - Define the new authselect
              custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Set Lockout Time for Failed Password Attempts - Check if any custom
              profile with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Set Lockout Time for Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Lockout Time for Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Lockout Time for Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Lockout Time for Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Set Lockout Time for Failed Password Attempts - Change the PAM file
              to be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Set Lockout Time for Failed Password Attempts - Define a fact for control
            already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Set Lockout Time for Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

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

        - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_auth_file_present.stat.exists

      - name: Set Lockout Time for Failed Password Attempts - Check if /etc/pam.d/password-auth
          file is present
        ansible.builtin.stat:
          path: /etc/pam.d/password-auth
        register: result_pam_password_auth_file_present

      - name: Set Lockout Time for Failed Password Attempts - Check the proper remediation
          for the system
        block:

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

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

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

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

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

          - name: Set Lockout Time for Failed Password Attempts - Get authselect current
              profile
            ansible.builtin.shell:
              cmd: authselect current -r | awk '{ print $1 }'
            register: result_authselect_profile
            changed_when: false
            when:
            - result_authselect_check_cmd is success

          - name: Set Lockout Time for Failed Password Attempts - Define the current authselect
              profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is match("custom/")

          - name: Set Lockout Time for Failed Password Attempts - Define the new authselect
              custom profile as a local fact
            ansible.builtin.set_fact:
              authselect_current_profile: '{{ result_authselect_profile.stdout }}'
              authselect_custom_profile: custom/hardening
            when:
            - result_authselect_profile is not skipped
            - result_authselect_profile.stdout is not match("custom/")

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

          - name: Set Lockout Time for Failed Password Attempts - Check if any custom
              profile with the same name was already created
            ansible.builtin.stat:
              path: /etc/authselect/{{ authselect_custom_profile }}
            register: result_authselect_custom_profile_present
            changed_when: false
            when:
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")

          - name: Set Lockout Time for Failed Password Attempts - Create an authselect
              custom profile based on the current profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b {{ authselect_current_profile
                }}
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is not match("^(custom/|local)")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Lockout Time for Failed Password Attempts - Create an authselect
              custom profile based on sssd profile
            ansible.builtin.command:
              cmd: authselect create-profile hardening -b sssd
            when:
            - result_authselect_profile is not skipped
            - result_authselect_check_cmd is success
            - authselect_current_profile is match("local")
            - not result_authselect_custom_profile_present.stat.exists

          - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Lockout Time for Failed Password Attempts - Ensure the authselect
              custom profile is selected
            ansible.builtin.command:
              cmd: authselect select {{ authselect_custom_profile }}
            register: result_pam_authselect_select_profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - authselect_current_profile is not match("custom/")
            - authselect_custom_profile is not match(authselect_current_profile)

          - name: Set Lockout Time for Failed Password Attempts - Restore the authselect
              features in the custom profile
            ansible.builtin.command:
              cmd: authselect enable-feature {{ item }}
            loop: '{{ result_authselect_features.stdout_lines }}'
            register: result_pam_authselect_restore_features
            when:
            - result_authselect_profile is not skipped
            - result_authselect_features is not skipped
            - result_pam_authselect_select_profile is not skipped

          - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
              are applied
            ansible.builtin.command:
              cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
            when:
            - result_authselect_check_cmd is success
            - result_authselect_profile is not skipped
            - result_pam_authselect_restore_features is not skipped

          - name: Set Lockout Time for Failed Password Attempts - Change the PAM file
              to be edited according to the custom authselect profile
            ansible.builtin.set_fact:
              pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
                | basename }}
            when:
            - authselect_custom_profile is defined
          when:
          - result_authselect_present.stat.exists

        - name: Set Lockout Time for Failed Password Attempts - Define a fact for control
            already filtered in case filters are used
          ansible.builtin.set_fact:
            pam_module_control: ''

        - name: Set Lockout Time for Failed Password Attempts - Check if {{ pam_file_path
            }} file is present
          ansible.builtin.stat:
            path: '{{ pam_file_path }}'
          register: result_pam_file_present

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

        - name: Set Lockout Time for Failed Password Attempts - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present.stat.exists
          - result_pam_option_removal is changed
        when:
        - result_pam_password_auth_file_present.stat.exists
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

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

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

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

      - name: Set Lockout Time for Failed Password Attempts - Ensure the desired value
          for pam_faillock.so authfail unlock_time parameter in auth section
        ansible.builtin.lineinfile:
          path: '{{ item }}'
          backrefs: true
          regexp: (^\s*auth\s+)([\w\[].*\b)(\s+pam_faillock.so authfail.*)(unlock_time)=[0-9]+(.*)
          line: \1required\3\4={{ var_accounts_passwords_pam_faillock_unlock_time }}\5
          state: present
        loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
        when:
        - result_pam_faillock_unlock_time_parameter_is_present.found > 0
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"pam" in ansible_facts.packages'
      - not result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - DISA-STIG-OL09-00-002417
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(b)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.7
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_unlock_time
      - 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 with pam_pwquality Group contains 5 rules
[ref] The `pam_pwquality` PAM module can be configured to meet requirements for a variety of policies. For example, to configure `pam_pwquality` to require at least one uppercase character, lowercase character, digit, and other (special) character, make sure that `pam_pwquality` exists in `/etc/pam.d/system-auth`:
    password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
If no such line exists, add one as the first line of the password section in `/etc/pam.d/system-auth`. Next, modify the settings in `/etc/security/pwquality.conf` to match the following:
    difok = 4
    minlen = 14
    dcredit = -1
    ucredit = -1
    lcredit = -1
    ocredit = -1
    maxrepeat = 3
The arguments can be modified to ensure compliance with your organization's security policy. Discussion of each parameter follows.
#### Rule Ensure PAM Enforces Password Requirements - Minimum Digit Characters [ref]
The pam_pwquality module's `dcredit` parameter controls requirements for usage of digits in a password. When set to a negative number, any password will be required to contain that many digits. When set to a positive number, pam_pwquality will grant +1 additional length credit for each digit. Modify the `dcredit` setting in `/etc/security/pwquality.conf` to require the use of a digit in passwords.
Rationale: | Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised. Requiring digits makes password guessing attacks more difficult by ensuring a larger search space.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit
References: |
cis-csc | 1, 12, 15, 16, 5
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013 | A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
ospp | FMT_SMF_EXT.1
pcidss | Req-8.2.3
os-srg | SRG-OS-000071-GPOS-00039
anssi | R31
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.6, 8.3
stigid | OL09-00-001020
stigref | SV-271616r1091560_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q libpwquality; }; then

    var_password_pam_dcredit='-1'

    if grep -sq dcredit /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/dcredit/d" /etc/security/pwquality.conf.d/*.conf
    fi

    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bdcredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bdcredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bdcredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bdcredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi

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

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_dcredit # promote to variable
      set_fact:
        var_password_pam_dcredit: !!str -1
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Find
        pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
        dcredit is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bdcredit\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
        if /etc/pam.d/system-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
        the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Define
          the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
          if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Define
          a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
          if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          the "dcredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bdcredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
        if /etc/pam.d/password-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_password_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
        the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Define
          the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
          if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters -
            Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Define
          a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Check
          if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          the "dcredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bdcredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
          authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Digit Characters - Ensure
        PAM variable dcredit is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*dcredit
        line: dcredit = {{ var_password_pam_dcredit }}
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001020
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_dcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters [ref]
The pam_pwquality module's `lcredit` parameter controls requirements for usage of lowercase letters in a password. When set to a negative number, any password will be required to contain that many lowercase characters. When set to a positive number, pam_pwquality will grant +1 additional length credit for each lowercase character. Modify the `lcredit` setting in `/etc/security/pwquality.conf` to require the use of a lowercase character in passwords.
Rationale: | Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised. Requiring a minimum number of lowercase characters makes password guessing attacks more difficult by ensuring a larger search space.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit
References: |
cis-csc | 1, 12, 15, 16, 5
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013 | A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
ospp | FMT_SMF_EXT.1
pcidss | Req-8.2.3
os-srg | SRG-OS-000070-GPOS-00038
anssi | R31
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.6, 8.3
stigid | OL09-00-001015
stigref | SV-271615r1091557_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q libpwquality; }; then

    var_password_pam_lcredit='-1'

    if grep -sq lcredit /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/lcredit/d" /etc/security/pwquality.conf.d/*.conf
    fi

    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\blcredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\blcredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\blcredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\blcredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi

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

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_lcredit # promote to variable
      set_fact:
        var_password_pam_lcredit: !!str -1
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Find pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Ensure lcredit is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\blcredit\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Check if /etc/pam.d/system-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Check the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure the "lcredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\blcredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Check if /etc/pam.d/password-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_password_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Check the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure the "lcredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\blcredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
          - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters -
        Ensure PAM variable lcredit is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*lcredit
        line: lcredit = {{ var_password_pam_lcredit }}
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001015
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_lcredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Length [ref]
The pam_pwquality module's `minlen` parameter controls requirements for minimum characters required in a password. Add `minlen=12` after pam_pwquality to set minimum password length requirements.
Rationale: | The shorter the password, the lower the number of possible combinations that need to be tested before the password is compromised. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromise the password.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.6.2.1.1
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013 | A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
ospp | FMT_SMF_EXT.1
pcidss | Req-8.2.3
os-srg | SRG-OS-000078-GPOS-00046
anssi | R31, R68
ccn | A.11.SEC-OL3
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.6, 8.3
stigid | OL09-00-001105
stigref | SV-271633r1091611_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q libpwquality; }; then

    var_password_pam_minlen='12'

    if grep -sq minlen /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/minlen/d" /etc/security/pwquality.conf.d/*.conf
    fi

    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bminlen\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bminlen\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bminlen\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bminlen\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi

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

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_minlen # promote to variable
      set_fact:
        var_password_pam_minlen: !!str 12
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Find pwquality.conf.d
        files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure minlen
        is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bminlen\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if /etc/pam.d/system-auth
        file is present
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check the proper
        remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
          PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if system
          relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
          custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check integrity
            of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Get authselect
            current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
            current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
            new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if
            any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Create an
            authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Create an
            authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure the
            authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Restore the
            authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Change the
            PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define a fact
          for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if {{
          pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure the
          "minlen" option from "pam_pwquality.so" is not present in {{ pam_file_path }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bminlen\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if /etc/pam.d/password-auth
        file is present
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_password_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check the proper
        remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
          PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if system
          relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
          custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check integrity
            of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Get authselect
            current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
            current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define the
            new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if
            any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Create an
            authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Create an
            authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure the
            authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Restore the
            authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Length - Change the
            PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Define a fact
          for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Check if {{
          pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure the
          "minlen" option from "pam_pwquality.so" is not present in {{ pam_file_path }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bminlen\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure authselect
          changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Length - Ensure PAM variable
        minlen is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*minlen
        line: minlen = {{ var_password_pam_minlen }}
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - DISA-STIG-OL09-00-001105
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Special Characters [ref]
The pam_pwquality 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_pwquality will grant +1 additional length credit for each special character. Modify the `ocredit` setting in `/etc/security/pwquality.conf` to equal -1 to require use of a special character in passwords.
Rationale: | Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised. 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_accounts_password_pam_ocredit
References: |
cis-csc | 1, 12, 15, 16, 5
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013 | A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000266-GPOS-00101
anssi | R31
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
stigid | OL09-00-001120
stigref | SV-271636r1091620_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q libpwquality; }; then

    var_password_pam_ocredit='-1'

    if grep -sq ocredit /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/ocredit/d" /etc/security/pwquality.conf.d/*.conf
    fi

    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bocredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bocredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bocredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bocredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi

    # 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' <<< "^ocredit")

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

    # 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 "^ocredit\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^ocredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
    else
        if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - 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: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Find
        pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Ensure
        ocredit is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bocredit\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Check
        if /etc/pam.d/system-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Check
        the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure the "ocredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bocredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Check
        if /etc/pam.d/password-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_password_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Check
        the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure the "ocredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bocredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters -
          Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Special Characters - Ensure
        PAM variable ocredit is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*ocredit
        line: ocredit = {{ var_password_pam_ocredit }}
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001120
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_ocredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters [ref]
The pam_pwquality 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_pwquality will grant +1 additional length credit for each uppercase character. Modify the `ucredit` setting in `/etc/security/pwquality.conf` to require the use of an uppercase character in passwords.
Rationale: | Use of a complex password helps to increase the time and resources required to compromise the password. Password complexity, or strength, is a measure of the effectiveness of a password in resisting attempts at guessing and brute-force attacks. Password complexity is one factor of several that determines how long it takes to crack a password. The more complex the password, the greater the number of possible combinations that need to be tested before the password is compromised.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit
References: |
cis-csc | 1, 12, 15, 16, 5
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013 | A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
ospp | FMT_SMF_EXT.1
pcidss | Req-8.2.3
os-srg | SRG-OS-000069-GPOS-00037, SRG-OS-000070-GPOS-00038
anssi | R31
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
stigid | OL09-00-001005
stigref | SV-271613r1091551_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q libpwquality; }; then

    var_password_pam_ucredit='-1'

    if grep -sq ucredit /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/ucredit/d" /etc/security/pwquality.conf.d/*.conf
    fi

    if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/system-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bucredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bucredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
    if [ -e "/etc/pam.d/password-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/password-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # The "local" profile does not contain essential security features required by multiple Benchmarks.
                # If currently used, it is replaced by "sssd", which is the best option in this case.
                if [[ $CURRENT_PROFILE == local ]]; then
                    CURRENT_PROFILE="sssd"
                fi
                authselect create-profile hardening -b $CURRENT_PROFILE
                CURRENT_PROFILE="custom/hardening"

                authselect apply-changes -b --backup=before-hardening-custom-profile
                authselect select $CURRENT_PROFILE
                for feature in $ENABLED_FEATURES; do
                    authselect enable-feature $feature;
                done

                authselect apply-changes -b --backup=after-hardening-custom-profile
            fi
            PAM_FILE_NAME=$(basename "/etc/pam.d/password-auth")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

    if grep -qP "^\s*password\s.*\bpam_pwquality.so\s.*\bucredit\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*pam_pwquality.so.*)\bucredit\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/password-auth was not found" >&2
    fi

    # 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' <<< "^ucredit")

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

    # 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 "^ucredit\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^ucredit\\>.*/$escaped_formatted_output/gi" "/etc/security/pwquality.conf"
    else
        if [[ -s "/etc/security/pwquality.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/security/pwquality.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/security/pwquality.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/security/pwquality.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - 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: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Find pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Ensure ucredit is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bucredit\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Check if /etc/pam.d/system-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/system-auth
      register: result_pam_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Check the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure the "ucredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bucredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Check if /etc/pam.d/password-auth file is present
      ansible.builtin.stat:
        path: /etc/pam.d/password-auth
      register: result_pam_password_auth_file_present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Check the proper remediation for the system
      block:

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Define the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Check if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Check integrity of authselect current profile
          ansible.builtin.command:
            cmd: authselect check
          register: result_authselect_check_cmd
          changed_when: false
          check_mode: false
          failed_when: false

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Get authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Define the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Define the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Check if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Create an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is not match("^(custom/|local)")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Create an authselect custom profile based on sssd profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b sssd
          when:
          - result_authselect_profile is not skipped
          - result_authselect_check_cmd is success
          - authselect_current_profile is match("local")
          - not result_authselect_custom_profile_present.stat.exists

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Restore the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Ensure authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
            - Change the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Define a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Check if {{ pam_file_path }} file is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure the "ucredit" option from "pam_pwquality.so" is not present in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_pwquality.so.*)\bucredit\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters
          - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_option_removal is changed
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Uppercase Characters -
        Ensure PAM variable ucredit is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*ucredit
        line: ucredit = {{ var_password_pam_ucredit }}
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-001005
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - accounts_password_pam_ucredit
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group Protect Physical Console Access Group contains 5 rules
[ref] It is impossible to fully protect a system from an attacker with physical access, so securing the space in which the system is located should be considered a necessary step. However, there are some steps which, if taken, make it more difficult for an attacker to quickly or undetectably modify a system from its console.
#### Rule Disable debug-shell SystemD Service [ref]
SystemD's `debug-shell` service is intended to diagnose SystemD related boot issues with various `systemctl` commands. Once enabled and following a system reboot, the root shell will be available on `tty9` which is access by pressing `CTRL-ALT-F9`. The `debug-shell` service should only be used for SystemD related issues and should otherwise be disabled. By default, the `debug-shell` SystemD service is already disabled. The `debug-shell` service can be disabled with the following command:
    $ sudo systemctl mask --now debug-shell.service
Rationale: | This prevents attackers with physical access from trivially bypassing security on the machine through valid troubleshooting configurations and gaining root access when the system is rebooted.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_debug-shell_disabled
References: |
cui | 3.4.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
nist | CM-6
ospp | FIA_UAU.1
os-srg | SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227
stigid | OL09-00-002403
stigref | SV-271742r1091938_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    masked = ["debug-shell"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" stop 'debug-shell.service'
    fi
    "$SYSTEMCTL_EXEC" disable 'debug-shell.service'
    "$SYSTEMCTL_EXEC" mask 'debug-shell.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" -q list-unit-files debug-shell.socket; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop 'debug-shell.socket'
        fi
        "$SYSTEMCTL_EXEC" mask 'debug-shell.socket'
    fi
    # The service may not be running because it has been started and failed,
    # so let's reset the state so OVAL checks pass.
    # Service should be 'inactive', not 'failed' after reboot though.
    "$SYSTEMCTL_EXEC" reset-failed 'debug-shell.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002403
      - NIST-800-171-3.4.5
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_debug-shell_disabled

    - name: Disable debug-shell SystemD Service - Disable service debug-shell
      block:

      - name: Disable debug-shell SystemD Service - Collect systemd Services Present in
          the System
        ansible.builtin.command: systemctl -q list-unit-files --type service
        register: service_exists
        changed_when: false
        failed_when: service_exists.rc not in [0, 1]
        check_mode: false

      - name: Disable debug-shell SystemD Service - Ensure debug-shell.service is Masked
        ansible.builtin.systemd:
          name: debug-shell.service
          state: stopped
          enabled: false
          masked: true
        when: service_exists.stdout_lines is search("debug-shell.service", multiline=True)

      - name: Unit Socket Exists - debug-shell.socket
        ansible.builtin.command: systemctl -q list-unit-files debug-shell.socket
        register: socket_file_exists
        changed_when: false
        failed_when: socket_file_exists.rc not in [0, 1]
        check_mode: false

      - name: Disable debug-shell SystemD Service - Disable Socket debug-shell
        ansible.builtin.systemd:
          name: debug-shell.socket
          enabled: false
          state: stopped
          masked: true
        when: socket_file_exists.stdout_lines is search("debug-shell.socket", multiline=True)
      tags:
      - DISA-STIG-OL09-00-002403
      - NIST-800-171-3.4.5
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_debug-shell_disabled
      - special_service_block
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include disable_debug-shell

    class disable_debug-shell {
      service {'debug-shell':
        enable => false,
        ensure => 'stopped',
      }
    }
#### Rule Disable Ctrl-Alt-Del Burst Action [ref]
By default, `SystemD` will reboot the system if the `Ctrl-Alt-Del` key sequence is pressed Ctrl-Alt-Delete more than 7 times in 2 seconds. To configure the system to ignore the `CtrlAltDelBurstAction` setting, add or modify the following to `/etc/systemd/system.conf`:
    CtrlAltDelBurstAction=none
Warning: Disabling the `Ctrl-Alt-Del` key sequence in `/etc/init/control-alt-delete.conf` DOES NOT disable the `Ctrl-Alt-Del` key sequence if running in `runlevel 6` (e.g. in GNOME, KDE, etc.)! The `Ctrl-Alt-Del` key sequence will only be disabled if running in the non-graphical `runlevel 3`.
Rationale: | A locally logged-in user who presses Ctrl-Alt-Del, when at the console, can reboot the system. If accidentally pressed, as could happen in the case of mixed OS environment, this can create the risk of short-term loss of availability of systems due to unintentional reboot.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
cui | 3.4.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-2009 | 4.3.3.7.3
isa-62443-2013 | SR 2.1, SR 5.2
iso27001-2013 | A.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-cip | CIP-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
nist | CM-6(a), AC-6(1), CM-6(a)
nist-csf | PR.AC-4, PR.DS-5
ospp | FAU_GEN.1.2
os-srg | SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227
stigid | OL09-00-002412
stigref | SV-271751r1091965_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q systemd; }; 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' <<< "^CtrlAltDelBurstAction=")

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

    # 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 "^CtrlAltDelBurstAction=\\>" "/etc/systemd/system.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^CtrlAltDelBurstAction=\\>.*/$escaped_formatted_output/gi" "/etc/systemd/system.conf"
    else
        if [[ -s "/etc/systemd/system.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/systemd/system.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/systemd/system.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/systemd/system.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002412
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - disable_ctrlaltdel_burstaction
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed

    - name: Disable Ctrl-Alt-Del Burst Action
      ansible.builtin.lineinfile:
        dest: /etc/systemd/system.conf
        state: present
        regexp: ^CtrlAltDelBurstAction
        line: CtrlAltDelBurstAction=none
        create: true
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"systemd" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002412
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - disable_ctrlaltdel_burstaction
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
#### Rule Disable Ctrl-Alt-Del Reboot Activation [ref]
By default, `SystemD` will reboot the system if the `Ctrl-Alt-Del` key sequence is pressed. To configure the system to ignore the `Ctrl-Alt-Del` key sequence from the command line instead of rebooting the system, do either of the following:
    ln -sf /dev/null /etc/systemd/system/ctrl-alt-del.target
or
    systemctl mask ctrl-alt-del.target
Do not simply delete the `/usr/lib/systemd/system/ctrl-alt-del.service` file, as this file may be restored during future system updates.
Rationale: | A locally logged-in user who presses Ctrl-Alt-Del, when at the console, can reboot the system. If accidentally pressed, as could happen in the case of mixed OS environment, this can create the risk of short-term loss of availability of systems due to unintentional reboot.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_reboot
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
cui | 3.4.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-2009 | 4.3.3.7.3
isa-62443-2013 | SR 2.1, SR 5.2
iso27001-2013 | A.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-cip | CIP-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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
ospp | FAU_GEN.1.2
os-srg | SRG-OS-000324-GPOS-00125, SRG-OS-000480-GPOS-00227
stigid | OL09-00-002413
stigref | SV-271752r1091968_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        systemctl disable ctrl-alt-del.target
        systemctl mask ctrl-alt-del.target
    else
        systemctl disable --now ctrl-alt-del.target
        systemctl mask --now ctrl-alt-del.target
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002413
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_ctrlaltdel_reboot
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed

    - name: Disable Ctrl-Alt-Del Reboot Activation
      ansible.builtin.systemd:
        name: ctrl-alt-del.target
        force: true
        masked: true
        state: stopped
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002413
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_ctrlaltdel_reboot
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
#### Rule Configure Logind to terminate idle sessions after certain time of inactivity [ref]
To configure `logind` service to terminate inactive user sessions after 1800 seconds, edit the file `/etc/systemd/logind.conf`. Ensure that there is a section
    [Login]
which contains the configuration
    StopIdleSessionSec=1800
.
Rationale: | Terminating an idle session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been let unattended.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_logind_session_timeout
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cjis | 5.5.6
cobit5 | APO13.01, BAI03.01, BAI03.02, BAI03.03, DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.1.11
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.3
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013 | A.12.4.1, A.12.4.3, A.14.1.1, A.14.2.1, A.14.2.5, A.18.1.4, A.6.1.2, A.6.1.5, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cip | CIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nist | CM-6(a), AC-17(a), AC-2(5), AC-12, AC-17(a), SC-10, CM-6(a)
nist-csf | DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.IP-2
ospp | FMT_SMF_EXT.1.1
pcidss | Req-8.1.8
os-srg | SRG-OS-000163-GPOS-00072
anssi | R32
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { ( ( grep -qP "^ID=[\"']?rhel[\"']?$" "/etc/os-release" && { real="$(grep -P "^VERSION_ID=[\"']?[\w.]+[\"']?$" /etc/os-release | sed "s/^VERSION_ID=[\"']\?\([^\"']\+\)[\"']\?$/\1/")"; expected="8.7"; printf "%s\n%s" "$expected" "$real" | sort -VC; } && grep -qP "^ID=[\"']?rhel[\"']?$" "/etc/os-release" && { real="$(grep -P "^VERSION_ID=[\"']?[\w.]+[\"']?$" /etc/os-release | sed "s/^VERSION_ID=[\"']\?\([^\"']\+\)[\"']\?$/\1/")"; expected="9.0"; [[ "$real" != "$expected" ]]; } ) ) || ( grep -qP "^ID=[\"']?ol[\"']?$" "/etc/os-release" && { real="$(grep -P "^VERSION_ID=[\"']?[\w.]+[\"']?$" /etc/os-release | sed "s/^VERSION_ID=[\"']\?\([^\"']\+\)[\"']\?$/\1/")"; expected="8.7"; printf "%s\n%s" "$expected" "$real" | sort -VC; } ) || ( grep -qP "^ID=[\"']?sles[\"']?$" "/etc/os-release" && { real="$(grep -P "^VERSION_ID=[\"']?[\w.]+[\"']?$" /etc/os-release | sed "s/^VERSION_ID=[\"']\?\([^\"']\+\)[\"']\?$/\1/")"; expected="15"; printf "%s\n%s" "$expected" "$real" | sort -VC; } ); }; then

    var_logind_session_timeout='1800'

    # Try find '[Login]' and 'StopIdleSessionSec' in '/etc/systemd/logind.conf', if it exists, set
    # to '$var_logind_session_timeout', if it isn't here, add it, if '[Login]' doesn't exist, add it there
    if grep -qzosP '[[:space:]]*\[Login]([^\n\[]*\n+)+?[[:space:]]*StopIdleSessionSec' '/etc/systemd/logind.conf'; then

        sed -i "s/StopIdleSessionSec[^(\n)]*/StopIdleSessionSec=$var_logind_session_timeout/" '/etc/systemd/logind.conf'
    elif grep -qs '[[:space:]]*\[Login]' '/etc/systemd/logind.conf'; then
        sed -i "/[[:space:]]*\[Login]/a StopIdleSessionSec=$var_logind_session_timeout" '/etc/systemd/logind.conf'
    else
        if test -d "/etc/systemd"; then
            printf '%s\n' '[Login]' "StopIdleSessionSec=$var_logind_session_timeout" >> '/etc/systemd/logind.conf'
        else
            echo "Config file directory '/etc/systemd' doesnt exist, not remediating, assuming non-applicability." >&2
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.6
      - NIST-800-171-3.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - logind_session_timeout
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
      - restrict_strategy
    - name: XCCDF Value var_logind_session_timeout # promote to variable
      set_fact:
        var_logind_session_timeout: !!str 1800
      tags:
        - always

    - name: Set 'StopIdleSessionSec' to '{{ var_logind_session_timeout }}' in the [Login]
        section of '/etc/systemd/logind.conf'
      community.general.ini_file:
        path: /etc/systemd/logind.conf
        section: Login
        option: StopIdleSessionSec
        value: '{{ var_logind_session_timeout }}'
        create: true
        mode: 420
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - ( ansible_distribution == 'RedHat' and ansible_distribution_version is version('8.7',
        '>=') and ansible_distribution == 'RedHat' and ansible_distribution_version is
        version('9.0', '!=') ) or ansible_distribution == 'OracleLinux' and ansible_distribution_version
        is version('8.7', '>=') or ansible_distribution == 'SLES' and ansible_distribution_version
        is version('15', '>=')
      tags:
      - CJIS-5.5.6
      - NIST-800-171-3.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - logind_session_timeout
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
      - restrict_strategy
#### Rule Require Authentication for Single User Mode [ref]
Single-user mode is intended as a system recovery method, providing a single user root access to the system by providing a boot option at startup. By default, single-user mode is protected by requiring a password and is set in `/usr/lib/systemd/system/rescue.service`.
Rationale: | This prevents attackers with physical access from trivially bypassing security on the machine and gaining root access. Such accesses are further prevented by configuring the bootloader password.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_require_singleuser_auth
References: |
cis-csc | 1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5 | DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui | 3.1.1, 3.4.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-2009 | 4.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-2013 | SR 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
iso27001-2013 | A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cip | CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nist | IA-2, AC-3, CM-6(a)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
ospp | FIA_UAU.1
os-srg | SRG-OS-000080-GPOS-00048
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
stigid | OL09-00-000030
stigref | SV-271442r1117265_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    found=false

    # set value in all files if they contain section or key
    for f in $(echo -n "/etc/systemd/system/rescue.service.d/10-oscap.conf /etc/systemd/system/rescue.service.d/*.conf"); do
        if [ ! -e "$f" ]; then
            continue
        fi

        # find key in section and change value
        if grep -qzosP "(?m)^[[:space:]]*\[Service\]([^\n\[]*\n+)+?[[:space:]]*ExecStart" "$f"; then
            if ! grep -qzosP "(?m)^[[:space:]]*ExecStart[[:space:]]*=[[:space:]]*\nExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue" "$f"; then

                sed -i "/^[[:space:]]*ExecStart/s/\([[:blank:]]*=[[:blank:]]*\).*/\1\nExecStart=-\/usr\/lib\/systemd\/systemd-sulogin-shell rescue/" "$f"

            fi

            found=true

        # find section and add key = value to it
        elif grep -qs "^[[:space:]]*\[Service\]" "$f"; then

                sed -i "/^[[:space:]]*\[Service\]/a ExecStart=\nExecStart=-\/usr\/lib\/systemd\/systemd-sulogin-shell rescue" "$f"

                found=true
        fi
    done

    # if section not in any file, append section with key = value to FIRST file in files parameter
    if ! $found ; then
        file=$(echo "/etc/systemd/system/rescue.service.d/10-oscap.conf /etc/systemd/system/rescue.service.d/*.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Service]\nExecStart=\nExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue" >> "$file"

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000030
      - NIST-800-171-3.1.1
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - require_singleuser_auth
      - restrict_strategy

    - name: Require Authentication for Single User Mode - find files which already override
        Execstart of rescue.service
      ansible.builtin.find:
        paths: /etc/systemd/system/rescue.service.d
        patterns: '*.conf'
        contains: ^\s*ExecStart=.*$
      register: rescue_service_overrides_found
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000030
      - NIST-800-171-3.1.1
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - require_singleuser_auth
      - restrict_strategy

    - name: Require Authentication for Single User Mode - set files containing ExecStart
        overrides as target
      ansible.builtin.set_fact:
        rescue_service_remediation_target_file: '{{ rescue_service_overrides_found.files
          | map(attribute=''path'') | list }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - rescue_service_overrides_found.matched is defined and rescue_service_overrides_found.matched
        > 0
      tags:
      - DISA-STIG-OL09-00-000030
      - NIST-800-171-3.1.1
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - require_singleuser_auth
      - restrict_strategy

    - name: Require Authentication for Single User Mode - set default target for rescue.service
        override
      ansible.builtin.set_fact:
        rescue_service_remediation_target_file:
        - /etc/systemd/system/rescue.service.d/10-oscap.conf
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - rescue_service_overrides_found.matched is defined and rescue_service_overrides_found.matched
        == 0
      tags:
      - DISA-STIG-OL09-00-000030
      - NIST-800-171-3.1.1
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - require_singleuser_auth
      - restrict_strategy

    - name: Require Authentication for Single User Mode - Require emergency user mode
        password
      community.general.ini_file:
        path: '{{ item }}'
        section: Service
        option: ExecStart
        values:
        - ''
        - -/usr/lib/systemd/systemd-sulogin-shell rescue
      loop: '{{ rescue_service_remediation_target_file }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000030
      - NIST-800-171-3.1.1
      - NIST-800-171-3.4.5
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - require_singleuser_auth
      - restrict_strategy
Group Protect Accounts by Restricting Password-Based Login Group contains 2 groups 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 Verify Proper Storage and Existence of Password Hashes Group contains 1 rule
[ref] By default, password hashes for local accounts are stored in the second field (colon-separated) in `/etc/shadow`. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as `/etc/passwd`, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.
#### Rule Prevent Login to Accounts With Empty Password [ref]
If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the `nullok` in `/etc/pam.d/system-auth` and `/etc/pam.d/password-auth` to prevent logins with empty passwords. Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
Rationale: | If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_no_empty_passwords
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2
cobit5 | APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui | 3.1.1, 3.1.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013 | A.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
nist | IA-5(1)(a), IA-5(c), CM-6(a)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
ospp | FIA_UAU.1
pcidss | Req-8.2.3
os-srg | SRG-OS-000480-GPOS-00227
ism | 1546
pcidss4 | 8.3.1, 8.3
stigid | OL09-00-001110
stigref | SV-271634r1091614_rule
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
            mode: 0644
            path: /etc/pam.d/password-auth
            overwrite: true
          - contents:
              source: data:,%23%20Generated%20by%20authselect%20on%20Sat%20Oct%2027%2014%3A59%3A36%202018%0A%23%20Do%20not%20modify%20this%20file%20manually.%0A%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_env.so%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_faildelay.so%20delay%3D2000000%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_fprintd.so%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet%0Aauth%20%20%20%20%20%20%20%20%5Bdefault%3D1%20ignore%3Dignore%20success%3Dok%5D%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20try_first_pass%0Aauth%20%20%20%20%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3E%3D%201000%20quiet_success%0Aauth%20%20%20%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20forward_pass%0Aauth%20%20%20%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_localuser.so%0Aaccount%20%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20uid%20%3C%201000%20quiet%0Aaccount%20%20%20%20%20%5Bdefault%3Dbad%20success%3Dok%20user_unknown%3Dignore%5D%20pam_sss.so%0Aaccount%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_permit.so%0A%0Apassword%20%20%20%20requisite%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_pwquality.so%20try_first_pass%20local_users_only%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%20sha512%20shadow%20try_first_pass%20use_authtok%0Apassword%20%20%20%20sufficient%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%20use_authtok%0Apassword%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_deny.so%0A%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_keyinit.so%20revoke%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_limits.so%0A-session%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_systemd.so%0Asession%20%20%20%20%20%5Bsuccess%3D1%20default%3Dignore%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_succeed_if.so%20service%20in%20crond%20quiet%20use_uid%0Asession%20%20%20%20%20required%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_unix.so%0Asession%20%20%20%20%20optional%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pam_sss.so%0A
            mode: 0644
            path: /etc/pam.d/system-auth
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

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

    authselect apply-changes -b
    else

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

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

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.2
      - DISA-STIG-OL09-00-001110
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - configure_strategy
      - high_severity
      - low_complexity
      - medium_disruption
      - no_empty_passwords
      - no_reboot_needed

    - name: Prevent Login to Accounts With Empty Password - Check if system relies on
        authselect
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.2
      - DISA-STIG-OL09-00-001110
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - configure_strategy
      - high_severity
      - low_complexity
      - medium_disruption
      - no_empty_passwords
      - no_reboot_needed

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

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

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

      - name: Prevent Login to Accounts With Empty Password - Get authselect current features
        ansible.builtin.shell:
          cmd: authselect current | tail -n+3 | awk '{ print $2 }'
        register: result_authselect_features
        changed_when: false
        check_mode: false
        when:
        - result_authselect_check_cmd is success

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

      - name: Prevent Login to Accounts With Empty Password - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_enable_feature_cmd is not skipped
        - result_authselect_enable_feature_cmd is success
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.2
      - DISA-STIG-OL09-00-001110
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - configure_strategy
      - high_severity
      - low_complexity
      - medium_disruption
      - no_empty_passwords
      - no_reboot_needed

    - name: Prevent Login to Accounts With Empty Password - Remediate directly editing
        PAM files
      ansible.builtin.replace:
        dest: '{{ item }}'
        regexp: nullok
      loop:
      - /etc/pam.d/system-auth
      - /etc/pam.d/password-auth
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - not result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.2
      - DISA-STIG-OL09-00-001110
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - configure_strategy
      - high_severity
      - low_complexity
      - medium_disruption
      - no_empty_passwords
      - no_reboot_needed
Group Restrict Root Logins Group contains 1 rule
[ref] Direct root logins should be allowed only for emergency use. In normal situations, the administrator should access the system via a unique unprivileged account, and then use `su` or `sudo` to execute privileged commands. Discouraging administrators from accessing the root account directly ensures an audit trail in organizations with multiple administrators. Locking down the channels through which root can connect directly also reduces opportunities for password-guessing against the root account. The `login` program uses the file `/etc/securetty` to determine which interfaces should allow root logins. The virtual devices `/dev/console` and `/dev/tty*` represent the system consoles (accessible via the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default installation). The default securetty file also contains `/dev/vc/*`. These are likely to be deprecated in most environments, but may be retained for compatibility. Root should also be prohibited from connecting via network protocols. Other sections of this document include guidance describing how to prevent root from logging in via SSH.
#### Rule Enforce usage of pam_wheel for su authentication [ref]
To ensure that only users who are members of the `wheel` group can run commands with altered privileges through the `su` command, make sure that the following line exists in the file `/etc/pam.d/su`:
    auth required pam_wheel.so use_uid
Warning: Members of "wheel" or GID 0 groups are checked by default if the group option is not set for pam_wheel.so module. Therefore, members of these groups should be manually checked or a different group should be informed according to the site policy.
Rationale: | The `su` program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_use_pam_wheel_for_su
References: |
ospp | FMT_SMF_EXT.1.1
os-srg | SRG-OS-000373-GPOS-00156, SRG-OS-000312-GPOS-00123
ccn | A.5.SEC-OL1
stigid | OL09-00-002361
stigref | SV-271723r1091881_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q pam; then

    # uncomment the option if commented
    sed '/^[[:space:]]*#[[:space:]]*auth[[:space:]]\+required[[:space:]]\+pam_wheel\.so[[:space:]]\+use_uid$/s/^[[:space:]]*#//' -i /etc/pam.d/su

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002361
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - use_pam_wheel_for_su

    - name: Restrict usage of su command only to members of wheel group
      ansible.builtin.replace:
        path: /etc/pam.d/su
        regexp: ^[\s]*#[\s]*auth[\s]+required[\s]+pam_wheel\.so[\s]+use_uid$
        replace: auth             required        pam_wheel.so use_uid
      when: '"pam" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002361
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - use_pam_wheel_for_su
#### Rule Enable authselect [ref]
Configure user authentication setup to use the `authselect` tool. If authselect profile is selected, the rule will enable the minimal profile. Warning: If the `sudo authselect select` command returns an error informing that the chosen profile cannot be selected, it is probably because PAM files have already been modified by the administrator. If this is the case, in order to not overwrite the desired changes made by the administrator, the current PAM settings should be investigated before forcing the selection of the chosen authselect profile.
Rationale: | Authselect is a successor to authconfig. It is a tool to select system authentication and identity sources from a list of supported profiles instead of letting the administrator manually build the PAM stack. That way, it avoids potential breakage of configuration, as it ships several tested profiles that are well tested and supported to solve different use-cases.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_enable_authselect
References: |
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
nist | AC-3
ospp | FIA_UAU.1, FIA_AFL.1
os-srg | SRG-OS-000480-GPOS-00227
anssi | R31
ccn | enable_authselect
ism | 1409
pcidss4 | 8.3.4, 8.3
stigid | needed_rules
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    var_authselect_profile='minimal'

    authselect current

    if test "$?" -ne 0; then
        if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; }; then
            authselect select --force "$var_authselect_profile"
        else
            authselect select "$var_authselect_profile"
        fi

        if test "$?" -ne 0; then
            if rpm --quiet --verify pam; then
                authselect select --force "$var_authselect_profile"
            else
    	        echo "authselect is not used but files from the 'pam' package have been altered, so the authselect configuration won't be forced." >&2
            fi
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
    - name: XCCDF Value var_authselect_profile # promote to variable
      set_fact:
        var_authselect_profile: !!str minimal
      tags:
        - always

    - name: Enable authselect - Check Current authselect Profile
      ansible.builtin.command:
        cmd: authselect current
      register: result_authselect_current
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enable authselect - Try to Select an authselect Profile
      ansible.builtin.command:
        cmd: authselect select "{{ var_authselect_profile }}"
      register: result_authselect_select
      changed_when: result_authselect_select.rc == 0
      failed_when: false
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - result_authselect_current.rc != 0
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enable authselect - Verify If pam Has Been Altered
      ansible.builtin.command:
        cmd: rpm -qV pam
      register: result_altered_authselect
      changed_when: false
      failed_when: false
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - result_authselect_select is not skipped
      - result_authselect_select.rc != 0
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enable authselect - Informative Message Based on authselect Integrity Check
      ansible.builtin.assert:
        that:
        - result_authselect_current.rc == 0 or result_altered_authselect is skipped or
          result_altered_authselect.rc == 0
        fail_msg:
        - authselect is not used but files from the 'pam' package have been altered, so
          the authselect configuration won't be forced.
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enable authselect - Force authselect Profile Selection
      ansible.builtin.command:
        cmd: authselect select --force "{{ var_authselect_profile }}"
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - result_authselect_current.rc != 0
      - result_authselect_select.rc != 0
      - result_altered_authselect.rc == 0
      tags:
      - DISA-STIG-needed_rules
      - NIST-800-53-AC-3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - configure_strategy
      - enable_authselect
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
Group GRUB2 bootloader configuration Group contains 1 group and 6 rules
[ref] During the boot process, the boot loader is responsible for starting the execution of the kernel and passing options to it. The boot loader allows for the selection of different kernels - possibly on different partitions or media. The default Oracle Linux 9 boot loader for x86 systems is called GRUB2. Options it can pass to the kernel include single-user mode, which provides root access without any authentication, and the ability to disable SELinux. To prevent local users from modifying the boot parameters and endangering security, protect the boot loader configuration with a password and ensure its configuration file's permissions are set properly.
Group Non-UEFI GRUB2 bootloader configuration Group contains 1 rule
[ref] Non-UEFI GRUB2 bootloader configuration
#### Rule Set Boot Loader Password in grub2 [ref]
The grub2 boot loader should have a superuser account and password protection enabled to protect boot-time settings. Since plaintext passwords are a security risk, generate a hash for the password by running the following command:
    # grub2-setpassword
When prompted, enter the password that was selected. Warning: To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation must be automated as a component of machine provisioning, or followed manually as outlined above. Also, do NOT manually add the superuser account and password to the `grub.cfg` file as the grub2-mkconfig command overwrites this file.
Rationale: | Password protection on the boot loader configuration ensures users with physical access cannot trivially alter important bootloader settings. These include which kernel to use, and whether to enter single-user mode.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_password
References: |
cis-csc | 1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5 | DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui | 3.4.5
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-2009 | 4.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-2013 | SR 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
iso27001-2013 | A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nist | CM-6(a)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
ospp | FIA_UAU.1
os-srg | SRG-OS-000080-GPOS-00048
anssi | R5
ccn | A.8.SEC-OL7
stigid | OL09-00-001115
stigref | SV-271635r1117265_rule
#### Rule Disable Recovery Booting [ref]
Oracle Linux 9 systems support an "recovery boot" option that can be used to prevent services from being started. The `GRUB_DISABLE_RECOVERY` configuration option in `/etc/default/grub` should be set to `true` to disable the generation of recovery mode menu entries. It is also required to change the runtime configuration, run:
    $ sudo grubby --update-kernel=ALL
Rationale: | Using recovery boot, the console user could disable auditing, firewalls, or other services, weakening system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_disable_recovery
References: |
ospp | FIA_UAU.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if grep -q '^GRUB_DISABLE_RECOVERY=.*'  '/etc/default/grub' ; then
        sed -i 's/GRUB_DISABLE_RECOVERY=.*/GRUB_DISABLE_RECOVERY=true/' "/etc/default/grub"
    else
        echo "GRUB_DISABLE_RECOVERY=true" >> '/etc/default/grub'
    fi

    grubby --update-kernel=ALL

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - grub2_disable_recovery
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Disable Recovery Booting - Verify GRUB_DISABLE_RECOVERY=true
      ansible.builtin.lineinfile:
        path: /etc/default/grub
        regexp: ^GRUB_DISABLE_RECOVERY=.*
        line: GRUB_DISABLE_RECOVERY=true
        state: present
      register: grub_config_changed
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_disable_recovery
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Disable Recovery Booting - Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - grub_config_changed is changed
      tags:
      - grub2_disable_recovery
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
      - restrict_strategy
#### Rule Configure kernel to zero out memory before allocation [ref]
To configure the kernel to zero out memory before allocating it, add the `init_on_alloc=1` argument to the default GRUB 2 command line. To ensure that `init_on_alloc=1` is added as a kernel command line argument to newly installed kernels, add `init_on_alloc=1` to the default Grub2 command line for Linux operating systems. Modify the line within `/etc/default/grub` as shown below:
    GRUB_CMDLINE_LINUX="... init_on_alloc=1 ..."
Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --args="init_on_alloc=1"
Rationale: | When the kernel configuration option `init_on_alloc` is enabled, all page allocator and slab allocator memory will be zeroed when allocated, eliminating many kinds of "uninitialized heap memory" flaws, effectively preventing data leaks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_init_on_alloc_argument
References: |
ospp | AVA_VAN.1
Remediation OSBuild Blueprint snippet ⇲
    [customizations.kernel]
    append = "init_on_alloc=1"
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        KARGS_DIR="/usr/lib/bootc/kargs.d/"
        if grep -q -E "init_on_alloc" "$KARGS_DIR/*.toml" ; then
            sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"init_on_alloc=[^\"]*\"(.*]\s*)/\1\"init_on_alloc=1\"\2/" "$KARGS_DIR/*.toml"
        else
            echo "kargs = [\"init_on_alloc=1\"]" >> "$KARGS_DIR/10-init_on_alloc.toml"
        fi
    else

        grubby --update-kernel=ALL --args=init_on_alloc=1

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - grub2_init_on_alloc_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if init_on_alloc argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_init_on_alloc_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if init_on_alloc argument is already present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_init_on_alloc_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --args="init_on_alloc=1"
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - (grubby_info.stdout is not search('init_on_alloc=1')) or ((etc_default_grub['content']
        | b64decode) is not search('init_on_alloc=1'))
      tags:
      - grub2_init_on_alloc_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy
#### Rule Enable randomization of the page allocator [ref]
To enable randomization of the page allocator in the kernel, add the `page_alloc.shuffle=1` argument to the default GRUB 2 command line. To ensure that `page_alloc.shuffle=1` is added as a kernel command line argument to newly installed kernels, add `page_alloc.shuffle=1` to the default Grub2 command line for Linux operating systems. Modify the line within `/etc/default/grub` as shown below:
    GRUB_CMDLINE_LINUX="... page_alloc.shuffle=1 ..."
Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --args="page_alloc.shuffle=1"
Rationale: | The `CONFIG_SHUFFLE_PAGE_ALLOCATOR` config option is primarily focused on improving the average utilization of a direct-mapped memory-side-cache. Aside of this performance effect, it also reduces predictability of page allocations in situations when the bad actor can crash the system and somehow leverage knowledge of (page) allocation order right after a fresh reboot, or can control the timing between a hot-pluggable memory node (as in NUMA node) and applications allocating memory ouf of that node. The `page_alloc.shuffle=1` kernel command line parameter then forces this functionality irrespectively of memory cache architecture.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_page_alloc_shuffle_argument
References: |
ospp | AVA_VAN.1
anssi | R8
Remediation OSBuild Blueprint snippet ⇲
    [customizations.kernel]
    append = "page_alloc.shuffle=1"
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        KARGS_DIR="/usr/lib/bootc/kargs.d/"
        if grep -q -E "page_alloc.shuffle" "$KARGS_DIR/*.toml" ; then
            sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"page_alloc.shuffle=[^\"]*\"(.*]\s*)/\1\"page_alloc.shuffle=1\"\2/" "$KARGS_DIR/*.toml"
        else
            echo "kargs = [\"page_alloc.shuffle=1\"]" >> "$KARGS_DIR/10-page_alloc_shuffle.toml"
        fi
    else

        grubby --update-kernel=ALL --args=page_alloc.shuffle=1

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - grub2_page_alloc_shuffle_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if page_alloc.shuffle argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_page_alloc_shuffle_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if page_alloc.shuffle argument is already present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_page_alloc_shuffle_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --args="page_alloc.shuffle=1"
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - (grubby_info.stdout is not search('page_alloc.shuffle=1')) or ((etc_default_grub['content']
        | b64decode) is not search('page_alloc.shuffle=1'))
      tags:
      - grub2_page_alloc_shuffle_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy
#### Rule Ensure debug-shell service is not enabled during boot [ref]
systemd's `debug-shell` service is intended to diagnose systemd related boot issues with various `systemctl` commands. Once enabled and following a system reboot, the root shell will be available on `tty9` which is access by pressing `CTRL-ALT-F9`. The `debug-shell` service should only be used for systemd related issues and should otherwise be disabled. By default, the `debug-shell` systemd service is already disabled. Ensure the debug-shell is not enabled by the `systemd.debug-shel=1` boot parameter option. Check that the line
    GRUB_CMDLINE_LINUX="..."
within `/etc/default/grub` doesn't contain the argument `systemd.debug-shell`. Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --remove-args="systemd.debug-shell"
Rationale: | This prevents attackers with physical access from trivially bypassing security on the machine through valid troubleshooting configurations and gaining root access when the system is rebooted.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_systemd_debug-shell_argument_absent
References: |
ospp | FIA_UAU.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        sed -i -E "/kargs\s*=\s*\[\s*\"systemd.debug-shell=[^\"]*\"\s*]/{:a;N;/^\n$/ba;N;/match-architectures.*/d;}" "$KARGS_DIR/*.toml"
        sed -i -E -e "s/^(\s*kargs\s*=\s*\[.*)\"systemd.debug-shell=[^\"]*\"[,[:space:]]*(.*]\s*)/\1\2/" -e "s/^(\s*kargs.*),\s*\]$/\1\]/" "$KARGS_DIR/*.toml"
    else

    grubby --update-kernel=ALL --remove-args=systemd.debug-shell

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - grub2_systemd_debug-shell_argument_absent
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if systemd.debug-shell argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_systemd_debug-shell_argument_absent
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if systemd.debug-shell argument is present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when: ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - grub2_systemd_debug-shell_argument_absent
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --remove-args="systemd.debug-shell"
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - (grubby_info.stdout is search('systemd.debug-shell')) or ((etc_default_grub['content']
        | b64decode) is search('systemd.debug-shell'))
      tags:
      - grub2_systemd_debug-shell_argument_absent
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy
#### Rule Disable vsyscalls [ref]
To disable use of virtual syscalls, add the argument `vsyscall=none` to the default GRUB 2 command line for the Linux operating system. To ensure that `vsyscall=none` is added as a kernel command line argument to newly installed kernels, add `vsyscall=none` to the default Grub2 command line for Linux operating systems. Modify the line within `/etc/default/grub` as shown below:
    GRUB_CMDLINE_LINUX="... vsyscall=none ..."
Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --args="vsyscall=none"
Warning: The vsyscall emulation is only available on x86_64 architecture (CONFIG_X86_VSYSCALL_EMULATION) making this rule not applicable to other CPU architectures.
Rationale: | Virtual Syscalls provide an opportunity of attack for a user who has control of the return instruction pointer.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_vsyscall_argument
References: |
nist | CM-7(a)
ospp | FPT_ASLR_EXT.1
os-srg | SRG-OS-000480-GPOS-00227, SRG-OS-000134-GPOS-00068
stigid | OL09-00-002393
stigref | SV-271737r1094967_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.kernel]
    append = "vsyscall=none"
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ) && { ( ( grep -sqE "^.*\.x86_64$" /proc/sys/kernel/osrelease || grep -sqE "^x86_64$" /proc/sys/kernel/arch; ) ); }; then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        KARGS_DIR="/usr/lib/bootc/kargs.d/"
        if grep -q -E "vsyscall" "$KARGS_DIR/*.toml" ; then
            sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"vsyscall=[^\"]*\"(.*]\s*)/\1\"vsyscall=none\"\2/" "$KARGS_DIR/*.toml"
        else
            echo "kargs = [\"vsyscall=none\"]" >> "$KARGS_DIR/10-vsyscall.toml"
        fi
    else

        grubby --update-kernel=ALL --args=vsyscall=none

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002393
      - NIST-800-53-CM-7(a)
      - grub2_vsyscall_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if vsyscall argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - ansible_architecture == "x86_64"
      tags:
      - DISA-STIG-OL09-00-002393
      - NIST-800-53-CM-7(a)
      - grub2_vsyscall_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Check if vsyscall argument is already present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - ansible_architecture == "x86_64"
      tags:
      - DISA-STIG-OL09-00-002393
      - NIST-800-53-CM-7(a)
      - grub2_vsyscall_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --args="vsyscall=none"
      when:
      - ( "grub2-common" in ansible_facts.packages and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      - ansible_architecture == "x86_64"
      - (grubby_info.stdout is not search('vsyscall=none')) or ((etc_default_grub['content']
        | b64decode) is not search('vsyscall=none'))
      tags:
      - DISA-STIG-OL09-00-002393
      - NIST-800-53-CM-7(a)
      - grub2_vsyscall_argument
      - low_disruption
      - medium_complexity
      - medium_severity
      - reboot_required
      - restrict_strategy
Group Network Configuration and Firewalls Group contains 5 groups and 6 rules
[ref] Most systems must be connected to a network of some sort, and this brings with it the substantial risk of network attack. This section discusses the security impact of decisions about networking which must be made when configuring a system. This section also discusses firewalls, network access controls, and other network security frameworks, which allow system-level rules to be written that can limit an attackers' ability to connect to your system. These rules can specify that network traffic should be allowed or denied from certain IP addresses, hosts, and networks. The rules can also specify which of the system's network services are available to particular hosts or networks.
Group firewalld Group contains 1 group and 2 rules
[ref] The dynamic firewall daemon `firewalld` provides a dynamically managed firewall with support for network “zones” to assign a level of trust to a network and its associated connections and interfaces. It has support for IPv4 and IPv6 firewall settings. It supports Ethernet bridges and has a separation of runtime and permanent configuration options. It also has an interface for services or applications to add firewall rules directly. A graphical configuration tool, `firewall-config`, is used to configure `firewalld`, which in turn uses `iptables` tool to communicate with `Netfilter` in the kernel which implements packet filtering. The firewall service provided by `firewalld` is dynamic rather than static because changes to the configuration can be made at anytime and are immediately implemented. There is no need to save or apply the changes. No unintended disruption of existing network connections occurs as no part of the firewall has to be reloaded.
Group Inspect and Activate Default firewalld Rules Group contains 2 rules
[ref] Firewalls can be used to separate networks into different zones based on the level of trust the user has decided to place on the devices and traffic within that network. `NetworkManager` informs firewalld to which zone an interface belongs. An interface's assigned zone can be changed by `NetworkManager` or via the `firewall-config` tool. The zone settings in `/etc/firewalld/` are a range of preset settings which can be quickly applied to a network interface. These are the zones provided by firewalld sorted according to the default trust level of the zones from untrusted to trusted: `drop` Any incoming network packets are dropped, there is no reply. Only outgoing network connections are possible. `block` Any incoming network connections are rejected with an `icmp-host-prohibited` message for IPv4 and `icmp6-adm-prohibited` for IPv6. Only network connections initiated from within the system are possible. `public` For use in public areas. You do not trust the other computers on the network to not harm your computer. Only selected incoming connections are accepted. `external` For use on external networks with masquerading enabled especially for routers. You do not trust the other computers on the network to not harm your computer. Only selected incoming connections are accepted. `dmz` For computers in your demilitarized zone that are publicly-accessible with limited access to your internal network. Only selected incoming connections are accepted. `work` For use in work areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted. `home` For use in home areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted. `internal` For use on internal networks. You mostly trust the other computers on the networks to not harm your computer. Only selected incoming connections are accepted. `trusted` All network connections are accepted. It is possible to designate one of these zones to be the default zone. When interface connections are added to `NetworkManager`, they are assigned to the default zone. On installation, the default zone in firewalld is set to be the public zone. To find out all the settings of a zone, for example the `public zone,` enter the following command as root:
    # firewall-cmd --zone=public --list-all
Example output of this command might look like the following:
    # firewall-cmd --zone=public --list-all
    public
      interfaces:
      services: mdns dhcpv6-client ssh
      ports:
      forward-ports:
      icmp-blocks: source-quench
To view the network zones currently active, enter the following command as root:
    # firewall-cmd --get-service
The following listing displays the result of this command on common Oracle Linux 9 system:
    # firewall-cmd --get-service
    amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp
    high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd
    ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn
    pmcd pmproxy pmwebapi pmwebapis pop3s postgresql proxy-dhcp radius rpc-bind
    samba samba-client smtp ssh telnet tftp tftp-client transmission-client
    vnc-server wbem-https
Finally to view the network zones that will be active after the next firewalld service reload, enter the following command as root:
    # firewall-cmd --get-service --permanent
#### Rule Install firewalld Package [ref]
The `firewalld` package can be installed with the following command:
    $ sudo yum install firewalld
Rationale: | "Firewalld" provides an easy and effective way to block/limit remote access to the system via ports, services, and protocols. Remote access services, such as those providing remote access to network devices and information systems, which lack automated control capabilities, increase risk and make remote user access management difficult at best. Remote access is access to nonpublic information systems by an authorized user (or an information system) communicating through an external, non-organization-controlled network. Remote access methods include, for example, dial-up, broadband, and wireless. Oracle Linux 9 functionality (e.g., SSH) must be capable of taking enforcement action if the audit reveals unauthorized activity. Automated control of remote access sessions allows organizations to ensure ongoing compliance with remote access policies by enforcing connection rules of remote access applications on a variety of information system components (e.g., servers, workstations, notebook computers, smartphones, and tablets)."
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_firewalld_installed
References: |
nist | CM-6(a)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000096-GPOS-00050, SRG-OS-000297-GPOS-00115, SRG-OS-000298-GPOS-00116, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00232
ccn | A.8.SEC-OL3
ism | 1409
pcidss4 | 1.2.1, 1.2
stigid | OL09-00-000220
stigref | SV-271469r1091119_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "firewalld"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if ! rpm -q --quiet "firewalld" ; then
        yum install -y "firewalld"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=firewalld
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000220
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_firewalld_installed

    - name: Ensure firewalld is installed
      ansible.builtin.package:
        name: firewalld
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000220
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_firewalld_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_firewalld

    class install_firewalld {
      package { 'firewalld':
        ensure => 'installed',
      }
    }
#### Rule Verify firewalld Enabled [ref]
The `firewalld` service can be enabled with the following command:
    $ sudo systemctl enable firewalld.service
Rationale: | Access control methods provide the ability to enhance system security posture by restricting services and known good IP addresses and address ranges. This prevents connections from unknown hosts and protocols.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_firewalld_enabled
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui | 3.1.3, 3.4.7
isa-62443-2009 | 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013 | SR 7.6
iso27001-2013 | A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nerc-cip | CIP-003-8 R4, CIP-003-8 R5, CIP-004-6 R3
nist | AC-4, CM-7(b), CA-3(5), SC-7(21), CM-6(a)
nist-csf | PR.IP-1
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000096-GPOS-00050, SRG-OS-000297-GPOS-00115, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232
bsi | SYS.1.6.A5, SYS.1.6.A21
ccn | A.8.SEC-OL3
ism | 1409
pcidss4 | 1.2.1, 1.2
stigid | OL09-00-000221
stigref | SV-271470r1092618_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["firewalld"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q firewalld; }; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'firewalld.service'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" start 'firewalld.service'
    fi
    "$SYSTEMCTL_EXEC" enable 'firewalld.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000221
      - NIST-800-171-3.1.3
      - NIST-800-171-3.4.7
      - NIST-800-53-AC-4
      - NIST-800-53-CA-3(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_firewalld_enabled

    - name: Verify firewalld Enabled - Enable service firewalld
      block:

      - name: Gather the package facts
        ansible.builtin.package_facts:
          manager: auto

      - name: Verify firewalld Enabled - Enable Service firewalld
        ansible.builtin.systemd:
          name: firewalld
          enabled: true
          state: started
          masked: false
        when:
        - '"firewalld" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000221
      - NIST-800-171-3.1.3
      - NIST-800-171-3.4.7
      - NIST-800-53-AC-4
      - NIST-800-53-CA-3(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_firewalld_enabled
      - special_service_block
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"firewalld" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_firewalld

    class enable_firewalld {
      service {'firewalld':
        enable => true,
        ensure => 'running',
      }
    }
Group Uncommon Network Protocols Group contains 3 rules
[ref] The system includes support for several network protocols which are not commonly used. Although security vulnerabilities in kernel networking code are not frequently discovered, the consequences can be dramatic. Ensuring uncommon network protocols are disabled reduces the system's risk to attacks targeted at its implementation of those protocols. Warning: Although these protocols are not commonly used, avoid disruption in your network environment by ensuring they are not needed prior to disabling them.
#### Rule Disable CAN Support [ref]
The Controller Area Network (CAN) is a serial communications protocol which was initially developed for automotive and is now also used in marine, industrial, and medical applications. To configure the system to prevent the `can` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/can.conf`:
    install can /bin/false
This entry will cause a non-zero return value during a `can` module installation and additionally convey the meaning of the entry to the user in form of an error message. If you would like to omit a non-zero return value and an error message, you may want to add a different line instead (both `/bin/true` and `/bin/false` are allowed by OVAL and will be accepted by the scan):
    install can /bin/true
To configure the system to prevent the `can` from being used, add the following line to file `/etc/modprobe.d/can.conf`:
    blacklist can
Rationale: | Disabling CAN protects the system against exploitation of any flaws in its implementation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_can_disabled
References: |
nist | AC-18
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
stigid | OL09-00-000041
stigref | SV-271444r1091044_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if LC_ALL=C grep -q -m 1 "^install can" /etc/modprobe.d/can.conf ; then

    	sed -i 's#^install can.*#install can /bin/false#g' /etc/modprobe.d/can.conf
    else
    	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/can.conf
    	echo "install can /bin/false" >> /etc/modprobe.d/can.conf
    fi

    if ! LC_ALL=C grep -q -m 1 "^blacklist can$" /etc/modprobe.d/can.conf ; then
    	echo "blacklist can" >> /etc/modprobe.d/can.conf
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000041
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_can_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'can' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/can.conf
        regexp: install\s+can
        line: install can /bin/false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000041
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_can_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'can' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/can.conf
        regexp: ^blacklist can$
        line: blacklist can
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000041
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_can_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
#### Rule Disable SCTP Support [ref]
The Stream Control Transmission Protocol (SCTP) is a transport layer protocol, designed to support the idea of message-oriented communication, with several streams of messages within one connection. To configure the system to prevent the `sctp` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/sctp.conf`:
    install sctp /bin/false
This entry will cause a non-zero return value during a `sctp` module installation and additionally convey the meaning of the entry to the user in form of an error message. If you would like to omit a non-zero return value and an error message, you may want to add a different line instead (both `/bin/true` and `/bin/false` are allowed by OVAL and will be accepted by the scan):
    install sctp /bin/true
To configure the system to prevent the `sctp` from being used, add the following line to file `/etc/modprobe.d/sctp.conf`:
    blacklist sctp
Rationale: | Disabling SCTP protects the system against exploitation of any flaws in its implementation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled
References: |
cis-csc | 11, 14, 3, 9
cjis | 5.10.1
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui | 3.4.6
isa-62443-2009 | 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-2013 | SR 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-2013 | A.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
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.IP-1, PR.PT-3
ospp | FMT_SMF_EXT.1
pcidss | Req-1.4.2
os-srg | SRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
pcidss4 | 1.4.2, 1.4
stigid | OL09-00-000043
stigref | SV-271446r1091050_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if LC_ALL=C grep -q -m 1 "^install sctp" /etc/modprobe.d/sctp.conf ; then

    	sed -i 's#^install sctp.*#install sctp /bin/false#g' /etc/modprobe.d/sctp.conf
    else
    	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/sctp.conf
    	echo "install sctp /bin/false" >> /etc/modprobe.d/sctp.conf
    fi

    if ! LC_ALL=C grep -q -m 1 "^blacklist sctp$" /etc/modprobe.d/sctp.conf ; then
    	echo "blacklist sctp" >> /etc/modprobe.d/sctp.conf
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.10.1
      - DISA-STIG-OL09-00-000043
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - kernel_module_sctp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'sctp' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/sctp.conf
        regexp: install\s+sctp
        line: install sctp /bin/false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.10.1
      - DISA-STIG-OL09-00-000043
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - kernel_module_sctp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'sctp' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/sctp.conf
        regexp: ^blacklist sctp$
        line: blacklist sctp
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.10.1
      - DISA-STIG-OL09-00-000043
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - kernel_module_sctp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
#### Rule Disable TIPC Support [ref]
The Transparent Inter-Process Communication (TIPC) protocol is designed to provide communications between nodes in a cluster. To configure the system to prevent the `tipc` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/tipc.conf`:
    install tipc /bin/false
This entry will cause a non-zero return value during a `tipc` module installation and additionally convey the meaning of the entry to the user in form of an error message. If you would like to omit a non-zero return value and an error message, you may want to add a different line instead (both `/bin/true` and `/bin/false` are allowed by OVAL and will be accepted by the scan):
    install tipc /bin/true
To configure the system to prevent the `tipc` from being used, add the following line to file `/etc/modprobe.d/tipc.conf`:
    blacklist tipc
Warning: This configuration baseline was created to deploy the base operating system for general purpose workloads. When the operating system is configured for certain purposes, such as a node in High Performance Computing cluster, it is expected that the `tipc` kernel module will be loaded.
Rationale: | Disabling TIPC protects the system against exploitation of any flaws in its implementation.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
isa-62443-2009 | 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-2013 | SR 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-2013 | A.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
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.IP-1, PR.PT-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000095-GPOS-00049
stigid | OL09-00-000044
stigref | SV-271447r1092464_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if LC_ALL=C grep -q -m 1 "^install tipc" /etc/modprobe.d/tipc.conf ; then

    	sed -i 's#^install tipc.*#install tipc /bin/false#g' /etc/modprobe.d/tipc.conf
    else
    	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/tipc.conf
    	echo "install tipc /bin/false" >> /etc/modprobe.d/tipc.conf
    fi

    if ! LC_ALL=C grep -q -m 1 "^blacklist tipc$" /etc/modprobe.d/tipc.conf ; then
    	echo "blacklist tipc" >> /etc/modprobe.d/tipc.conf
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000044
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_tipc_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'tipc' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/tipc.conf
        regexp: install\s+tipc
        line: install tipc /bin/false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000044
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_tipc_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'tipc' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/tipc.conf
        regexp: ^blacklist tipc$
        line: blacklist tipc
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000044
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_tipc_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
Group Wireless Networking Group contains 1 group and 1 rule
[ref] Wireless networking, such as 802.11 (WiFi) and Bluetooth, can present a security risk to sensitive or classified systems and networks. Wireless networking hardware is much more likely to be included in laptop or portable systems than in desktops or servers. Removal of hardware provides the greatest assurance that the wireless capability remains disabled. Acquisition policies often include provisions to prevent the purchase of equipment that will be used in sensitive spaces and includes wireless capabilities. If it is impractical to remove the wireless hardware, and policy permits the device to enter sensitive spaces as long as wireless is disabled, efforts should instead focus on disabling wireless capability via software.
Group Disable Wireless Through Software Configuration Group contains 1 rule
[ref] If it is impossible to remove the wireless hardware from the device in question, disable as much of it as possible through software. The following methods can disable software support for wireless networking, but note that these methods do not prevent malicious software or careless users from re-activating the devices.
#### Rule Disable Bluetooth Kernel Module [ref]
The kernel's module loading system can be configured to prevent loading of the Bluetooth module. Add the following to the appropriate `/etc/modprobe.d` configuration file to prevent the loading of the Bluetooth module:
    install bluetooth /bin/true
Rationale: | If Bluetooth functionality must be disabled, preventing the kernel from loading the kernel module provides an additional safeguard against its activation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_bluetooth_disabled
References: |
cis-csc | 11, 12, 14, 15, 3, 8, 9
cjis | 5.13.1.3
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
cui | 3.1.16
isa-62443-2009 | 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-2013 | SR 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-2013 | A.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
nist | AC-18(a), AC-18(3), CM-7(a), CM-7(b), CM-6(a), MP-7
nist-csf | PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000095-GPOS-00049, SRG-OS-000300-GPOS-00118
stigid | OL09-00-000046
stigref | SV-271449r1091059_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if LC_ALL=C grep -q -m 1 "^install bluetooth" /etc/modprobe.d/bluetooth.conf ; then

    	sed -i 's#^install bluetooth.*#install bluetooth /bin/false#g' /etc/modprobe.d/bluetooth.conf
    else
    	echo -e "\n# Disable per security requirements" >> /etc/modprobe.d/bluetooth.conf
    	echo "install bluetooth /bin/false" >> /etc/modprobe.d/bluetooth.conf
    fi

    if ! LC_ALL=C grep -q -m 1 "^blacklist bluetooth$" /etc/modprobe.d/bluetooth.conf ; then
    	echo "blacklist bluetooth" >> /etc/modprobe.d/bluetooth.conf
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.13.1.3
      - DISA-STIG-OL09-00-000046
      - NIST-800-171-3.1.16
      - NIST-800-53-AC-18(3)
      - NIST-800-53-AC-18(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - disable_strategy
      - kernel_module_bluetooth_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'bluetooth' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/bluetooth.conf
        regexp: install\s+bluetooth
        line: install bluetooth /bin/false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.13.1.3
      - DISA-STIG-OL09-00-000046
      - NIST-800-171-3.1.16
      - NIST-800-53-AC-18(3)
      - NIST-800-53-AC-18(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - disable_strategy
      - kernel_module_bluetooth_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'bluetooth' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/bluetooth.conf
        regexp: ^blacklist bluetooth$
        line: blacklist bluetooth
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.13.1.3
      - DISA-STIG-OL09-00-000046
      - NIST-800-171-3.1.16
      - NIST-800-53-AC-18(3)
      - NIST-800-53-AC-18(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - disable_strategy
      - kernel_module_bluetooth_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
Group File Permissions and Masks Group contains 4 groups and 13 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 Oracle Linux 9 installations:
    $ mount -t xfs | awk '{print $3}'
For any systems that use a different local filesystem type, modify this command as appropriate.
Group Restrict Partition Mount Options Group contains 3 rules
[ref] System partitions can be mounted with certain options that limit what files on those partitions can do. These options are set in the `/etc/fstab` configuration file, and can be used to make certain types of malicious behavior more difficult.
#### Rule Add nodev Option to /var/log/audit [ref]
The `nodev` mount option can be used to prevent device files from being created in `/var/log/audit`. Legitimate character and block devices should exist only in the `/dev` directory on the root partition or within chroot jails built for system services. Add the `nodev` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/log/audit`.
Rationale: | The only legitimate location for device files is the `/dev` directory located on the root partition. The only exception to this is chroot jails.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nodev
References: |
nerc-cip | CIP-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
nist | CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csf | PR.IP-1, PR.PT-2, PR.PT-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000368-GPOS-00154
stigid | OL09-00-002064
stigref | SV-271660r1091692_rule
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) && { ( findmnt --kernel "/var/log/audit" > /dev/null || findmnt --fstab "/var/log/audit" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log/audit has to be defined in /etc/fstab
            # before this remediation can be executed. In case it is not defined, the
            # remediation aborts and no changes regarding the mount point are done.
            mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" "/var/log/audit")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log/audit' is not even in /etc/fstab, so we can't set up mount options" >&2;
                    echo "Not remediating, because there is no record of /var/log/audit in /etc/fstab" >&2; return 1; }

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /var/log/audit)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
            # runtime opts without some automatic kernel/userspace-added defaults
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                        | sed -E "s/(rw|defaults|seclabel|nodev)(,|$)//g;s/,$//")
            [ "$previous_mount_opts" ] && previous_mount_opts+=","
            # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
            # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
            fs_type=""
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo " /var/log/audit  defaults,${previous_mount_opts}nodev 0 0" >> /etc/fstab
        # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
        elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "nodev"; then
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
            sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,nodev|" /etc/fstab
        fi

        if mkdir -p "/var/log/audit"; then
            if mountpoint -q "/var/log/audit"; then
                mount -o remount --target "/var/log/audit"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | enable
    part /var/log/audit --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log/audit: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log/audit'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log/audit: Create mount_info dictionary variable'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - '{{ device_name.stdout_lines[0].split() | map(''lower'') | list }}'
      - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log/audit: If /var/log/audit not mounted, craft
        mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /var/log/audit
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - ("--fstab" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log/audit: Make sure nodev option is part of the
        to /var/log/audit options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log/audit: Ensure /var/log/audit is mounted with
        nodev option'
      ansible.posix.mount:
        path: /var/log/audit
        src: '{{ mount_info.source | default('''') }}'
        opts: '{{ mount_info.options | default('''') }}'
        state: mounted
        fstype: '{{ mount_info.fstype | default('''') }}'
      register: mount_result
      failed_when:
      - mount_result is failed
      - '''target is busy'' not in (mount_result.msg | default(''''))'
      - '''already mounted'' not in (mount_result.msg | default(''''))'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("--fstab"
        | length == 0)
      tags:
      - DISA-STIG-OL09-00-002064
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nodev
      - no_reboot_needed
#### Rule Add noexec Option to /var/log/audit [ref]
The `noexec` mount option can be used to prevent binaries from being executed out of `/var/log/audit`. Add the `noexec` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/log/audit`.
Rationale: | Allowing users to execute binaries from directories containing audit log files such as `/var/log/audit` should never be necessary in normal operation and can expose the system to potential compromise.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_log_audit_noexec
References: |
nerc-cip | CIP-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
nist | CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csf | PR.IP-1, PR.PT-2, PR.PT-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000368-GPOS-00154
stigid | OL09-00-002065
stigref | SV-271661r1091695_rule
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) && { ( findmnt --kernel "/var/log/audit" > /dev/null || findmnt --fstab "/var/log/audit" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log/audit has to be defined in /etc/fstab
            # before this remediation can be executed. In case it is not defined, the
            # remediation aborts and no changes regarding the mount point are done.
            mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" "/var/log/audit")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log/audit' is not even in /etc/fstab, so we can't set up mount options" >&2;
                    echo "Not remediating, because there is no record of /var/log/audit in /etc/fstab" >&2; return 1; }

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /var/log/audit)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
            # runtime opts without some automatic kernel/userspace-added defaults
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                        | sed -E "s/(rw|defaults|seclabel|noexec)(,|$)//g;s/,$//")
            [ "$previous_mount_opts" ] && previous_mount_opts+=","
            # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
            # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
            fs_type=""
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo " /var/log/audit  defaults,${previous_mount_opts}noexec 0 0" >> /etc/fstab
        # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
        elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "noexec"; then
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
            sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,noexec|" /etc/fstab
        fi

        if mkdir -p "/var/log/audit"; then
            if mountpoint -q "/var/log/audit"; then
                mount -o remount --target "/var/log/audit"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | enable
    part /var/log/audit --mountoptions="noexec"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log/audit: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log/audit'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log/audit: Create mount_info dictionary variable'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - '{{ device_name.stdout_lines[0].split() | map(''lower'') | list }}'
      - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log/audit: If /var/log/audit not mounted, craft
        mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /var/log/audit
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - ("--fstab" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log/audit: Make sure noexec option is part of the
        to /var/log/audit options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''noexec''
          }) }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "noexec" not in (mount_info.options | default(''))
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log/audit: Ensure /var/log/audit is mounted with
        noexec option'
      ansible.posix.mount:
        path: /var/log/audit
        src: '{{ mount_info.source | default('''') }}'
        opts: '{{ mount_info.options | default('''') }}'
        state: mounted
        fstype: '{{ mount_info.fstype | default('''') }}'
      register: mount_result
      failed_when:
      - mount_result is failed
      - '''target is busy'' not in (mount_result.msg | default(''''))'
      - '''already mounted'' not in (mount_result.msg | default(''''))'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("--fstab"
        | length == 0)
      tags:
      - DISA-STIG-OL09-00-002065
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_noexec
      - no_reboot_needed
#### Rule Add nosuid Option to /var/log/audit [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/var/log/audit`. The SUID and SGID permissions should not be required in directories containing audit log files. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/log/audit`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from partitions designated for audit log files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_log_audit_nosuid
References: |
nerc-cip | CIP-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
nist | CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csf | PR.IP-1, PR.PT-2, PR.PT-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000368-GPOS-00154
stigid | OL09-00-002066
stigref | SV-271662r1091698_rule
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel ;} && { rpm --quiet -q rpm-ostree ;} && { rpm --quiet -q bootc ;} && { ! rpm --quiet -q openshift-kubelet ;} && ([ -f /run/ostree-booted ] || [ -L /ostree ]) ) && ! ( [ -f /.dockerenv ] || [ -f /run/.containerenv ] ) ) && { ( findmnt --kernel "/var/log/audit" > /dev/null || findmnt --fstab "/var/log/audit" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log/audit has to be defined in /etc/fstab
            # before this remediation can be executed. In case it is not defined, the
            # remediation aborts and no changes regarding the mount point are done.
            mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" "/var/log/audit")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log/audit' is not even in /etc/fstab, so we can't set up mount options" >&2;
                    echo "Not remediating, because there is no record of /var/log/audit in /etc/fstab" >&2; return 1; }

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /var/log/audit)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if ! grep -q "$mount_point_match_regexp" /etc/fstab; then
            # runtime opts without some automatic kernel/userspace-added defaults
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/mtab | head -1 |  awk '{print $4}' \
                        | sed -E "s/(rw|defaults|seclabel|nosuid)(,|$)//g;s/,$//")
            [ "$previous_mount_opts" ] && previous_mount_opts+=","
            # In iso9660 filesystems mtab could describe a "blocksize" value, this should be reflected in
            # fstab as "block".  The next variable is to satisfy shellcheck SC2050.
            fs_type=""
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo " /var/log/audit  defaults,${previous_mount_opts}nosuid 0 0" >> /etc/fstab
        # If the mount_opt option is not already in the mount point's /etc/fstab entry, add it
        elif ! grep "$mount_point_match_regexp" /etc/fstab | grep -q "nosuid"; then
            previous_mount_opts=$(grep "$mount_point_match_regexp" /etc/fstab | awk '{print $4}')
            sed -i "s|\(${mount_point_match_regexp}.*${previous_mount_opts}\)|\1,nosuid|" /etc/fstab
        fi

        if mkdir -p "/var/log/audit"; then
            if mountpoint -q "/var/log/audit"; then
                mount -o remount --target "/var/log/audit"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | enable
    part /var/log/audit --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log/audit: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log/audit'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log/audit: Create mount_info dictionary variable'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - '{{ device_name.stdout_lines[0].split() | map(''lower'') | list }}'
      - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log/audit: If /var/log/audit not mounted, craft
        mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /var/log/audit
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - ("--fstab" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log/audit: Make sure nosuid option is part of the
        to /var/log/audit options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log/audit: Ensure /var/log/audit is mounted with
        nosuid option'
      ansible.posix.mount:
        path: /var/log/audit
        src: '{{ mount_info.source | default('''') }}'
        opts: '{{ mount_info.options | default('''') }}'
        state: mounted
        fstype: '{{ mount_info.fstype | default('''') }}'
      register: mount_result
      failed_when:
      - mount_result is failed
      - '''target is busy'' not in (mount_result.msg | default(''''))'
      - '''already mounted'' not in (mount_result.msg | default(''''))'
      when:
      - ( not ( "kernel" in ansible_facts.packages and "rpm-ostree" in ansible_facts.packages
        and "bootc" in ansible_facts.packages and not "openshift-kubelet" in ansible_facts.packages
        and "ostree" in ansible_proc_cmdline ) and not ( ansible_virtualization_type in
        ["docker", "lxc", "openvz", "podman", "container"] ) )
      - '"/var/log/audit" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("--fstab"
        | length == 0)
      tags:
      - DISA-STIG-OL09-00-002066
      - NIST-800-53-AC-6
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-MP-7
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_log_audit_nosuid
      - no_reboot_needed
Group Restrict Programs from Dangerous Execution Patterns Group contains 2 groups and 10 rules
[ref] The recommendations in this section are designed to ensure that the system's features to protect against potentially dangerous program execution are activated. These protections are applied at the system initialization or kernel level, and defend against certain types of badly-configured or compromised programs.
Group Disable Core Dumps Group contains 1 rule
[ref] A core dump file is the memory image of an executable program when it was terminated by the operating system due to errant behavior. In most cases, only software developers legitimately need to access these files. The core dump files may also contain sensitive information, or unnecessarily occupy large amounts of disk space. Once a hard limit is set in `/etc/security/limits.conf`, or to a file within the `/etc/security/limits.d/` directory, a user cannot increase that limit within his or her own session. If access to core dumps is required, consider restricting them to only certain users or groups. See the `limits.conf` man page for more information. The core dumps of setuid programs are further protected. The `sysctl` variable `fs.suid_dumpable` controls whether the kernel allows core dumps from these programs at all. The default value of 0 is recommended.
#### Rule Disable acquiring, saving, and processing core dumps [ref]
The `systemd-coredump.socket` unit is a socket activation of the `systemd-coredump@.service` which processes core dumps. By masking the unit, core dump processing is disabled.
Rationale: | A core dump includes a memory image taken at the time the operating system terminates an application. The memory image could contain sensitive data and is generally useful only for developers trying to debug problems.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_systemd-coredump_disabled
References: |
nist | SC-7(10)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000480-GPOS-00227
stigid | OL09-00-002384
stigref | SV-271732r1091908_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    SOCKET_NAME="systemd-coredump.socket"
    SYSTEMCTL_EXEC='/usr/bin/systemctl'

    if "$SYSTEMCTL_EXEC" -q list-unit-files --type socket | grep -q "$SOCKET_NAME"; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop "$SOCKET_NAME"
        fi
        "$SYSTEMCTL_EXEC" mask "$SOCKET_NAME"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002384
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-coredump_disabled

    - name: Disable acquiring, saving, and processing core dumps - Collect systemd Socket
        Units Present in the System
      ansible.builtin.command:
        cmd: systemctl -q list-unit-files --type socket
      register: result_systemd_unit_files
      changed_when: false
      check_mode: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002384
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-coredump_disabled

    - name: Disable acquiring, saving, and processing core dumps - Ensure systemd-coredump.socket
        is Masked
      ansible.builtin.systemd:
        name: systemd-coredump.socket
        state: stopped
        enabled: false
        masked: true
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - result_systemd_unit_files.stdout_lines is search("systemd-coredump.socket")
      tags:
      - DISA-STIG-OL09-00-002384
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-coredump_disabled
Group Enable ExecShield Group contains 1 rule
[ref] ExecShield describes kernel features that provide protection against exploitation of memory corruption errors such as buffer overflows. These features include random placement of the stack and other memory regions, prevention of execution in memory that should only hold data, and special handling of text buffers. These protections are enabled by default on 32-bit systems and controlled through `sysctl` variables `kernel.exec-shield` and `kernel.randomize_va_space`. On the latest 64-bit systems, `kernel.exec-shield` cannot be enabled or disabled with `sysctl`.
#### Rule Restrict Exposed Kernel Pointer Addresses Access [ref]
To set the runtime status of the `kernel.kptr_restrict` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.kptr_restrict=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.kptr_restrict = 1
Rationale: | Exposing kernel pointers (through procfs or `seq_printf()`) exposes kernel writeable structures which may contain functions pointers. If a write vulnerability occurs in the kernel, allowing write access to any of this structure, the kernel can be compromised. This option disallow any program without the CAP_SYSLOG capability to get the addresses of kernel pointers by replacing them with 0.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_kptr_restrict
References: |
nerc-cip | CIP-002-5 R1.1, CIP-002-5 R1.2, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 4.1, CIP-004-6 4.2, CIP-004-6 R2.2.3, CIP-004-6 R2.2.4, CIP-004-6 R2.3, CIP-004-6 R4, CIP-005-6 R1, CIP-005-6 R1.1, CIP-005-6 R1.2, CIP-007-3 R3, CIP-007-3 R3.1, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3, CIP-007-3 R8.4, CIP-009-6 R.1.1, CIP-009-6 R4
nist | SC-30, SC-30(2), SC-30(5), CM-6(a)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000132-GPOS-00067, SRG-OS-000433-GPOS-00192, SRG-OS-000480-GPOS-00227
anssi | R9
ism | 1409
stigid | OL09-00-002408
stigref | SV-271747r1117266_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.kptr_restrict from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_kptr_restrict.conf'

    sysctl_kernel_kptr_restrict_value='1'

    #
    # Set runtime for kernel.kptr_restrict
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.kptr_restrict="$sysctl_kernel_kptr_restrict_value"
    fi

    #
    # If kernel.kptr_restrict present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "kernel.kptr_restrict = value" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.kptr_restrict")

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

    # 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 "^kernel.kptr_restrict\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.kptr_restrict\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict
    - name: XCCDF Value sysctl_kernel_kptr_restrict_value # promote to variable
      set_fact:
        sysctl_kernel_kptr_restrict_value: !!str 1
      tags:
        - always

    - name: Restrict Exposed Kernel Pointer Addresses Access - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict

    - name: Restrict Exposed Kernel Pointer Addresses Access - Find all files that contain
        kernel.kptr_restrict
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.kptr_restrict\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict

    - name: Restrict Exposed Kernel Pointer Addresses Access - Find all files that set
        kernel.kptr_restrict to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.kptr_restrict\s*=\s*{{ sysctl_kernel_kptr_restrict_value }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict

    - name: Restrict Exposed Kernel Pointer Addresses Access - Comment out any occurrences
        of kernel.kptr_restrict from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.kptr_restrict
        replace: '#kernel.kptr_restrict'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict

    - name: Restrict Exposed Kernel Pointer Addresses Access - Comment out any occurrences
        of kernel.kptr_restrict from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.kptr_restrict
        replace: '#kernel.kptr_restrict'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict

    - name: Restrict Exposed Kernel Pointer Addresses Access - Ensure sysctl kernel.kptr_restrict
        is set
      ansible.posix.sysctl:
        name: kernel.kptr_restrict
        value: '{{ sysctl_kernel_kptr_restrict_value }}'
        sysctl_file: /etc/sysctl.d/kernel_kptr_restrict.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002408
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - NIST-800-53-SC-30(5)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kptr_restrict
#### Rule Disable storing core dumps [ref]
The `kernel.core_pattern` option specifies the core dumpfile pattern name. It can be set to an empty string. In this case, the kernel behaves differently based on another related option. If `kernel.core_uses_pid` is set to `1`, then a file named as `.PID` (where `PID` is process ID of the crashed process) is created in the working directory. If `kernel.core_uses_pid` is set to `0`, no coredump is saved. To set the runtime status of the `kernel.core_pattern` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.core_pattern=
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.core_pattern =
Rationale: | A core dump includes a memory image taken at the time the operating system terminates an application. The memory image could contain sensitive data and is generally useful only for developers trying to debug problems.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_core_pattern_empty_string
References: |
ospp | FMT_SMF_EXT.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.core_pattern from /etc/sysctl.d/*.conf files
    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_core_pattern.conf'

    #
    # Set runtime for kernel.core_pattern
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.core_pattern=""
    fi

    #
    # If kernel.core_pattern present in /etc/sysctl.conf, change value to empty
    #	else, add "kernel.core_pattern =" to /etc/sysctl.conf
    #

    sed -i --follow-symlinks "/^kernel.core_pattern/d" /etc/sysctl.conf

    # 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' <<< "^kernel.core_pattern")

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

    # 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 "^kernel.core_pattern\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.core_pattern\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Find all files that contain kernel.core_pattern
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.core_pattern\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Find all files that set kernel.core_pattern to
        correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.core_pattern\s*=\s*$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Comment out any occurrences of kernel.core_pattern
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.core_pattern
        replace: '#kernel.core_pattern'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Comment out any occurrences of kernel.core_pattern
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.core_pattern
        replace: '#kernel.core_pattern'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string

    - name: Disable storing core dumps - Ensure sysctl kernel.core_pattern is set to empty
      ansible.posix.sysctl:
        name: kernel.core_pattern
        value: ' '
        sysctl_file: /etc/sysctl.d/kernel_core_pattern.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_pattern_empty_string
#### Rule Configure file name of core dumps [ref]
To set the runtime status of the `kernel.core_uses_pid` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.core_uses_pid=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.core_uses_pid = 0
Rationale: | The default coredump filename is `core`. By setting `core_uses_pid` to `1`, the coredump filename becomes `core.PID`. If `core_pattern` does not include `%p` (default does not) and `core_uses_pid` is set, then `.PID` will be appended to the filename. When combined with `kernel.core_pattern = ""` configuration, it is ensured that no core dumps are generated and also no confusing error messages are printed by a shell.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_core_uses_pid
References: |
ospp | FMT_SMF_EXT.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.core_uses_pid from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_core_uses_pid.conf'

    #
    # Set runtime for kernel.core_uses_pid
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.core_uses_pid="0"
    fi

    #
    # If kernel.core_uses_pid present in /etc/sysctl.conf, change value to "0"
    #	else, add "kernel.core_uses_pid = 0" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.core_uses_pid")

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

    # 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 "^kernel.core_uses_pid\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.core_uses_pid\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Find all files that contain kernel.core_uses_pid
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.core_uses_pid\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Find all files that set kernel.core_uses_pid
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.core_uses_pid\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Comment out any occurrences of kernel.core_uses_pid
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.core_uses_pid
        replace: '#kernel.core_uses_pid'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Comment out any occurrences of kernel.core_uses_pid
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.core_uses_pid
        replace: '#kernel.core_uses_pid'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid

    - name: Configure file name of core dumps - Ensure sysctl kernel.core_uses_pid is
        set to 0
      ansible.posix.sysctl:
        name: kernel.core_uses_pid
        value: '0'
        sysctl_file: /etc/sysctl.d/kernel_core_uses_pid.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_core_uses_pid
#### Rule Restrict Access to Kernel Message Buffer [ref]
To set the runtime status of the `kernel.dmesg_restrict` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.dmesg_restrict=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.dmesg_restrict = 1
Rationale: | Unprivileged access to the kernel syslog can expose sensitive kernel address information.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_dmesg_restrict
References: |
cui | 3.1.5
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
nist | SI-11(a), SI-11(b)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000132-GPOS-00067, SRG-OS-000138-GPOS-00069
app-srg-ctr | SRG-APP-000243-CTR-000600
anssi | R9
ism | 1546
stigid | OL09-00-002406
stigref | SV-271745r1117266_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.dmesg_restrict from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_dmesg_restrict.conf'

    #
    # Set runtime for kernel.dmesg_restrict
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.dmesg_restrict="1"
    fi

    #
    # If kernel.dmesg_restrict present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.dmesg_restrict = 1" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.dmesg_restrict")

    # 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 "^kernel.dmesg_restrict\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.dmesg_restrict\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Find all files that contain kernel.dmesg_restrict
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.dmesg_restrict\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Find all files that set kernel.dmesg_restrict
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.dmesg_restrict\s*=\s*1$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Comment out any occurrences of
        kernel.dmesg_restrict from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.dmesg_restrict
        replace: '#kernel.dmesg_restrict'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Comment out any occurrences of
        kernel.dmesg_restrict from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.dmesg_restrict
        replace: '#kernel.dmesg_restrict'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict

    - name: Restrict Access to Kernel Message Buffer - Ensure sysctl kernel.dmesg_restrict
        is set to 1
      ansible.posix.sysctl:
        name: kernel.dmesg_restrict
        value: '1'
        sysctl_file: /etc/sysctl.d/kernel_dmesg_restrict.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002406
      - NIST-800-171-3.1.5
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_dmesg_restrict
#### Rule Disable Kernel Image Loading [ref]
To set the runtime status of the `kernel.kexec_load_disabled` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.kexec_load_disabled=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.kexec_load_disabled = 1
Rationale: | Disabling kexec_load allows greater control of the kernel memory. It makes it impossible to load another kernel image after it has been disabled.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_kexec_load_disabled
References: |
nist | CM-6
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000480-GPOS-00227, SRG-OS-000366-GPOS-00153
ism | 1409
stigid | OL09-00-002428
stigref | SV-271766r1092010_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.kexec_load_disabled from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_kexec_load_disabled.conf'

    #
    # Set runtime for kernel.kexec_load_disabled
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.kexec_load_disabled="1"
    fi

    #
    # If kernel.kexec_load_disabled present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.kexec_load_disabled = 1" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.kexec_load_disabled")

    # 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 "^kernel.kexec_load_disabled\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.kexec_load_disabled\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Find all files that contain kernel.kexec_load_disabled
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.kexec_load_disabled\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Find all files that set kernel.kexec_load_disabled
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.kexec_load_disabled\s*=\s*1$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Comment out any occurrences of kernel.kexec_load_disabled
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.kexec_load_disabled
        replace: '#kernel.kexec_load_disabled'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Comment out any occurrences of kernel.kexec_load_disabled
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.kexec_load_disabled
        replace: '#kernel.kexec_load_disabled'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled

    - name: Disable Kernel Image Loading - Ensure sysctl kernel.kexec_load_disabled is
        set to 1
      ansible.posix.sysctl:
        name: kernel.kexec_load_disabled
        value: '1'
        sysctl_file: /etc/sysctl.d/kernel_kexec_load_disabled.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002428
      - NIST-800-53-CM-6
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_kexec_load_disabled
#### Rule Disallow kernel profiling by unprivileged users [ref]
To set the runtime status of the `kernel.perf_event_paranoid` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.perf_event_paranoid=2
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.perf_event_paranoid = 2
Rationale: | Kernel profiling can reveal sensitive information about kernel behaviour.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_perf_event_paranoid
References: |
nist | AC-6
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000132-GPOS-00067, SRG-OS-000138-GPOS-00069
app-srg-ctr | SRG-APP-000243-CTR-000600
anssi | R9
stigid | OL09-00-002407
stigref | SV-271746r1117266_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.perf_event_paranoid from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_perf_event_paranoid.conf'

    #
    # Set runtime for kernel.perf_event_paranoid
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.perf_event_paranoid="2"
    fi

    #
    # If kernel.perf_event_paranoid present in /etc/sysctl.conf, change value to "2"
    #	else, add "kernel.perf_event_paranoid = 2" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.perf_event_paranoid")

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

    # 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 "^kernel.perf_event_paranoid\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.perf_event_paranoid\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Find all files that contain
        kernel.perf_event_paranoid
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.perf_event_paranoid\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Find all files that set
        kernel.perf_event_paranoid to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.perf_event_paranoid\s*=\s*2$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Comment out any occurrences
        of kernel.perf_event_paranoid from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.perf_event_paranoid
        replace: '#kernel.perf_event_paranoid'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Comment out any occurrences
        of kernel.perf_event_paranoid from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.perf_event_paranoid
        replace: '#kernel.perf_event_paranoid'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid

    - name: Disallow kernel profiling by unprivileged users - Ensure sysctl kernel.perf_event_paranoid
        is set to 2
      ansible.posix.sysctl:
        name: kernel.perf_event_paranoid
        value: '2'
        sysctl_file: /etc/sysctl.d/kernel_perf_event_paranoid.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002407
      - NIST-800-53-AC-6
      - disable_strategy
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
      - sysctl_kernel_perf_event_paranoid
#### Rule Disable Access to Network bpf() Syscall From Unprivileged Processes [ref]
To prevent unprivileged processes from using the `bpf()` syscall the `kernel.unprivileged_bpf_disabled` kernel parameter must be set to `1` or `2`. Writing `1` to this entry will disable unprivileged calls to `bpf()`; once disabled, calling `bpf()` without `CAP_SYS_ADMIN` or `CAP_BPF` will return `-EPERM`. Once set to `1`, this can't be cleared from the running kernel anymore. To set the runtime status of the `kernel.unprivileged_bpf_disabled` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.unprivileged_bpf_disabled=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.unprivileged_bpf_disabled = 1
Writing `2` to this entry will also disable unprivileged calls to `bpf()`, however, an admin can still change this setting later on, if needed, by writing `0` or `1` to this entry. To set the runtime status of the `kernel.unprivileged_bpf_disabled` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.unprivileged_bpf_disabled=2
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.unprivileged_bpf_disabled = 2
Rationale: | Loading and accessing the packet filters programs and maps using the bpf() syscall has the potential of revealing sensitive information about the kernel state.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_unprivileged_bpf_disabled_accept_default
References: |
nist | AC-6, SC-7(10)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000132-GPOS-00067, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.unprivileged_bpf_disabled from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_unprivileged_bpf_disabled.conf'

    sysctl_kernel_unprivileged_bpf_disabled_value='2'

    #
    # Set runtime for kernel.unprivileged_bpf_disabled
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.unprivileged_bpf_disabled="$sysctl_kernel_unprivileged_bpf_disabled_value"
    fi

    #
    # If kernel.unprivileged_bpf_disabled present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "kernel.unprivileged_bpf_disabled = value" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.unprivileged_bpf_disabled")

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

    # 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 "^kernel.unprivileged_bpf_disabled\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.unprivileged_bpf_disabled\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default
    - name: XCCDF Value sysctl_kernel_unprivileged_bpf_disabled_value # promote to variable
      set_fact:
        sysctl_kernel_unprivileged_bpf_disabled_value: !!str 2
      tags:
        - always

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Set
        fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Find
        all files that contain kernel.unprivileged_bpf_disabled
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.unprivileged_bpf_disabled\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Find
        all files that set kernel.unprivileged_bpf_disabled to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.unprivileged_bpf_disabled\s*=\s*{{ sysctl_kernel_unprivileged_bpf_disabled_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Comment
        out any occurrences of kernel.unprivileged_bpf_disabled from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.unprivileged_bpf_disabled
        replace: '#kernel.unprivileged_bpf_disabled'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Comment
        out any occurrences of kernel.unprivileged_bpf_disabled from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.unprivileged_bpf_disabled
        replace: '#kernel.unprivileged_bpf_disabled'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default

    - name: Disable Access to Network bpf() Syscall From Unprivileged Processes - Ensure
        sysctl kernel.unprivileged_bpf_disabled is set
      ansible.posix.sysctl:
        name: kernel.unprivileged_bpf_disabled
        value: '{{ sysctl_kernel_unprivileged_bpf_disabled_value }}'
        sysctl_file: /etc/sysctl.d/kernel_unprivileged_bpf_disabled.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_unprivileged_bpf_disabled_accept_default
#### Rule Restrict usage of ptrace to descendant processes [ref]
To set the runtime status of the `kernel.yama.ptrace_scope` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.yama.ptrace_scope=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.yama.ptrace_scope = 1
Rationale: | Unrestricted usage of ptrace allows compromised binaries to run ptrace on another processes of the user. Like this, the attacker can steal sensitive information from the target processes (e.g. SSH sessions, web browser, ...) without any additional assistance from the user (i.e. without resorting to phishing).
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_yama_ptrace_scope
References: |
nist | SC-7(10)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000132-GPOS-00067, SRG-OS-000480-GPOS-00227
anssi | R11
ism | 1409
stigid | OL09-00-002410
stigref | SV-271749r1117266_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of kernel.yama.ptrace_scope from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

      matching_list=$(grep -P '^(?!#).*[\s]*kernel.yama.ptrace_scope.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "kernel.yama.ptrace_scope" matches to preserve user data
          sed -i --follow-symlinks "s/^${escaped_entry}$/# &/g" $f
        done <<< "$matching_list"
      fi
    done

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/kernel_yama_ptrace_scope.conf'

    #
    # Set runtime for kernel.yama.ptrace_scope
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w kernel.yama.ptrace_scope="1"
    fi

    #
    # If kernel.yama.ptrace_scope present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.yama.ptrace_scope = 1" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^kernel.yama.ptrace_scope")

    # 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 "^kernel.yama.ptrace_scope\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.yama.ptrace_scope\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Find all files that contain
        kernel.yama.ptrace_scope
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.yama.ptrace_scope\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Find all files that set
        kernel.yama.ptrace_scope to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.yama.ptrace_scope\s*=\s*1$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Comment out any occurrences
        of kernel.yama.ptrace_scope from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.yama.ptrace_scope
        replace: '#kernel.yama.ptrace_scope'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Comment out any occurrences
        of kernel.yama.ptrace_scope from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.yama.ptrace_scope
        replace: '#kernel.yama.ptrace_scope'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope

    - name: Restrict usage of ptrace to descendant processes - Ensure sysctl kernel.yama.ptrace_scope
        is set to 1
      ansible.posix.sysctl:
        name: kernel.yama.ptrace_scope
        value: '1'
        sysctl_file: /etc/sysctl.d/kernel_yama_ptrace_scope.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002410
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope
#### Rule Disable the use of user namespaces [ref]
To set the runtime status of the `user.max_user_namespaces` kernel parameter, run the following command:
    $ sudo sysctl -w user.max_user_namespaces=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    user.max_user_namespaces = 0
When containers are deployed on the machine, the value should be set to large non-zero value. Warning: Remediation of this rule might impair or prevent functionality of certain applications. This stands especially for general container usage and for certain desktop applications. There is an alternative rule which performs the same check but it intentionally lacks the remediation part. If needed, you can use the rule `sysctl_user_max_user_namespaces_no_remediation`. In that case, ensure that such use case is properly documented.
Rationale: | It is detrimental for operating systems to provide, or install by default, functionality exceeding requirements or system objectives. These unnecessary capabilities or services are often overlooked and therefore may remain unsecured. They increase the risk to the platform by providing additional attack vectors. User namespaces are used primarily for Linux containers. The value 0 disallows the use of user namespaces.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_user_max_user_namespaces
References: |
nist | SC-39, CM-6(a)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    # Comment out any occurrences of user.max_user_namespaces from /etc/sysctl.d/*.conf files

    for f in /etc/sysctl.d/*.conf /run/sysctl.d/*.conf /usr/local/lib/sysctl.d/*.conf; do

      # skip systemd-sysctl symlink (/etc/sysctl.d/99-sysctl.conf -> /etc/sysctl.conf)
      if [[ "$(readlink -f "$f")" == "/etc/sysctl.conf" ]]; then continue; fi

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

    #
    # Set sysctl config file which to save the desired value
    #

    SYSCONFIG_FILE='/etc/sysctl.d/user_max_user_namespaces.conf'

    #
    # Set runtime for user.max_user_namespaces
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w user.max_user_namespaces="0"
    fi

    #
    # If user.max_user_namespaces present in /etc/sysctl.conf, change value to "0"
    #	else, add "user.max_user_namespaces = 0" to /etc/sysctl.conf
    #

    sed -i "/^$SYSCONFIG_VAR/d" /etc/sysctl.conf

    # 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' <<< "^user.max_user_namespaces")

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

    # 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 "^user.max_user_namespaces\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^user.max_user_namespaces\\>.*/$escaped_formatted_output/gi" "${SYSCONFIG_FILE}"
    else
        if [[ -s "${SYSCONFIG_FILE}" ]] && [[ -n "$(tail -c 1 -- "${SYSCONFIG_FILE}" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "${SYSCONFIG_FILE}"
        fi
        printf '%s\n' "$formatted_output" >> "${SYSCONFIG_FILE}"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Find all files that contain user.max_user_namespaces
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*user.max_user_namespaces\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Find all files that set user.max_user_namespaces
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*user.max_user_namespaces\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Comment out any occurrences of user.max_user_namespaces
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*user.max_user_namespaces
        replace: '#user.max_user_namespaces'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - find_correct_value.stdout_lines | length == 0 or find_all_values.stdout_lines
        | length > find_correct_value.stdout_lines | length
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Comment out any occurrences of user.max_user_namespaces
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*user.max_user_namespaces
        replace: '#user.max_user_namespaces'
      with_fileglob:
      - /etc/sysctl.conf
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces

    - name: Disable the use of user namespaces - Ensure sysctl user.max_user_namespaces
        is set to 0
      ansible.posix.sysctl:
        name: user.max_user_namespaces
        value: '0'
        sysctl_file: /etc/sysctl.d/user_max_user_namespaces.conf
        state: present
        reload: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-39
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_user_max_user_namespaces
Group SELinux Group contains 2 rules
[ref] SELinux is a feature of the Linux kernel which can be used to guard against misconfigured or compromised programs. SELinux enforces the idea that programs should be limited in what files they can access and what actions they can take. The default SELinux policy, as configured on Oracle Linux 9, has been sufficiently developed and debugged that it should be usable on almost any system with minimal configuration and a small amount of system administrator training. This policy prevents system services - including most of the common network-visible services such as mail servers, FTP servers, and DNS servers - from accessing files which those services have no valid reason to access. This action alone prevents a huge amount of possible damage from network attacks against services, from trojaned software, and so forth. This guide recommends that SELinux be enabled using the default (targeted) policy on every Oracle Linux 9 system, unless that system has unusual requirements which make a stronger policy appropriate. For more information on SELinux, see https://docs.oracle.com/en/operating-systems/oracle-linux/selinux/.
#### Rule Configure SELinux Policy [ref]
The SELinux `targeted` policy is appropriate for general-purpose desktops and servers, as well as systems in many other roles. To configure the system to use this policy, add or correct the following line in `/etc/selinux/config`:
    SELINUXTYPE=targeted
Other policies, such as `mls`, provide additional security labeling and greater confinement but are not compatible with many general-purpose use cases.
Rationale: | Setting the SELinux policy to `targeted` or a more specialized policy ensures the system will confine processes that are likely to be targeted for exploitation, such as network or system services. Note: During the development or debugging of SELinux modules, it is common to temporarily place non-production systems in `permissive` mode. In such temporary cases, SELinux policies should be developed, and once work is completed, the system should be reconfigured to `targeted`.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_selinux_policytype
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9
cobit5 | APO01.06, APO11.04, APO13.01, BAI03.05, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, MEA02.01
cui | 3.1.2, 3.7.2
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-2009 | 4.2.3.4, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.4, 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.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, 4.4.3.3
isa-62443-2013 | SR 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.10, SR 2.11, SR 2.12, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 2.8, SR 2.9, 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-2013 | A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, 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.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cip | CIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nist | AC-3, AC-3(3)(a), AU-9, SC-7(21)
nist-csf | DE.AE-1, ID.AM-3, PR.AC-4, PR.AC-5, PR.AC-6, PR.DS-5, PR.PT-1, PR.PT-3, PR.PT-4
ospp | FMT_MOF_EXT.1
os-srg | SRG-OS-000445-GPOS-00199
app-srg-ctr | SRG-APP-000233-CTR-000585
anssi | R46, R64
bsi | APP.4.4.A4, SYS.1.6.A3, SYS.1.6.A18, SYS.1.6.A21
ccn | A.6.SEC-OL1
ism | 1409
pcidss4 | 1.2.6, 1.2
stigid | OL09-00-000065
stigref | SV-271453r1091071_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_selinux_policy_name='targeted'

    if [ -e "/etc/selinux/config" ] ; then

        LC_ALL=C sed -i "/^SELINUXTYPE=/Id" "/etc/selinux/config"
    else
        touch "/etc/selinux/config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/selinux/config"

    cp "/etc/selinux/config" "/etc/selinux/config.bak"
    # Insert at the end of the file
    printf '%s\n' "SELINUXTYPE=$var_selinux_policy_name" >> "/etc/selinux/config"
    # Clean up after ourselves.
    rm "/etc/selinux/config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000065
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - selinux_policytype
    - name: XCCDF Value var_selinux_policy_name # promote to variable
      set_fact:
        var_selinux_policy_name: !!str targeted
      tags:
        - always

    - name: Configure SELinux Policy
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUXTYPE=
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/selinux/config
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUXTYPE=
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/selinux/config
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUXTYPE=
          line: SELINUXTYPE={{ var_selinux_policy_name }}
          state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000065
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - selinux_policytype
#### Rule Ensure SELinux State is Enforcing [ref]
The SELinux state should be set to `enforcing` at system boot time. In the file `/etc/selinux/config`, add or correct the following line to configure the system to boot into enforcing mode:
    SELINUX=enforcing
Ensure that all files have correct SELinux labels by running:
    fixfiles onboot
Then reboot the system.
Rationale: | Setting the SELinux state to enforcing ensures SELinux is able to confine potentially compromised processes to the security policy, which is designed to prevent them from causing damage to the system or further elevating their privileges.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_selinux_state
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9
cobit5 | APO01.06, APO11.04, APO13.01, BAI03.05, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, MEA02.01
cui | 3.1.2, 3.7.2
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-2009 | 4.2.3.4, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.4, 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.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, 4.4.3.3
isa-62443-2013 | SR 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.10, SR 2.11, SR 2.12, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 2.8, SR 2.9, 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-2013 | A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, 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.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cip | CIP-003-8 R5.1.1, CIP-003-8 R5.2, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-004-6 R3.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nist | AC-3, AC-3(3)(a), AU-9, SC-7(21)
nist-csf | DE.AE-1, ID.AM-3, PR.AC-4, PR.AC-5, PR.AC-6, PR.DS-5, PR.PT-1, PR.PT-3, PR.PT-4
ospp | FMT_MOF_EXT.1
os-srg | SRG-OS-000445-GPOS-00199, SRG-OS-000134-GPOS-00068
anssi | R37, R79
bsi | APP.4.4.A4, SYS.1.6.A3, SYS.1.6.A18, SYS.1.6.A21
ccn | A.6.SEC-OL1
ism | 1409
pcidss4 | 1.2.6, 1.2
stigid | OL09-00-000060
stigref | SV-271452r1091068_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_selinux_state='enforcing'

    if [ -e "/etc/selinux/config" ] ; then

        LC_ALL=C sed -i "/^SELINUX=/Id" "/etc/selinux/config"
    else
        touch "/etc/selinux/config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/selinux/config"

    cp "/etc/selinux/config" "/etc/selinux/config.bak"
    # Insert at the end of the file
    printf '%s\n' "SELINUX=$var_selinux_state" >> "/etc/selinux/config"
    # Clean up after ourselves.
    rm "/etc/selinux/config.bak"

    fixfiles onboot

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000060
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - selinux_state
    - name: XCCDF Value var_selinux_state # promote to variable
      set_fact:
        var_selinux_state: !!str enforcing
      tags:
        - always

    - name: Ensure SELinux State is Enforcing - Check current SELinux state
      ansible.builtin.command:
        cmd: getenforce
      register: current_selinux_state
      check_mode: false
      changed_when: false
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000060
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - selinux_state

    - name: Ensure SELinux State is Enforcing
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUX=
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/selinux/config
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUX=
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/selinux/config
        ansible.builtin.lineinfile:
          path: /etc/selinux/config
          create: true
          regexp: (?i)^SELINUX=
          line: SELINUX={{ var_selinux_state }}
          state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000060
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - selinux_state

    - name: Ensure SELinux State is Enforcing - Mark system to relabel SELinux on next
        boot
      ansible.builtin.file:
        path: /.autorelabel
        state: touch
        access_time: preserve
        modification_time: preserve
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - current_selinux_state.stdout | lower != var_selinux_state
      tags:
      - DISA-STIG-OL09-00-000060
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - NIST-800-53-AU-9
      - NIST-800-53-SC-7(21)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - selinux_state
Group Services Group contains 7 groups and 20 rules
[ref] The best protection against vulnerable software is running less software. This section describes how to review the software which Oracle Linux 9 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Oracle Linux 9 system and provides guidance about which ones can be safely disabled. Oracle Linux 9 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Oracle Linux 9 systems, it is highly recommended to select the minimal packages and then build up the system from there.
Group Base Services Group contains 1 rule
[ref] This section addresses the base services that are installed on a Oracle Linux 9 default installation which are not covered in other sections. Some of these services listen on the network and should be treated with particular discretion. Other services are local system utilities that may or may not be extraneous. In general, system services should be disabled if not required.
#### Rule Disable KDump Kernel Crash Analyzer (kdump) [ref]
The `kdump` service provides a kernel crash dump analyzer. It uses the `kexec` system call to boot a secondary kernel ("capture" kernel) following a system crash, which can load information from the crashed kernel for analysis. The `kdump` service can be disabled with the following command:
    $ sudo systemctl mask --now kdump.service
Rationale: | Kernel core dumps may contain the full contents of system memory at the time of the crash. Kernel core dumps consume a considerable amount of disk space and may result in denial of service by exhausting the available space on the target file system partition. Unless the system is used for kernel development or testing, there is little need to run the kdump service.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_kdump_disabled
References: |
cis-csc | 11, 12, 14, 15, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(3), 164.308(a)(4), 164.310(b), 164.310(c), 164.312(a), 164.312(e)
isa-62443-2009 | 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-2013 | SR 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-2013 | A.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
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
ospp | FMT_SMF_EXT.1.1
os-srg | SRG-OS-000269-GPOS-00103, SRG-OS-000480-GPOS-00227
stigid | OL09-00-002385
stigref | SV-271733r1092598_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    masked = ["kdump"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" stop 'kdump.service'
    fi
    "$SYSTEMCTL_EXEC" disable 'kdump.service'
    "$SYSTEMCTL_EXEC" mask 'kdump.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" -q list-unit-files kdump.socket; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop 'kdump.socket'
        fi
        "$SYSTEMCTL_EXEC" mask 'kdump.socket'
    fi
    # The service may not be running because it has been started and failed,
    # so let's reset the state so OVAL checks pass.
    # Service should be 'inactive', not 'failed' after reboot though.
    "$SYSTEMCTL_EXEC" reset-failed 'kdump.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
    kdump --disable
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002385
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_kdump_disabled

    - name: Disable KDump Kernel Crash Analyzer (kdump) - Disable service kdump
      block:

      - name: Disable KDump Kernel Crash Analyzer (kdump) - Collect systemd Services Present
          in the System
        ansible.builtin.command: systemctl -q list-unit-files --type service
        register: service_exists
        changed_when: false
        failed_when: service_exists.rc not in [0, 1]
        check_mode: false

      - name: Disable KDump Kernel Crash Analyzer (kdump) - Ensure kdump.service is Masked
        ansible.builtin.systemd:
          name: kdump.service
          state: stopped
          enabled: false
          masked: true
        when: service_exists.stdout_lines is search("kdump.service", multiline=True)

      - name: Unit Socket Exists - kdump.socket
        ansible.builtin.command: systemctl -q list-unit-files kdump.socket
        register: socket_file_exists
        changed_when: false
        failed_when: socket_file_exists.rc not in [0, 1]
        check_mode: false

      - name: Disable KDump Kernel Crash Analyzer (kdump) - Disable Socket kdump
        ansible.builtin.systemd:
          name: kdump.socket
          enabled: false
          state: stopped
          masked: true
        when: socket_file_exists.stdout_lines is search("kdump.socket", multiline=True)
      tags:
      - DISA-STIG-OL09-00-002385
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_kdump_disabled
      - special_service_block
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include disable_kdump

    class disable_kdump {
      service {'kdump':
        enable => false,
        ensure => 'stopped',
      }
    }
Group Application Whitelisting Daemon Group contains 2 rules
[ref] Fapolicyd (File Access Policy Daemon) implements application whitelisting to decide file access rights. Applications that are known via a reputation source are allowed access while unknown applications are not. The daemon makes use of the kernel's `fanotify` interface to determine file access rights.
#### Rule Install fapolicyd Package [ref]
The `fapolicyd` package can be installed with the following command:
    $ sudo yum install fapolicyd
Rationale: | `fapolicyd` (File Access Policy Daemon) implements application whitelisting to decide file access rights.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_fapolicyd_installed
References: |
nist | CM-6(a), SI-4(22)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000370-GPOS-00155, SRG-OS-000368-GPOS-00154, SRG-OS-000480-GPOS-00230
ism | 1409
stigid | OL09-00-000340
stigref | SV-271506r1091230_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "fapolicyd"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if ! rpm -q --quiet "fapolicyd" ; then
        yum install -y "fapolicyd"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=fapolicyd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000340
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(22)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_fapolicyd_installed

    - name: Ensure fapolicyd is installed
      ansible.builtin.package:
        name: fapolicyd
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000340
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(22)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_fapolicyd_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_fapolicyd

    class install_fapolicyd {
      package { 'fapolicyd':
        ensure => 'installed',
      }
    }
#### Rule Enable the File Access Policy Service [ref]
The File Access Policy service should be enabled. The `fapolicyd` service can be enabled with the following command:
    $ sudo systemctl enable fapolicyd.service
Rationale: | The `fapolicyd` service (File Access Policy Daemon) implements application whitelisting to decide file access rights.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_fapolicyd_enabled
References: |
nist | CM-6(a), SI-4(22)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000370-GPOS-00155, SRG-OS-000368-GPOS-00154, SRG-OS-000480-GPOS-00230
ism | 1409
stigid | OL09-00-000341
stigref | SV-271507r1091233_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["fapolicyd"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'fapolicyd.service'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" start 'fapolicyd.service'
    fi
    "$SYSTEMCTL_EXEC" enable 'fapolicyd.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000341
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(22)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_fapolicyd_enabled

    - name: Enable the File Access Policy Service - Enable service fapolicyd
      block:

      - name: Gather the package facts
        ansible.builtin.package_facts:
          manager: auto

      - name: Enable the File Access Policy Service - Enable Service fapolicyd
        ansible.builtin.systemd:
          name: fapolicyd
          enabled: true
          state: started
          masked: false
        when:
        - '"fapolicyd" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000341
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(22)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_fapolicyd_enabled
      - special_service_block
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_fapolicyd

    class enable_fapolicyd {
      service {'fapolicyd':
        enable => true,
        ensure => 'running',
      }
    }
Group Network Time Protocol Group contains 2 rules
[ref] The Network Time Protocol is used to manage the system clock over a network. Computer clocks are not very accurate, so time will drift unpredictably on unmanaged systems. Central time protocols can be used both to ensure that time is consistent among a network of systems, and that their time is consistent with the outside world. If every system on a network reliably reports the same time, then it is much easier to correlate log messages in case of an attack. In addition, a number of cryptographic protocols (such as Kerberos) use timestamps to prevent certain types of attacks. If your network does not have synchronized time, these protocols may be unreliable or even unusable. Depending on the specifics of the network, global time accuracy may be just as important as local synchronization, or not very important at all. If your network is connected to the Internet, using a public timeserver (or one provided by your enterprise) provides globally accurate timestamps which may be essential in investigating or responding to an attack which originated outside of your network. A typical network setup involves a small number of internal systems operating as NTP servers, and the remainder obtaining time information from those internal servers. There is a choice between the daemons `ntpd` and `chronyd`, which are available from the repositories in the `ntp` and `chrony` packages respectively. The default `chronyd` daemon can work well when external time references are only intermittently accessible, can perform well even when the network is congested for longer periods of time, can usually synchronize the clock faster and with better time accuracy, and quickly adapts to sudden changes in the rate of the clock, for example, due to changes in the temperature of the crystal oscillator. `Chronyd` should be considered for all systems which are frequently suspended or otherwise intermittently disconnected and reconnected to a network. Mobile and virtual systems for example. The `ntpd` NTP daemon fully supports NTP protocol version 4 (RFC 5905), including broadcast, multicast, manycast clients and servers, and the orphan mode. It also supports extra authentication schemes based on public-key cryptography (RFC 5906). The NTP daemon (`ntpd`) should be considered for systems which are normally kept permanently on. Systems which are required to use broadcast or multicast IP, or to perform authentication of packets with the `Autokey` protocol, should consider using `ntpd`. Refer to https://docs.oracle.com/en/operating-systems/oracle-linux/9/network/network-ConfiguringNetworkTime.html#ol-nettime for more detailed comparison of features of `chronyd` and `ntpd` daemon features respectively, and for further guidance how to choose between the two NTP daemons. The upstream manual pages at https://chrony-project.org/documentation.html for `chronyd` and http://www.ntp.org for `ntpd` provide additional information on the capabilities and configuration of each of the NTP daemons.
#### Rule The Chrony package is installed [ref]
System time should be synchronized between all systems in an environment. This is typically done by establishing an authoritative time server or set of servers and having all systems synchronize their clocks to them. The `chrony` package can be installed with the following command:
    $ sudo yum install chrony
Rationale: | Time synchronization is important to support time sensitive security mechanisms like Kerberos and also ensures log files have consistent time records across the enterprise, which aids in forensic investigations.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_chrony_installed
References: |
ospp | FMT_SMF_EXT.1
pcidss | Req-10.4
os-srg | SRG-OS-000355-GPOS-00143
anssi | R71
ccn | A.3.SEC-OL3
ism | 0988, 1405
pcidss4 | 10.6.1, 10.6
stigid | OL09-00-000310
stigref | SV-271501r1091215_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "chrony"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if ! rpm -q --quiet "chrony" ; then
        yum install -y "chrony"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=chrony
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000310
      - PCI-DSS-Req-10.4
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_chrony_installed

    - name: Ensure chrony is installed
      ansible.builtin.package:
        name: chrony
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000310
      - PCI-DSS-Req-10.4
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_chrony_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_chrony

    class install_chrony {
      package { 'chrony':
        ensure => 'installed',
      }
    }
#### Rule Disable chrony daemon from acting as server [ref]
The `port` option in `/etc/chrony.conf` can be set to `0` to make chrony daemon to never open any listening port for server operation and to operate strictly in a client-only mode.
Rationale: | In order to prevent unauthorized connection of devices, unauthorized transfer of information, or unauthorized tunneling (i.e., embedding of data types within data types), organizations must disable or restrict unused or unnecessary physical and logical ports/protocols on information systems. Operating systems are capable of providing a wide variety of functions and services. Some of the functions and services provided by default may not be necessary to support essential organizational operations. Additionally, it is sometimes convenient to provide multiple services from a single component (e.g., VPN and IPS); however, doing so increases risk over limiting the services provided by any one component. To support the requirements and principles of least functionality, the operating system must support the organizational requirements, providing only essential capabilities and limiting the use of ports, protocols, and/or services to only those required, authorized, and approved to conduct official business or to address authorized quality of life issues.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_chronyd_client_only
References: |
nist | AU-8(1), AU-12(1)
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000096-GPOS-00050, SRG-OS-000095-GPOS-00049
stigid | OL09-00-002320
stigref | SV-271697r1091803_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q chrony; }; 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' <<< "^port")

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

    # 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 "^port\\>" "/etc/chrony.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^port\\>.*/$escaped_formatted_output/gi" "/etc/chrony.conf"
    else
        if [[ -s "/etc/chrony.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/chrony.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/chrony.conf"
        fi
        printf '%s\n' "$formatted_output" >> "/etc/chrony.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002320
      - NIST-800-53-AU-12(1)
      - NIST-800-53-AU-8(1)
      - chronyd_client_only
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable chrony daemon from acting as server
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/chrony.conf
          create: true
          regexp: (?i)^\s*port\s+
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/chrony.conf
        ansible.builtin.lineinfile:
          path: /etc/chrony.conf
          create: true
          regexp: (?i)^\s*port\s+
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/chrony.conf
        ansible.builtin.lineinfile:
          path: /etc/chrony.conf
          create: true
          regexp: (?i)^\s*port\s+
          line: port 0
          state: present
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"chrony" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002320
      - NIST-800-53-AU-12(1)
      - NIST-800-53-AU-8(1)
      - chronyd_client_only
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy
Group SSH Server Group contains 2 groups and 11 rules
[ref] The SSH protocol is recommended for remote login and remote file transfer. SSH provides confidentiality and integrity for data exchanged between two systems, as well as server authentication, through the use of public key cryptography. The implementation included with the system is called OpenSSH, and more detailed documentation is available from its website, https://www.openssh.com. Its server program is called `sshd` and provided by the RPM package `openssh-server`.
Group Configure OpenSSH Client if Necessary Group contains 1 rule
[ref] The following configuration changes apply to the SSH client. They can improve security parameters relwevant to the client user, e.g. increasing entropy while generating initialization vectors. Note that these changes influence only the default SSH client configuration. Changes in this group can be overridden by the client user by modifying files within the
    ~/.ssh
directory or by supplying parameters on the command line.
#### Rule Configure session renegotiation for SSH client [ref]
The `RekeyLimit` parameter specifies how often the session key is renegotiated, both in terms of amount of data that may be transmitted and the time elapsed. To decrease the default limits, put line `RekeyLimit 1G 1h` to file `/etc/ssh/ssh_config.d/02-rekey-limit.conf`. Make sure that there is no other `RekeyLimit` configuration preceding the `include` directive in the main config file `/etc/ssh/ssh_config`. Check also other files in `/etc/ssh/ssh_config.d` directory. Files are processed according to lexicographical order of file names. Make sure that there is no file processed before `02-rekey-limit.conf` containing definition of `RekeyLimit`.
Rationale: | By decreasing the limit based on the amount of data and enabling time-based limit, effects of potential attacks against encryption keys are limited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_ssh_client_rekey_limit
References: |
ospp | FCS_SSH_EXT.1.8
os-srg | SRG-OS-000423-GPOS-00187, SRG-OS-000033-GPOS-00014, SRG-OS-000424-GPOS-00188
Remediation Shell script ⇲
    var_ssh_client_rekey_limit_size='1G'
    var_ssh_client_rekey_limit_time='1h'

    main_config="/etc/ssh/ssh_config"
    include_directory="/etc/ssh/ssh_config.d"

    if grep -q '^[\s]*RekeyLimit.*$' "$main_config"; then
      sed -i '/^[\s]*RekeyLimit.*/d' "$main_config"
    fi

    for file in "$include_directory"/*.conf; do
      if grep -q '^[\s]*RekeyLimit.*$' "$file"; then
        sed -i '/^[\s]*RekeyLimit.*/d' "$file"
      fi
    done

    if [ -e "/etc/ssh/ssh_config.d/02-rekey-limit.conf" ] ; then

        LC_ALL=C sed -i "/^\s*RekeyLimit\s\+/d" "/etc/ssh/ssh_config.d/02-rekey-limit.conf"
    else
        touch "/etc/ssh/ssh_config.d/02-rekey-limit.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/ssh_config.d/02-rekey-limit.conf"

    cp "/etc/ssh/ssh_config.d/02-rekey-limit.conf" "/etc/ssh/ssh_config.d/02-rekey-limit.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "RekeyLimit $var_ssh_client_rekey_limit_size $var_ssh_client_rekey_limit_time" >> "/etc/ssh/ssh_config.d/02-rekey-limit.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/ssh_config.d/02-rekey-limit.conf.bak"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: XCCDF Value var_ssh_client_rekey_limit_size # promote to variable
      set_fact:
        var_ssh_client_rekey_limit_size: !!str 1G
      tags:
        - always
    - name: XCCDF Value var_ssh_client_rekey_limit_time # promote to variable
      set_fact:
        var_ssh_client_rekey_limit_time: !!str 1h
      tags:
        - always

    - name: Ensure RekeyLimit is not configured in /etc/ssh/ssh_config
      ansible.builtin.lineinfile:
        path: /etc/ssh/ssh_config
        create: false
        regexp: ^\s*RekeyLimit.*$
        state: absent
      tags:
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - ssh_client_rekey_limit

    - name: Collect all include config files for ssh client which configure RekeyLimit
      ansible.builtin.find:
        paths: /etc/ssh/ssh_config.d/
        contains: ^[\s]*RekeyLimit.*$
        patterns: '*.config'
      register: ssh_config_include_files
      tags:
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - ssh_client_rekey_limit

    - name: Remove all occurrences of RekeyLimit configuration from include config files
        of ssh client
      ansible.builtin.lineinfile:
        path: '{{ item }}'
        regexp: ^[\s]*RekeyLimit.*$
        state: absent
      loop: '{{ ssh_config_include_files.files }}'
      tags:
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - ssh_client_rekey_limit

    - name: Ensure that rekey limit is set to {{ var_ssh_client_rekey_limit_size }} {{
        var_ssh_client_rekey_limit_time }} in /etc/ssh/ssh_config.d/02-rekey-limit.conf
      ansible.builtin.lineinfile:
        path: /etc/ssh/ssh_config.d/02-rekey-limit.conf
        create: true
        regexp: ^\s*RekeyLimit.*$
        line: RekeyLimit {{ var_ssh_client_rekey_limit_size }} {{ var_ssh_client_rekey_limit_time
          }}
        state: present
      tags:
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - ssh_client_rekey_limit
Group Configure OpenSSH Server if Necessary Group contains 8 rules
[ref] If the system needs to act as an SSH server, then certain changes should be made to the OpenSSH daemon configuration file `/etc/ssh/sshd_config`. The following recommendations can be applied to this file. See the `sshd_config(5)` man page for more detailed information.
#### Rule Disable Host-Based Authentication [ref]
SSH's cryptographic host-based authentication is more secure than `.rhosts` authentication. However, it is not recommended that hosts unilaterally trust one another, even within an organization. The default SSH configuration disables host-based authentication. The appropriate configuration is used if no value is set for `HostbasedAuthentication`. To explicitly disable host-based authentication, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    HostbasedAuthentication no
Rationale: | SSH trust relationships mean a compromise on one host can allow an attacker to move trivially to other hosts.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_disable_host_auth
References: |
cis-csc | 11, 12, 14, 15, 16, 18, 3, 5, 9
cjis | 5.5.6
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui | 3.1.12
hipaa | 164.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-2009 | 4.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-2013 | SR 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-2013 | A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.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
nerc-cip | CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nist | AC-3, AC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-4, PR.AC-6, PR.IP-1, PR.PT-3
ospp | FIA_UAU.1
os-srg | SRG-OS-000480-GPOS-00229
ism | 0421, 0422, 0484, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.1, 8.3
stigid | OL09-00-002357
stigref | SV-271719r1091869_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*HostbasedAuthentication/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

    LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then

        LC_ALL=C sed -i "/^\s*HostbasedAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    else
        touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

    cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "HostbasedAuthentication no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*HostbasedAuthentication.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Host-Based Authentication - Check if the parameter HostbasedAuthentication
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Host-Based Authentication - Check if the parameter HostbasedAuthentication
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Host-Based Authentication
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter HostbasedAuthentication is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "HostbasedAuthentication"| regex_escape }}\s+
          line: HostbasedAuthentication no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Host-Based Authentication - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002357
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - disable_host_auth
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Disable SSH Access via Empty Passwords [ref]
Disallow SSH login with empty passwords. The default SSH configuration disables logins with empty passwords. The appropriate configuration is used if no value is set for `PermitEmptyPasswords`. To explicitly disallow SSH login from accounts with empty passwords, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    PermitEmptyPasswords no
Any accounts with empty passwords should be disabled immediately, and PAM configuration should prevent users from being able to assign themselves empty passwords.
Rationale: | Configuring this setting for the SSH daemon provides additional assurance that remote login via SSH will require a password, even in the event of misconfiguration elsewhere.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_empty_passwords
References: |
cis-csc | 11, 12, 13, 14, 15, 16, 18, 3, 5, 9
cjis | 5.5.6
cobit5 | APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06
cui | 3.1.1, 3.1.5
hipaa | 164.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-2009 | 4.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-2013 | SR 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-2013 | A.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
nist | AC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3
ospp | FIA_UAU.1
pcidss | Req-2.2.4
os-srg | SRG-OS-000106-GPOS-00053, SRG-OS-000480-GPOS-00229, SRG-OS-000480-GPOS-00227
ism | 1546
pcidss4 | 2.2.6, 2.2
stigid | OL09-00-002343
stigref | SV-271706r1091830_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*PermitEmptyPasswords/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

    LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then

        LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    else
        touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

    cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "PermitEmptyPasswords no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*PermitEmptyPasswords.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Disable SSH Access via Empty Passwords - Check if the parameter PermitEmptyPasswords
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Disable SSH Access via Empty Passwords - Check if the parameter PermitEmptyPasswords
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Disable SSH Access via Empty Passwords
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter PermitEmptyPasswords is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "PermitEmptyPasswords"| regex_escape }}\s+
          line: PermitEmptyPasswords no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords

    - name: Disable SSH Access via Empty Passwords - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002343
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_empty_passwords
#### Rule Disable GSSAPI Authentication [ref]
Unless needed, SSH should not permit extraneous or unnecessary authentication mechanisms like GSSAPI. The default SSH configuration disallows authentications based on GSSAPI. The appropriate configuration is used if no value is set for `GSSAPIAuthentication`. To explicitly disable GSSAPI authentication, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    GSSAPIAuthentication no
Rationale: | GSSAPI authentication is used to provide additional authentication mechanisms to applications. Allowing GSSAPI authentication through SSH exposes the system's GSSAPI to remote hosts, increasing the attack surface of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_gssapi_auth
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui | 3.1.12
hipaa | 164.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-2009 | 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013 | SR 7.6
iso27001-2013 | A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nist | CM-7(a), CM-7(b), CM-6(a), AC-17(a)
nist-csf | PR.IP-1
ospp | FTP_ITC_EXT.1, FCS_SSH_EXT.1.2
os-srg | SRG-OS-000364-GPOS-00151, SRG-OS-000480-GPOS-00227
ism | 0418, 1055, 1402
stigid | OL09-00-002341
stigref | SV-271704r1091824_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*GSSAPIAuthentication/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

    LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then

        LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    else
        touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

    cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "GSSAPIAuthentication no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*GSSAPIAuthentication.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Disable GSSAPI Authentication - Check if the parameter GSSAPIAuthentication
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Disable GSSAPI Authentication - Check if the parameter GSSAPIAuthentication
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Disable GSSAPI Authentication
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter GSSAPIAuthentication is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "GSSAPIAuthentication"| regex_escape }}\s+
          line: GSSAPIAuthentication no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth

    - name: Disable GSSAPI Authentication - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002341
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_gssapi_auth
#### Rule Disable Kerberos Authentication [ref]
Unless needed, SSH should not permit extraneous or unnecessary authentication mechanisms like Kerberos. The default SSH configuration disallows authentication validation through Kerberos. The appropriate configuration is used if no value is set for `KerberosAuthentication`. To explicitly disable Kerberos authentication, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    KerberosAuthentication no
Rationale: | Kerberos authentication for SSH is often implemented using GSSAPI. If Kerberos is enabled through SSH, the SSH daemon provides a means of access to the system's Kerberos implementation. Configuring these settings for the SSH daemon provides additional assurance that remote logon via SSH will not use unused methods of authentication, even in the event of misconfiguration elsewhere.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_kerb_auth
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
cui | 3.1.12
hipaa | 164.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-2009 | 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013 | SR 7.6
iso27001-2013 | A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nist | AC-17(a), CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.IP-1
ospp | FTP_ITC_EXT.1, FCS_SSH_EXT.1.2
os-srg | SRG-OS-000364-GPOS-00151, SRG-OS-000480-GPOS-00227
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
stigid | OL09-00-002356
stigref | SV-271718r1091866_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*KerberosAuthentication/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
    chmod 0600 /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf

    LC_ALL=C sed -i "/^\s*KerberosAuthentication\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*KerberosAuthentication\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" ] ; then

        LC_ALL=C sed -i "/^\s*KerberosAuthentication\s\+/Id" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    else
        touch "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"

    cp "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf" "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "KerberosAuthentication no" > "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    cat "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak" >> "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*KerberosAuthentication.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Disable Kerberos Authentication - Check if the parameter KerberosAuthentication
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Disable Kerberos Authentication - Check if the parameter KerberosAuthentication
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Disable Kerberos Authentication
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter KerberosAuthentication is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "KerberosAuthentication"| regex_escape }}\s+
          line: KerberosAuthentication no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth

    - name: Disable Kerberos Authentication - set file mode for /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/01-complianceascode-reinforce-os-defaults.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002356
      - NIST-800-171-3.1.12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_kerb_auth
#### Rule Disable SSH Root Login [ref]
The root user should never be allowed to login to a system directly over a network. To disable root login via SSH, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    PermitRootLogin no
Rationale: | Even though the communications channel may be encrypted, an additional layer of security is gained by extending the policy of not logging directly on as root. In addition, logging in with a user-specific account provides individual accountability of actions performed on the system and also helps to minimize direct attack attempts on root's password.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_root_login
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.6
cobit5 | APO01.06, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.06, DSS06.10
cui | 3.1.1, 3.1.5
hipaa | 164.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-2009 | 4.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-2013 | SR 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-2013 | A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cip | CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.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, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nist | AC-6(2), AC-17(a), IA-2, IA-2(5), CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, PR.PT-3
ospp | FAU_GEN.1
pcidss | Req-2.2.4
os-srg | SRG-OS-000109-GPOS-00056, SRG-OS-000480-GPOS-00227
app-srg-ctr | SRG-APP-000148-CTR-000335, SRG-APP-000190-CTR-000500
anssi | R33
ism | 1546
pcidss4 | 2.2.6, 2.2
stigid | OL09-00-002345
stigref | SV-271708r1092594_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*PermitRootLogin/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

    LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then

        LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    else
        touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

    cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "PermitRootLogin no" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*PermitRootLogin.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
        correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Disable SSH Root Login
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter PermitRootLogin is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
          line: PermitRootLogin no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login

    - name: Disable SSH Root Login - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-002345
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(2)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-2(5)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_root_login
#### Rule Enable SSH Warning Banner [ref]
To enable the warning banner and ensure it is consistent across the system, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    Banner /etc/issue
Another section contains information on how to create an appropriate system-wide warning banner.
Rationale: | The warning message reinforces policy awareness during the logon process and facilitates possible legal action against attackers. Alternatively, systems whose ownership should not be obvious should ensure usage of a banner that does not provide easy attribution.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_enable_warning_banner
References: |
cis-csc | 1, 12, 15, 16
cjis | 5.5.6
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.9
hipaa | 164.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-2009 | 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013 | SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013 | A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nist | AC-8(a), AC-8(c), AC-17(a), CM-6(a)
nist-csf | PR.AC-7
ospp | FTA_TAB.1
pcidss | Req-2.2.4
os-srg | SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
ism | 0484
stigid | OL09-00-000256
stigref | SV-271487r1091173_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*Banner/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

    LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then

        LC_ALL=C sed -i "/^\s*Banner\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    else
        touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

    cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "Banner /etc/issue" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*Banner.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Enable SSH Warning Banner - Check if the parameter Banner is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "Banner"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Enable SSH Warning Banner - Check if the parameter Banner is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "Banner"| regex_escape }}\s+/etc/issue$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Enable SSH Warning Banner
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter Banner is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "Banner"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "Banner"| regex_escape }}\s+
          line: Banner /etc/issue
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner

    - name: Enable SSH Warning Banner - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - CJIS-5.5.6
      - DISA-STIG-OL09-00-000256
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner
#### Rule Force frequent session key renegotiation [ref]
The `RekeyLimit` parameter specifies how often the session key of the is renegotiated, both in terms of amount of data that may be transmitted and the time elapsed. To decrease the default limits, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    RekeyLimit 1G
             1h
Rationale: | By decreasing the limit based on the amount of data and enabling time-based limit, effects of potential attacks against encryption keys are limited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_rekey_limit
References: |
ospp | FCS_SSH_EXT.1.8
os-srg | SRG-OS-000480-GPOS-00227, SRG-OS-000033-GPOS-00014
stigid | OL09-00-002342
stigref | SV-271705r1091827_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_rekey_limit_size='1G'
    var_rekey_limit_time='1h'

    # Find the include keyword, extract from the line the glob expression representing included files.
    # And if it is a relative path prepend '/etc/ssh/'
    included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*include\s*//I' | sed -e 's|^[^/]|/etc/ssh/&|')
    for included_file in ${included_files} ; do

        LC_ALL=C sed -i "/^\s*RekeyLimit/Id" "$included_file"
    done

    mkdir -p /etc/ssh/sshd_config.d
    touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf

    LC_ALL=C sed -i "/^\s*RekeyLimit\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*RekeyLimit\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
    if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then

        LC_ALL=C sed -i "/^\s*RekeyLimit\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    else
        touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

    cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "RekeyLimit $var_rekey_limit_size $var_rekey_limit_time" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit
    - name: XCCDF Value var_rekey_limit_size # promote to variable
      set_fact:
        var_rekey_limit_size: !!str 1G
      tags:
        - always
    - name: XCCDF Value var_rekey_limit_time # promote to variable
      set_fact:
        var_rekey_limit_time: !!str 1h
      tags:
        - always

    - name: Find sshd_config included files
      ansible.builtin.shell: |-
        included_files=$(grep -oP "^\s*(?i)include.*" /etc/ssh/sshd_config | sed -e 's/\s*Include\s*//i' | sed -e 's|^[^/]|/etc/ssh/&|')
        [[ -n $included_files ]] && ls $included_files || true
      register: sshd_config_included_files
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit

    - name: Comment conf from included files
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^(\s*RekeyLimit.*)$
        replace: '# \1'
      loop: '{{ sshd_config_included_files.stdout_lines }}'
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit

    - name: Force frequent session key renegotiation - Check if the parameter RekeyLimit
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit

    - name: Force frequent session key renegotiation - Check if the parameter RekeyLimit
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+{{ var_rekey_limit_size }}
          {{ var_rekey_limit_time }}$
      register: _sshd_config_correctly
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit

    - name: Force frequent session key renegotiation
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+
          state: absent

      - name: Check if /etc/ssh/sshd_config.d exists
        ansible.builtin.stat:
          path: /etc/ssh/sshd_config.d
        register: _etc_ssh_sshd_config_d_exists

      - name: Check if the parameter RekeyLimit is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+
        register: _etc_ssh_sshd_config_d_has_parameter
        when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

      - name: Remove parameter from files in /etc/ssh/sshd_config.d
        ansible.builtin.lineinfile:
          path: '{{ item.path }}'
          create: false
          regexp: (?i)(?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+
          state: absent
        with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files }}'
        when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

      - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
          create: true
          regexp: (?i)(?i)^\s*{{ "RekeyLimit"| regex_escape }}\s+
          line: RekeyLimit {{ var_rekey_limit_size }} {{ var_rekey_limit_time }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit

    - name: Force frequent session key renegotiation - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        mode: '0600'
        state: touch
        modification_time: preserve
        access_time: preserve
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-002342
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - sshd_rekey_limit
#### Rule Distribute the SSH Server configuration to multiple files in a config directory. [ref]
Make sure to have the `Include /etc/ssh/sshd_config.d/*.conf` line in the `/etc/ssh/sshd_config` file. Ideally, don't have any active configuration directives in that file, and distribute the service configuration to several files in the `/etc/ssh/sshd_config.d` directory.
Rationale: | This form of distributed configuration is considered as a good practice, and as other sshd rules assume that directives in files in the `/etc/ssh/sshd_config.d` config directory are effective, there has to be a rule that ensures this. Aside from that, having multiple configuration files makes the SSH Server configuration changes easier to partition according to the reason that they were introduced, and therefore it should help to perform merges of hardening updates.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_use_directory_configuration
References: |
hipaa | 164.312(a)
ospp | FCS_SSH_EXT.1
ism | 1409
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if test -f /etc/ssh/sshd_config.d/sshd_config_original.conf; then
    	printf '%s\n' "Remediation probably already happened, '/etc/ssh/sshd_config.d/sshd_config_original.conf' already exists, not doing anything." >&2
    false 1
    elif grep -Eq '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config && ! grep -Eq '^\s*Match\s' /etc/ssh/sshd_config; then
    	printf '%s\n' "Remediation probably already happened, '/etc/ssh/sshd_config' already contains the include directive." >&2
    false 1
    else
    	mkdir -p /etc/ssh/sshd_config.d
    	mv /etc/ssh/sshd_config /etc/ssh/sshd_config.d/sshd_config_original.conf
    cat > /etc/ssh/sshd_config << EOF
    # To modify the system-wide sshd configuration, create a  *.conf  file under
    #  /etc/ssh/sshd_config.d/  which will be automatically included below

    Include /etc/ssh/sshd_config.d/*.conf
    EOF
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
#### Rule Install OpenSSH client software [ref]
The `openssh-clients` package can be installed with the following command:
    $ sudo yum install openssh-clients
Rationale: | This package includes utilities to make encrypted connections and transfer files securely to SSH servers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_openssh-clients_installed
References: |
ospp | FIA_UAU.5, FTP_ITC_EXT.1, FCS_SSH_EXT.1, FCS_SSHC_EXT.1
os-srg | SRG-OS-000480-GPOS-00227
stigid | OL09-00-000260
stigref | SV-271488r1091176_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "openssh-clients"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    if ! rpm -q --quiet "openssh-clients" ; then
        yum install -y "openssh-clients"
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=openssh-clients
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Ensure openssh-clients is installed
      ansible.builtin.package:
        name: openssh-clients
        state: present
      tags:
      - DISA-STIG-OL09-00-000260
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_openssh-clients_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_openssh-clients

    class install_openssh-clients {
      package { 'openssh-clients':
        ensure => 'installed',
      }
    }
#### Rule Install the OpenSSH Server Package [ref]
The `openssh-server` package should be installed. The `openssh-server` package can be installed with the following command:
    $ sudo yum install openssh-server
Rationale: | Without protection of the transmitted information, confidentiality, and integrity may be compromised because unprotected communications can be intercepted and either read or altered.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_openssh-server_installed
References: |
cis-csc | 13, 14
cobit5 | APO01.06, DSS05.02, DSS05.04, DSS05.07, DSS06.02, DSS06.06
isa-62443-2013 | SR 3.1, SR 3.8, SR 4.1, SR 4.2, SR 5.2
iso27001-2013 | A.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
nist | CM-6(a)
nist-csf | PR.DS-2, PR.DS-5
ospp | FIA_UAU.5, FTP_ITC_EXT.1, FCS_SSH_EXT.1, FCS_SSHS_EXT.1
os-srg | SRG-OS-000423-GPOS-00187, SRG-OS-000424-GPOS-00188, SRG-OS-000425-GPOS-00189, SRG-OS-000426-GPOS-00190
stigid | OL09-00-000250
stigref | SV-271482r1091158_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "openssh-server"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if ! rpm -q --quiet "openssh-server" ; then
        yum install -y "openssh-server"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=openssh-server
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000250
      - NIST-800-53-CM-6(a)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_openssh-server_installed

    - name: Ensure openssh-server is installed
      ansible.builtin.package:
        name: openssh-server
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000250
      - NIST-800-53-CM-6(a)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_openssh-server_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_openssh-server

    class install_openssh-server {
      package { 'openssh-server':
        ensure => 'installed',
      }
    }
Group USBGuard daemon Group contains 4 rules
[ref] The USBGuard daemon enforces the USB device authorization policy for all USB devices.
#### Rule Install usbguard Package [ref]
The `usbguard` package can be installed with the following command:
    $ sudo yum install usbguard
Rationale: | `usbguard` is a software framework that helps to protect against rogue USB devices by implementing basic whitelisting/blacklisting capabilities based on USB device attributes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_usbguard_installed
References: |
nist | CM-8(3), IA-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000378-GPOS-00163
app-srg-ctr | SRG-APP-000141-CTR-000315
ccn | A.23.SEC-OL1
ism | 1418
stigid | OL09-00-000320
stigref | SV-271503r1091221_rule
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
      extensions:
        - usbguard
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "usbguard"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if ( ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    if ! rpm -q --quiet "usbguard" ; then
        yum install -y "usbguard"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=usbguard
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000320
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_usbguard_installed

    - name: Ensure usbguard is installed
      ansible.builtin.package:
        name: usbguard
        state: present
      when: ( ansible_architecture != "s390x" and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - DISA-STIG-OL09-00-000320
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_usbguard_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_usbguard

    class install_usbguard {
      package { 'usbguard':
        ensure => 'installed',
      }
    }
#### Rule Enable the USBGuard Service [ref]
The USBGuard service should be enabled. The `usbguard` service can be enabled with the following command:
    $ sudo systemctl enable usbguard.service
Rationale: | The `usbguard` service must be running in order to enforce the USB device authorization policy for all USB devices.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_usbguard_enabled
References: |
nist | CM-8(3)(a), IA-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000378-GPOS-00163
app-srg-ctr | SRG-APP-000141-CTR-000315
ccn | A.23.SEC-OL1
ism | 1418
stigid | OL09-00-000321
stigref | SV-271504r1091224_rule
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      annotations:
        complianceascode.io/depends-on: xccdf_org.ssgproject.content_rule_package_usbguard_installed
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: usbguard.service
            enabled: true
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["usbguard"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if ( ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'usbguard.service'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" start 'usbguard.service'
    fi
    "$SYSTEMCTL_EXEC" enable 'usbguard.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000321
      - NIST-800-53-CM-8(3)(a)
      - NIST-800-53-IA-3
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_usbguard_enabled

    - name: Enable the USBGuard Service - Enable service usbguard
      block:

      - name: Gather the package facts
        ansible.builtin.package_facts:
          manager: auto

      - name: Enable the USBGuard Service - Enable Service usbguard
        ansible.builtin.systemd:
          name: usbguard
          enabled: true
          state: started
          masked: false
        when:
        - '"usbguard" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000321
      - NIST-800-53-CM-8(3)(a)
      - NIST-800-53-IA-3
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_usbguard_enabled
      - special_service_block
      when: ( ansible_architecture != "s390x" and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_usbguard

    class enable_usbguard {
      service {'usbguard':
        enable => true,
        ensure => 'running',
      }
    }
#### Rule Log USBGuard daemon audit events using Linux Audit [ref]
To configure USBGuard daemon to log via Linux Audit (as opposed directly to a file), `AuditBackend` option in `/etc/usbguard/usbguard-daemon.conf` needs to be set to `LinuxAudit`.
Rationale: | Using the Linux Audit logging allows for centralized trace of events.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend
References: |
nist | AU-2, CM-8(3), IA-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000062-GPOS-00031, SRG-OS-000471-GPOS-00215
app-srg-ctr | SRG-APP-000141-CTR-000315
stigid | OL09-00-002330
stigref | SV-271700r1091812_rule
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      annotations:
        complianceascode.io/depends-on: xccdf_org.ssgproject.content_rule_package_usbguard_installed
        complianceascode.io/ocp-version: '>=4.7.0'
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,{{ %0A%23%0A%23%20Rule%20set%20file%20path.%0A%23%0A%23%20The%20USBGuard%20daemon%20will%20use%20this%20file%20to%20load%20the%20policy%0A%23%20rule%20set%20from%20it%20and%20to%20write%20new%20rules%20received%20via%20the%0A%23%20IPC%20interface.%0A%23%0A%23%20RuleFile%3D/path/to/rules.conf%0A%23%0ARuleFile%3D/etc/usbguard/rules.conf%0A%0A%23%0A%23%20Rule%20set%20folder%20path.%0A%23%0A%23%20The%20USBGuard%20daemon%20will%20use%20this%20folder%20to%20load%20the%20policy%0A%23%20rule%20set%20from%20it%20and%20to%20write%20new%20rules%20received%20via%20the%0A%23%20IPC%20interface.%20Usually%2C%20we%20set%20the%20option%20to%0A%23%20/etc/usbguard/rules.d/.%20The%20USBGuard%20daemon%20is%20supposed%20to%0A%23%20behave%20like%20any%20other%20standard%20Linux%20daemon%20therefore%20it%0A%23%20loads%20rule%20files%20in%20alpha-numeric%20order.%20File%20names%20inside%0A%23%20RuleFolder%20directory%20should%20start%20with%20a%20two-digit%20number%0A%23%20prefix%20indicating%20the%20position%2C%20in%20which%20the%20rules%20are%0A%23%20scanned%20by%20the%20daemon.%0A%23%0A%23%20RuleFolder%3D/path/to/rulesfolder/%0A%23%0ARuleFolder%3D/etc/usbguard/rules.d/%0A%0A%23%0A%23%20Implicit%20policy%20target.%0A%23%0A%23%20How%20to%20treat%20devices%20that%20don%27t%20match%20any%20rule%20in%20the%0A%23%20policy.%20One%20of%3A%0A%23%0A%23%20%2A%20allow%20%20-%20authorize%20the%20device%0A%23%20%2A%20block%20%20-%20block%20the%20device%0A%23%20%2A%20reject%20-%20remove%20the%20device%0A%23%0AImplicitPolicyTarget%3Dblock%0A%0A%23%0A%23%20Present%20device%20policy.%0A%23%0A%23%20How%20to%20treat%20devices%20that%20are%20already%20connected%20when%20the%0A%23%20daemon%20starts.%20One%20of%3A%0A%23%0A%23%20%2A%20allow%20%20%20%20%20%20%20%20-%20authorize%20every%20present%20device%0A%23%20%2A%20block%20%20%20%20%20%20%20%20-%20deauthorize%20every%20present%20device%0A%23%20%2A%20reject%20%20%20%20%20%20%20-%20remove%20every%20present%20device%0A%23%20%2A%20keep%20%20%20%20%20%20%20%20%20-%20just%20sync%20the%20internal%20state%20and%20leave%20it%0A%23%20%2A%20apply-policy%20-%20evaluate%20the%20ruleset%20for%20every%20present%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%0A%23%0APresentDevicePolicy%3Dapply-policy%0A%0A%23%0A%23%20Present%20controller%20policy.%0A%23%0A%23%20How%20to%20treat%20USB%20controllers%20that%20are%20already%20connected%0A%23%20when%20the%20daemon%20starts.%20One%20of%3A%0A%23%0A%23%20%2A%20allow%20%20%20%20%20%20%20%20-%20authorize%20every%20present%20device%0A%23%20%2A%20block%20%20%20%20%20%20%20%20-%20deauthorize%20every%20present%20device%0A%23%20%2A%20reject%20%20%20%20%20%20%20-%20remove%20every%20present%20device%0A%23%20%2A%20keep%20%20%20%20%20%20%20%20%20-%20just%20sync%20the%20internal%20state%20and%20leave%20it%0A%23%20%2A%20apply-policy%20-%20evaluate%20the%20ruleset%20for%20every%20present%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%0A%23%0APresentControllerPolicy%3Dkeep%0A%0A%23%0A%23%20Inserted%20device%20policy.%0A%23%0A%23%20How%20to%20treat%20USB%20devices%20that%20are%20already%20connected%0A%23%20%2Aafter%2A%20the%20daemon%20starts.%20One%20of%3A%0A%23%0A%23%20%2A%20block%20%20%20%20%20%20%20%20-%20deauthorize%20every%20present%20device%0A%23%20%2A%20reject%20%20%20%20%20%20%20-%20remove%20every%20present%20device%0A%23%20%2A%20apply-policy%20-%20evaluate%20the%20ruleset%20for%20every%20present%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%0A%23%0AInsertedDevicePolicy%3Dapply-policy%0A%0A%23%0A%23%20Control%20which%20devices%20are%20authorized%20by%20default.%0A%23%0A%23%20The%20USBGuard%20daemon%20modifies%20some%20the%20default%20authorization%20state%20attributes%0A%23%20of%20controller%20devices.%20This%20setting%2C%20enables%20you%20to%20define%20what%20value%20the%0A%23%20default%20authorization%20is%20set%20to.%0A%23%0A%23%20%2A%20keep%20%20%20%20%20%20%20%20%20-%20do%20not%20change%20the%20authorization%20state%0A%23%20%2A%20none%20%20%20%20%20%20%20%20%20-%20every%20new%20device%20starts%20out%20deauthorized%0A%23%20%2A%20all%20%20%20%20%20%20%20%20%20%20-%20every%20new%20device%20starts%20out%20authorized%0A%23%20%2A%20internal%20%20%20%20%20-%20internal%20devices%20start%20out%20authorized%2C%20external%20devices%20start%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20out%20deauthorized%20%28this%20requires%20the%20ACPI%20tables%20to%20properly%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%20internal%20devices%2C%20and%20kernel%20support%29%0A%23%0A%23AuthorizedDefault%3Dnone%0A%0A%23%0A%23%20Restore%20controller%20device%20state.%0A%23%0A%23%20The%20USBGuard%20daemon%20modifies%20some%20attributes%20of%20controller%0A%23%20devices%20like%20the%20default%20authorization%20state%20of%20new%20child%20device%0A%23%20instances.%20Using%20this%20setting%2C%20you%20can%20control%20whether%20the%0A%23%20daemon%20will%20try%20to%20restore%20the%20attribute%20values%20to%20the%20state%0A%23%20before%20modification%20on%20shutdown.%0A%23%0A%23%20SECURITY%20CONSIDERATIONS%3A%20If%20set%20to%20true%2C%20the%20USB%20authorization%0A%23%20policy%20could%20be%20bypassed%20by%20performing%20some%20sort%20of%20attack%20on%20the%0A%23%20daemon%20%28via%20a%20local%20exploit%20or%20via%20a%20USB%20device%29%20to%20make%20it%20shutdown%0A%23%20and%20restore%20to%20the%20operating-system%20default%20state%20%28known%20to%20be%20permissive%29.%0A%23%0ARestoreControllerDeviceState%3Dfalse%0A%0A%23%0A%23%20Device%20manager%20backend%0A%23%0A%23%20Which%20device%20manager%20backend%20implementation%20to%20use.%20One%20of%3A%0A%23%0A%23%20%2A%20uevent%20%20%20-%20Netlink%20based%20implementation%20which%20uses%20sysfs%20to%20scan%20for%20present%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20devices%20and%20an%20uevent%20netlink%20socket%20for%20receiving%20USB%20device%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20related%20events.%0A%23%20%2A%20umockdev%20-%20umockdev%20based%20device%20manager%20capable%20of%20simulating%20devices%20based%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20on%20umockdev-record%20files.%20Useful%20for%20testing.%0A%23%0ADeviceManagerBackend%3Duevent%0A%0A%23%21%21%21%20WARNING%3A%20It%27s%20good%20practice%20to%20set%20at%20least%20one%20of%20the%20%21%21%21%0A%23%21%21%21%20%20%20%20%20%20%20%20%20%20two%20options%20bellow.%20If%20none%20of%20them%20are%20set%2C%20%20%21%21%21%0A%23%21%21%21%20%20%20%20%20%20%20%20%20%20the%20daemon%20will%20accept%20IPC%20connections%20from%20%20%20%21%21%21%0A%23%21%21%21%20%20%20%20%20%20%20%20%20%20anyone%2C%20thus%20allowing%20anyone%20to%20modify%20the%20%20%20%20%21%21%21%0A%23%21%21%21%20%20%20%20%20%20%20%20%20%20rule%20set%20and%20%28de%29authorize%20USB%20devices.%20%20%20%20%20%20%20%21%21%21%0A%0A%23%0A%23%20Users%20allowed%20to%20use%20the%20IPC%20interface.%0A%23%0A%23%20A%20space%20delimited%20list%20of%20usernames%20that%20the%20daemon%20will%0A%23%20accept%20IPC%20connections%20from.%0A%23%0A%23%20IPCAllowedUsers%3Dusername1%20username2%20...%0A%23%0AIPCAllowedUsers%3Droot%0A%0A%23%0A%23%20Groups%20allowed%20to%20use%20the%20IPC%20interface.%0A%23%0A%23%20A%20space%20delimited%20list%20of%20groupnames%20that%20the%20daemon%20will%0A%23%20accept%20IPC%20connections%20from.%0A%23%0A%23%20IPCAllowedGroups%3Dgroupname1%20groupname2%20...%0A%23%0AIPCAllowedGroups%3Dwheel%0A%0A%23%0A%23%20IPC%20access%20control%20definition%20files%20path.%0A%23%0A%23%20The%20files%20at%20this%20location%20will%20be%20interpreted%20by%20the%20daemon%0A%23%20as%20access%20control%20definition%20files.%20The%20%28base%29name%20of%20a%20file%0A%23%20should%20be%20in%20the%20form%3A%0A%23%0A%23%20%20%20%5Buser%5D%5B%3A%3Cgroup%3E%5D%0A%23%0A%23%20and%20should%20contain%20lines%20in%20the%20form%3A%0A%23%0A%23%20%20%20%3Csection%3E%3D%5Bprivilege%5D%20...%0A%23%0A%23%20This%20way%20each%20file%20defines%20who%20is%20able%20to%20connect%20to%20the%20IPC%0A%23%20bus%20and%20what%20privileges%20he%20has.%0A%23%0AIPCAccessControlFiles%3D/etc/usbguard/IPCAccessControl.d/%0A%0A%23%0A%23%20Generate%20device%20specific%20rules%20including%20the%20%22via-port%22%0A%23%20attribute.%0A%23%0A%23%20This%20option%20modifies%20the%20behavior%20of%20the%20allowDevice%0A%23%20action.%20When%20instructed%20to%20generate%20a%20permanent%20rule%2C%0A%23%20the%20action%20can%20generate%20a%20port%20specific%20rule.%20Because%0A%23%20some%20systems%20have%20unstable%20port%20numbering%2C%20the%20generated%0A%23%20rule%20might%20not%20match%20the%20device%20after%20rebooting%20the%20system.%0A%23%0A%23%20If%20set%20to%20false%2C%20the%20generated%20rule%20will%20still%20contain%0A%23%20the%20%22parent-hash%22%20attribute%20which%20also%20defines%20an%20association%0A%23%20to%20the%20parent%20device.%20See%20usbguard-rules.conf%285%29%20for%20more%0A%23%20details.%0A%23%0ADeviceRulesWithPort%3Dfalse%0A%0A%23%0A%23%20USBGuard%20Audit%20events%20log%20backend%0A%23%0A%23%20One%20of%3A%0A%23%0A%23%20%2A%20FileAudit%20-%20Log%20audit%20events%20into%20a%20file%20specified%20by%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20AuditFilePath%20setting%20%28see%20below%29%0A%23%20%2A%20LinuxAudit%20-%20Log%20audit%20events%20using%20the%20Linux%20Audit%0A%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subsystem%20%28using%20audit_log_user_message%29%0A%23%0AAuditBackend%3DLinuxAudit%0A%0A%23%0A%23%20USBGuard%20audit%20events%20log%20file%20path.%0A%23%0A%23AuditFilePath%3D/var/log/usbguard/usbguard-audit.log%0A%0A%23%0A%23%20Hides%20personally%20identifiable%20information%20such%20as%20device%20serial%20numbers%20and%0A%23%20hashes%20of%20descriptors%20%28which%20include%20the%20serial%20number%29%20from%20audit%20entries.%0A%23%0A%23HidePII%3Dfalse }}
            mode: 0600
            path: /etc/usbguard/usbguard-daemon.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if ( ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ) && { rpm --quiet -q usbguard; }; then

    if [ -e "/etc/usbguard/usbguard-daemon.conf" ] ; then

        LC_ALL=C sed -i "/^[ \\t]*AuditBackend=/Id" "/etc/usbguard/usbguard-daemon.conf"
    else
        touch "/etc/usbguard/usbguard-daemon.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/usbguard/usbguard-daemon.conf"

    cp "/etc/usbguard/usbguard-daemon.conf" "/etc/usbguard/usbguard-daemon.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "AuditBackend=LinuxAudit" >> "/etc/usbguard/usbguard-daemon.conf"
    # Clean up after ourselves.
    rm "/etc/usbguard/usbguard-daemon.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-002330
      - NIST-800-53-AU-2
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - configure_strategy
      - configure_usbguard_auditbackend
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed

    - name: Log USBGuard daemon audit events using Linux Audit
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/usbguard/usbguard-daemon.conf
          create: true
          regexp: (?i)^[ \\t]*AuditBackend=
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/usbguard/usbguard-daemon.conf
        ansible.builtin.lineinfile:
          path: /etc/usbguard/usbguard-daemon.conf
          create: true
          regexp: (?i)^[ \\t]*AuditBackend=
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/usbguard/usbguard-daemon.conf
        ansible.builtin.lineinfile:
          path: /etc/usbguard/usbguard-daemon.conf
          create: true
          regexp: (?i)^[ \\t]*AuditBackend=
          line: AuditBackend=LinuxAudit
          state: present
      when:
      - ( ansible_architecture != "s390x" and ("kernel" in ansible_facts.packages or "kernel-uek"
        in ansible_facts.packages) )
      - '"usbguard" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-002330
      - NIST-800-53-AU-2
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - configure_strategy
      - configure_usbguard_auditbackend
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
#### Rule Authorize Human Interface Devices and USB hubs in USBGuard daemon [ref]
To allow authorization of USB devices combining human interface device and hub capabilities by USBGuard daemon, add the line `allow with-interface match-all { 03:*:* 09:00:* }` to `/etc/usbguard/rules.conf`. Warning: This rule should be understood primarily as a convenience administration feature. This rule ensures that if the USBGuard default rules.conf file is present, it will alter it so that USB human interface devices and hubs are allowed. However, if the rules.conf file is altered by system administrator, the rule does not check if USB human interface devices and hubs are allowed. This assumes that an administrator modified the file with some purpose in mind.
Rationale: | Without allowing Human Interface Devices, it might not be possible to interact with the system. Without allowing hubs, it might not be possible to use any USB devices on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_usbguard_allow_hid_and_hub
References: |
nist | CM-8(3), IA-3
ospp | FMT_SMF_EXT.1
os-srg | SRG-OS-000114-GPOS-00059
app-srg-ctr | SRG-APP-000092-CTR-000165
ism | 1418
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      annotations:
        complianceascode.io/depends-on: xccdf_org.ssgproject.content_rule_package_usbguard_installed
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,{{ %0Aallow%20with-interface%20match-all%20%7B%2003%3A%2A%3A%2A%2009%3A00%3A%2A%20%7D }}
            mode: 0600
            path: /etc/usbguard/rules.d/75-hid-and-hub.conf
            overwrite: true
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if ( ! ( grep -sqE "^.*\.s390x$" /proc/sys/kernel/osrelease || grep -sqE "^s390x$" /proc/sys/kernel/arch; ) && rpm --quiet -q kernel || rpm --quiet -q kernel-uek ); then

    echo "allow with-interface match-all { 03:*:* 09:00:* }" >> /etc/usbguard/rules.conf

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - usbguard_allow_hid_and_hub

    - name: Allow HID devices and hubs
      ansible.builtin.lineinfile:
        path: /etc/usbguard/rules.conf
        create: true
        regexp: ''
        line: allow with-interface match-all { 03:*:* 09:00:* }
        state: present
      when: ( ansible_architecture != "s390x" and ("kernel" in ansible_facts.packages
        or "kernel-uek" in ansible_facts.packages) )
      tags:
      - NIST-800-53-CM-8(3)
      - NIST-800-53-IA-3
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - usbguard_allow_hid_and_hub
Group System Accounting with auditd Group contains 2 groups and 24 rules
[ref] The audit service provides substantial capabilities for recording system activities. By default, the service audits about SELinux AVC denials and certain types of security-relevant events such as system logins, account modifications, and authentication events performed by programs such as sudo. Under its default configuration, `auditd` has modest disk space requirements, and should not noticeably impact system performance. NOTE: The Linux Audit daemon `auditd` can be configured to use the `augenrules` program to read audit rules files (`*.rules`) located in `/etc/audit/rules.d` location and compile them to create the resulting form of the `/etc/audit/audit.rules` configuration file during the daemon startup (default configuration). Alternatively, the `auditd` daemon can use the `auditctl` utility to read audit rules from the `/etc/audit/audit.rules` configuration file during daemon startup, and load them into the kernel. The expected behavior is configured via the appropriate `ExecStartPost` directive setting in the `/usr/lib/systemd/system/auditd.service` configuration file. To instruct the `auditd` daemon to use the `augenrules` program to read audit rules (default configuration), use the following setting:
    ExecStartPost=-/sbin/augenrules --load
in the `/usr/lib/systemd/system/auditd.service` configuration file. In order to instruct the `auditd` daemon to use the `auditctl` utility to read audit rules, use the following setting:
    ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the `/usr/lib/systemd/system/auditd.service` configuration file. Refer to `[Service]` section of the `/usr/lib/systemd/system/auditd.service` configuration file for further details. Government networks often have substantial auditing requirements and `auditd` can be configured to meet these requirements. Examining some example audit records demonstrates how the Linux audit system satisfies common requirements. The following example from Red Hat Enterprise Linux 7 Documentation available at `https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/selinux_users_and_administrators_guide/index#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages` shows the substantial amount of information captured in a two typical "raw" audit messages, followed by a breakdown of the most important fields. In this example the message is SELinux-related and reports an AVC denial (and the associated system call) that occurred when the Apache HTTP Server attempted to access the `/var/www/html/file1` file (labeled with the `samba_share_t` type):
    type=AVC msg=audit(1226874073.147:96): avc:  denied  { getattr } for pid=2465 comm="httpd"
    path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
    tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file

    type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
    a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
    gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
    exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)
`msg=audit(1226874073.147:96)` The number in parentheses is the unformatted time stamp (Epoch time) for the event, which can be converted to standard time by using the `date` command. `{ getattr }` The item in braces indicates the permission that was denied. `getattr` indicates the source process was trying to read the target file's status information. This occurs before reading files. This action is denied due to the file being accessed having the wrong label. Commonly seen permissions include `getattr`, `read`, and `write`. `comm="httpd"` The executable that launched the process. The full path of the executable is found in the `exe=` section of the system call (`SYSCALL`) message, which in this case, is `exe="/usr/sbin/httpd"`. `path="/var/www/html/file1"` The path to the object (target) the process attempted to access. `scontext="unconfined_u:system_r:httpd_t:s0"` The SELinux context of the process that attempted the denied action. In this case, it is the SELinux context of the Apache HTTP Server, which is running in the `httpd_t` domain. `tcontext="unconfined_u:object_r:samba_share_t:s0"` The SELinux context of the object (target) the process attempted to access. In this case, it is the SELinux context of `file1`. Note: the `samba_share_t` type is not accessible to processes running in the `httpd_t` domain. From the system call (`SYSCALL`) message, two items are of interest: `success=no`: indicates whether the denial (AVC) was enforced or not. `success=no` indicates the system call was not successful (SELinux denied access). `success=yes` indicates the system call was successful - this can be seen for permissive domains or unconfined domains, such as `initrc_t` and `kernel_t`. `exe="/usr/sbin/httpd"`: the full path to the executable that launched the process, which in this case, is `exe="/usr/sbin/httpd"`.
Group Configure auditd Data Retention Group contains 4 rules
[ref] The audit system writes data to `/var/log/audit/audit.log`. By default, `auditd` rotates 5 logs by size (6MB), retaining a maximum of 30MB of data in total, and refuses to write entries when the disk is too full. This minimizes the risk of audit data filling its partition and impacting other services. This also minimizes the risk of the audit daemon temporarily disabling the system if it cannot write audit log (which it can be configured to do). For a busy system or a system which is thoroughly auditing system activity, the default settings for data retention may be insufficient. The log file size needed will depend heavily on what types of events are being audited. First configure auditing to log all the events of interest. Then monitor the log size manually for awhile to determine what file size will allow you to keep the required data for the correct time period. Using a dedicated partition for `/var/log/audit` prevents the `auditd` logs from disrupting system functionality if they fill, and, more importantly, prevents other activity in `/var` from filling the partition and stopping the audit trail. (The audit logs are size-limited and therefore unlikely to grow without bound unless configured to do so.) Some machines may have requirements that no actions occur which cannot be audited. If this is the case, then `auditd` can be configured to halt the machine if it runs out of space. Note: Since older logs are rotated, configuring `auditd` this way does not prevent older logs from being rotated away before they can be viewed. If your system is configured to halt when logging cannot be performed, make sure this can never happen under normal circumstances! Ensure that `/var/log/audit` is on its own partition, and that this partition is larger than the maximum amount of data `auditd` will retain normally.
#### Rule Configure auditd flush priority [ref]
The `auditd` service can be configured to synchronously write audit event data to disk. Add or correct the following line in `/etc/audit/auditd.conf` to ensure that audit event data is fully synchronized with the log files on the disk:
    flush = incremental_async
Rationale: | Audit data should be synchronously written to disk to ensure log integrity. These parameters assure that all audit event data is fully synchronized with the log files on the disk.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_data_retention_flush
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9
cobit5 | APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui | 3.3.1
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-2009 | 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013 | SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2
iso27001-2013 | A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2
nerc-cip | CIP-004-6 R2.2.3, CIP-004-6 R3.3, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5
nist | AU-11, CM-6(a)
nist-csf | DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1
ospp | FAU_GEN.1
os-srg | SRG-OS-000480-GPOS-00227
ism | 0582
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit && rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_auditd_flush='incremental_async'

    AUDITCONFIG=/etc/audit/auditd.conf

    # if flush is present, flush param edited to var_auditd_flush
    # else flush param is defined by var_auditd_flush
    #
    # the freq param is only used for values 'incremental' and 'incremental_async' and will be
    # commented out if flush != incremental or flush != incremental_async
    #
    # if flush == incremental or flush == incremental_async && freq param is not defined, it
    # will be defined as the package-default value of 20

    grep -q ^flush $AUDITCONFIG && \
      sed -i 's/^flush.*/flush = '"$var_auditd_flush"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "flush = $var_auditd_flush" >> $AUDITCONFIG
    fi

    if ! [ "$var_auditd_flush" == "incremental" ] && ! [ "$var_auditd_flush" == "incremental_async" ]; then
      sed -i 's/^freq/##freq/g' $AUDITCONFIG
    elif [ "$var_auditd_flush" == "incremental" ] || [ "$var_auditd_flush" == "incremental_async" ]; then
      grep -q freq $AUDITCONFIG && \
        sed -i 's/^#\+freq/freq/g' $AUDITCONFIG
      if ! [ $? -eq 0 ]; then
        echo "freq = 20" >> $AUDITCONFIG
      fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-171-3.3.1
      - NIST-800-53-AU-11
      - NIST-800-53-CM-6(a)
      - auditd_data_retention_flush
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_auditd_flush # promote to variable
      set_fact:
        var_auditd_flush: !!str incremental_async
      tags:
        - always

    - name: Configure auditd Flush Priority
      ansible.builtin.lineinfile:
        dest: /etc/audit/auditd.conf
        regexp: ^\s*flush\s*=\s*.*$
        line: flush = {{ var_auditd_flush }}
        state: present
        create: true
      when:
      - '"audit" in ansible_facts.packages'
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-171-3.3.1
      - NIST-800-53-AU-11
      - NIST-800-53-CM-6(a)
      - auditd_data_retention_flush
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set number of records to cause an explicit flush to audit logs [ref]
To configure Audit daemon to issue an explicit flush to disk command after writing 50 records, set `freq` to `50` in `/etc/audit/auditd.conf`.
Rationale: | If option `freq` isn't set to `50`, the flush to disk may happen after higher number of records, increasing the danger of audit loss.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_freq
References: |
nist | CM-6
ospp | FAU_GEN.1
os-srg | SRG-OS-000051-GPOS-00024
ism | 0582
stigid | OL09-00-000775
stigref | SV-271582r1092574_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit && rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_auditd_freq='50'

    if [ -e "/etc/audit/auditd.conf" ] ; then

        LC_ALL=C sed -i "/^\s*freq\s*=\s*/Id" "/etc/audit/auditd.conf"
    else
        touch "/etc/audit/auditd.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/audit/auditd.conf"

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "freq = $var_auditd_freq" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000775
      - NIST-800-53-CM-6
      - auditd_freq
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_auditd_freq # promote to variable
      set_fact:
        var_auditd_freq: !!str 50
      tags:
        - always

    - name: Set number of records to cause an explicit flush to audit logs
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*freq\s*=\s*
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*freq\s*=\s*
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*freq\s*=\s*
          line: freq = {{ var_auditd_freq }}
          state: present
      when:
      - '"audit" in ansible_facts.packages'
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000775
      - NIST-800-53-CM-6
      - auditd_freq
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Resolve information before writing to audit logs [ref]
To configure Audit daemon to resolve all uid, gid, syscall, architecture, and socket address information before writing the events to disk, set `log_format` to `ENRICHED` in `/etc/audit/auditd.conf`.
Rationale: | If option `log_format` isn't set to `ENRICHED`, the audit records will be stored in a format exactly as the kernel sends them.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_log_format
References: |
nist | CM-6, AU-3
ospp | FAU_GEN.1.2
os-srg | SRG-OS-000255-GPOS-00096, SRG-OS-000480-GPOS-00227
app-srg-ctr | SRG-APP-000096-CTR-000175, SRG-APP-000097-CTR-000180, SRG-APP-000098-CTR-000185, SRG-APP-000099-CTR-000190, SRG-APP-000100-CTR-000195, SRG-APP-000100-CTR-000200, SRG-APP-000109-CTR-000215, SRG-APP-000290-CTR-000670, SRG-APP-000357-CTR-000800
ism | 0582
stigid | OL09-00-000835
stigref | SV-271593r1092580_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit && rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

        LC_ALL=C sed -i "/^\s*log_format\s*=\s*/Id" "/etc/audit/auditd.conf"
    else
        touch "/etc/audit/auditd.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/audit/auditd.conf"

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "log_format = ENRICHED" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000835
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6
      - auditd_log_format
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Resolve information before writing to audit logs
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*log_format\s*=\s*
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*log_format\s*=\s*
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*log_format\s*=\s*
          line: log_format = ENRICHED
          state: present
      when:
      - '"audit" in ansible_facts.packages'
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000835
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6
      - auditd_log_format
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set type of computer node name logging in audit logs [ref]
To configure Audit daemon to use a unique identifier as computer node name in the audit events, set `name_format` to `hostname` in `/etc/audit/auditd.conf`. Warning: Whenever the variable
    var_auditd_name_format
uses a multiple value option, for example
    A|B|C
, the first value will be used when remediating this rule.
Rationale: | If option `name_format` is left at its default value of `none`, audit events from different computers may be hard to distinguish.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_name_format
References: |
nist | CM-6, AU-3
ospp | FAU_GEN.1.2
os-srg | SRG-OS-000039-GPOS-00017, SRG-OS-000342-GPOS-00133, SRG-OS-000479-GPOS-00224
ism | 0582
pcidss4 | 10.2.2, 10.2
stigid | OL09-00-000755
stigref | SV-271578r1092572_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit && rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    var_auditd_name_format='hostname'

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

    if [ -e "/etc/audit/auditd.conf" ] ; then

        LC_ALL=C sed -i "/^\s*name_format\s*=\s*/Id" "/etc/audit/auditd.conf"
    else
        touch "/etc/audit/auditd.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/audit/auditd.conf"

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "name_format = $var_auditd_name_format" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000755
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.2
      - auditd_name_format
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_auditd_name_format # promote to variable
      set_fact:
        var_auditd_name_format: !!str hostname
      tags:
        - always

    - name: Set type of computer node name logging in audit logs - Define Value to Be
        Used in the Remediation
      ansible.builtin.set_fact: auditd_name_format_split="{{ var_auditd_name_format.split('|')[0]
        }}"
      when:
      - '"audit" in ansible_facts.packages'
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000755
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.2
      - auditd_name_format
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set type of computer node name logging in audit logs
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*name_format\s*=\s*
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*name_format\s*=\s*
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/audit/auditd.conf
        ansible.builtin.lineinfile:
          path: /etc/audit/auditd.conf
          create: true
          regexp: (?i)(?i)^\s*name_format\s*=\s*
          line: name_format = {{ auditd_name_format_split }}
          state: present
      when:
      - '"audit" in ansible_facts.packages'
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000755
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.2
      - auditd_name_format
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group System Accounting with auditd Group contains 16 rules
[ref] The `auditd` program can perform comprehensive monitoring of system activity. This section makes use of recommended configuration settings for specific policies or use cases. The rules in this section make use of rules defined in `/usr/share/doc/audit-VERSION/rules`.
#### Rule Configure auditing of unsuccessful file accesses [ref]
Ensure that unsuccessful attempts to access a file are audited. The following rules configure audit as described above:
    ## Unsuccessful file access (any other opens) This has to go last.
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful attempts to access a file might be signs of malicious activity happening within the system. Auditing of such activities helps in their monitoring and investigation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_access_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
app-srg-ctr | SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295
ism | 0582, 0846
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-3-access-failed.rules
    ## Unsuccessful file access (any other opens) This has to go last.
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-3-access-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-3-access-failed.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-3-access-failed.rules
        content: |
          ## Unsuccessful file access (any other opens) This has to go last.
          -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
          -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
          -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
          -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-access
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful file accesses - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-3-access-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful file accesses [ref]
Ensure that successful attempts to access a file are audited. The following rules configure audit as described above:
    ## Successful file access (any other opens) This has to go last.
    ## These next two are likely to result in a whole lot of events
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of successful attempts to access a file helps in investigation of activities performed on the system.
Severity: | info
Rule ID: | xccdf_org.ssgproject.content_rule_audit_access_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
ism | 0582, 0846
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-3-access-success.rules
    ## Successful file access (any other opens) This has to go last.
    ## These next two are likely to result in a whole lot of events
    -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
    -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-3-access-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-3-access-success.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-3-access-success.rules
        content: |
          ## Successful file access (any other opens) This has to go last.
          ## These next two are likely to result in a whole lot of events
          -a always,exit -F arch=b32 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
          -a always,exit -F arch=b64 -S open,openat,openat2,open_by_handle_at -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-access
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful file accesses - Remove any permissions from
        group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-3-access-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_access_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure basic parameters of Audit system [ref]
Perform basic configuration of Audit system. Make sure that any previously defined rules are cleared, the auditing system is configured to handle sudden bursts of events, and in cases of failure, messages are configured to be directed to system log. The following rules configure audit as described above:
    ## First rule - delete all
    -D

    ## Increase the buffers to survive stress events.
    ## Make this bigger for busy systems
    -b 8192

    ## This determine how long to wait in burst of events
    --backlog_wait_time 60000

    ## Set failure mode to syslog
    -f 1
Load new Audit rules into kernel by running:
    augenrules --load
Warning: It might happen that Audit buffer configured by this rule is not large enough for certain use cases. If that is the case, the buffer size can be overridden by placing
    -b larger_buffer_size
into a file within `/etc/audit/rules.d` directory, replacing `larger_file_size` with the desired value. The file name should start with a number higher than 10 and lower than 99.
Rationale: | Without basic configurations, audit may not perform as expected. It may not be able to correctly handle events under stressful conditions, or log events in case of failure.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_basic_configuration
References: |
nist | AU-2(a)
ospp | FAU_GEN.1
os-srg | SRG-OS-000365-GPOS-00152, SRG-OS-000475-GPOS-00220
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/10-base-config.rules
    ## First rule - delete all
    -D

    ## Increase the buffers to survive stress events.
    ## Make this bigger for busy systems
    -b 8192

    ## This determine how long to wait in burst of events
    --backlog_wait_time 60000

    ## Set failure mode to syslog
    -f 1

    EOF

    chmod o-rwx /etc/audit/rules.d/10-base-config.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_basic_configuration
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/10-base-config.rules according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/10-base-config.rules
        content: |+
          ## First rule - delete all
          -D

          ## Increase the buffers to survive stress events.
          ## Make this bigger for busy systems
          -b 8192

          ## This determine how long to wait in burst of events
          --backlog_wait_time 60000

          ## Set failure mode to syslog
          -f 1

        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_basic_configuration
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure basic parameters of Audit system - Remove any permissions from group
        and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/10-base-config.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_basic_configuration
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of unsuccessful file creations [ref]
Ensure that unsuccessful attempts to create a file are audited. The following rules configure audit as described above:
    ## Unsuccessful file creation (open with O_CREAT)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful file creations might be a sign of a malicious action being performed on the system. Keeping log of such events helps in monitoring and investigation of such actions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_create_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
app-srg-ctr | SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-1-create-failed.rules
    ## Unsuccessful file creation (open with O_CREAT)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    -a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-1-create-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-1-create-failed.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-1-create-failed.rules
        content: |
          ## Unsuccessful file creation (open with O_CREAT)
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b32 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S creat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b32 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S open -F a1&0100 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b32 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
          -a always,exit -F arch=b64 -S creat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-create
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful file creations - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-1-create-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful file creations [ref]
Ensure that successful attempts to create a file are audited. The following rules configure audit as described above:
    ## Successful file creation (open with O_CREAT)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b32 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of successful attempts to create a file helps in investigation of actions which happened on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_create_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-1-create-success.rules
    ## Successful file creation (open with O_CREAT)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b32 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b32 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    -a always,exit -F arch=b64 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-1-create-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-1-create-success.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-1-create-success.rules
        content: |
          ## Successful file creation (open with O_CREAT)
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
          -a always,exit -F arch=b32 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
          -a always,exit -F arch=b64 -S open -F a1&0100 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
          -a always,exit -F arch=b32 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
          -a always,exit -F arch=b64 -S creat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-create
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful file creations - Remove any permissions from
        group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-1-create-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_create_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of unsuccessful file deletions [ref]
Ensure that unsuccessful attempts to delete a file are audited. The following rules configure audit as described above:
    ## Unsuccessful file delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful attempts to delete a file might be signs of malicious activities. Auditing of such events help in monitoring and investigating of such activities.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_delete_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205, SRG-OS-000468-GPOS-00212
app-srg-ctr | SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-4-delete-failed.rules
    ## Unsuccessful file delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-4-delete-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-4-delete-failed.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-4-delete-failed.rules
        content: |
          ## Unsuccessful file delete
          -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
          -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
          -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
          -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-delete
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful file deletions - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-4-delete-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful file deletions [ref]
Ensure that successful attempts to delete a file are audited. The following rules configure audit as described above:
    ## Successful file delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of successful attempts to delete a file may help in monitoring and investigation of activities performed on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_delete_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205, SRG-OS-000468-GPOS-00212
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-4-delete-success.rules
    ## Successful file delete
    -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
    -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-4-delete-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-4-delete-success.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-4-delete-success.rules
        content: |
          ## Successful file delete
          -a always,exit -F arch=b32 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
          -a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-delete
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful file deletions - Remove any permissions from
        group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-4-delete-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_delete_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure immutable Audit login UIDs [ref]
Configure kernel to prevent modification of login UIDs once they are set. Changing login UIDs while this configuration is enforced requires special capabilities which are not available to unprivileged users. The following rules configure audit as described above:
    ## Make the loginuid immutable. This prevents tampering with the auid.
    --loginuid-immutable
Load new Audit rules into kernel by running:
    augenrules --load
Rationale: | If modification of login UIDs is not prevented, they can be changed by unprivileged users and make auditing complicated or impossible.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_immutable_login_uids
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.2
os-srg | SRG-OS-000462-GPOS-00206, SRG-OS-000475-GPOS-00220, SRG-OS-000057-GPOS-00027, SRG-OS-000058-GPOS-00028, SRG-OS-000059-GPOS-00029
app-srg-ctr | SRG-APP-000121-CTR-000255, SRG-APP-000495-CTR-001235
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/11-loginuid.rules
    ## Make the loginuid immutable. This prevents tampering with the auid.
    --loginuid-immutable

    EOF

    chmod o-rwx /etc/audit/rules.d/11-loginuid.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_immutable_login_uids
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/11-loginuid.rules according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/11-loginuid.rules
        content: |+
          ## Make the loginuid immutable. This prevents tampering with the auid.
          --loginuid-immutable

        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_immutable_login_uids
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure immutable Audit login UIDs - Remove any permissions from group and
        other
      ansible.builtin.file:
        path: /etc/audit/rules.d/11-loginuid.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_immutable_login_uids
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of unsuccessful file modifications [ref]
Ensure that unsuccessful attempts to modify a file are audited. The following rules configure audit as described above:
    ## Unsuccessful file modifications (open for write or truncate)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful file modifications might be a sign of a malicious action being performed on the system. Auditing of such events helps in detection and investigation of such actions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_modify_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
app-srg-ctr | SRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-2-modify-failed.rules
    ## Unsuccessful file modifications (open for write or truncate)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-2-modify-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-2-modify-failed.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-2-modify-failed.rules
        content: |
          ## Unsuccessful file modifications (open for write or truncate)
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b32 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S open -F a1&01003 -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b32 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
          -a always,exit -F arch=b64 -S truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-modification
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful file modifications - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-2-modify-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful file modifications [ref]
Ensure that successful attempts to modify a file are audited. The following rules configure audit as described above:
    ## Successful file modifications (open for write or truncate)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of successful attempts to modify a file helps in investigation of actions which happened on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_modify_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000461-GPOS-00205
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-2-modify-success.rules
    ## Successful file modifications (open for write or truncate)
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b32 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b32 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    -a always,exit -F arch=b64 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-2-modify-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-2-modify-success.rules according
        to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-2-modify-success.rules
        content: |
          ## Successful file modifications (open for write or truncate)
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
          -a always,exit -F arch=b32 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
          -a always,exit -F arch=b64 -S open -F a1&01003 -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
          -a always,exit -F arch=b32 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
          -a always,exit -F arch=b64 -S truncate,ftruncate -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-modification
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful file modifications - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-2-modify-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_modify_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of loading and unloading of kernel modules [ref]
Ensure that loading and unloading of kernel modules is audited. The following rules configure audit as described above:
    ## These rules watch for kernel module insertion. By monitoring
    ## the syscall, we do not need any watches on programs.
    -a always,exit -F arch=b32 -S init_module,finit_module -F key=module-load
    -a always,exit -F arch=b64 -S init_module,finit_module -F key=module-load
    -a always,exit -F arch=b32 -S delete_module -F key=module-unload
    -a always,exit -F arch=b64 -S delete_module -F key=module-unload
Load new Audit rules into kernel by running:
    augenrules --load
Rationale: | Loading of a malicious kernel module introduces a risk to the system, as the module has access to sensitive data and perform actions at the operating system kernel level. Having such events audited helps in monitoring and investigating of malicious activities.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_module_load
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000471-GPOS-00216, SRG-OS-000477-GPOS-00222, SRG-OS-000475-GPOS-00220
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/43-module-load.rules
    ## These rules watch for kernel module insertion. By monitoring
    ## the syscall, we do not need any watches on programs.
    -a always,exit -F arch=b32 -S init_module,finit_module -F key=module-load
    -a always,exit -F arch=b64 -S init_module,finit_module -F key=module-load
    -a always,exit -F arch=b32 -S delete_module -F key=module-unload
    -a always,exit -F arch=b64 -S delete_module -F key=module-unload
    EOF

    chmod o-rwx /etc/audit/rules.d/43-module-load.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_module_load
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/43-module-load.rules according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/43-module-load.rules
        content: |
          ## These rules watch for kernel module insertion. By monitoring
          ## the syscall, we do not need any watches on programs.
          -a always,exit -F arch=b32 -S init_module,finit_module -F key=module-load
          -a always,exit -F arch=b64 -S init_module,finit_module -F key=module-load
          -a always,exit -F arch=b32 -S delete_module -F key=module-unload
          -a always,exit -F arch=b64 -S delete_module -F key=module-unload
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_module_load
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of loading and unloading of kernel modules - Remove any
        permissions from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/43-module-load.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_module_load
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Perform general configuration of Audit for OSPP [ref]
Configure some basic `Audit` parameters specific for OSPP profile. In particular, configure `Audit` to watch for direct modification of files storing system user and group information, and usage of applications with special rights which can change system configuration. Further audited events include access to audit log it self, attempts to Alter Process and Session Initiation Information, and attempts to modify MAC controls. The following rules configure audit as described above:
    ## The purpose of these rules is to meet the requirements for Operating
    ## System Protection Profile (OSPP)v4.2. These rules depends on having
    ## the following rule files copied to /etc/audit/rules.d:
    ##
    ## 10-base-config.rules, 11-loginuid.rules,
    ## 30-ospp-v42-1-create-failed.rules, 30-ospp-v42-1-create-success.rules,
    ## 30-ospp-v42-2-modify-failed.rules, 30-ospp-v42-2-modify-success.rules,
    ## 30-ospp-v42-3-access-failed.rules, 30-ospp-v42-3-access-success.rules,
    ## 30-ospp-v42-4-delete-failed.rules, 30-ospp-v42-4-delete-success.rules,
    ## 30-ospp-v42-5-perm-change-failed.rules,
    ## 30-ospp-v42-5-perm-change-success.rules,
    ## 30-ospp-v42-6-owner-change-failed.rules,
    ## 30-ospp-v42-6-owner-change-success.rules
    ##
    ## original copies may be found in /usr/share/audit/sample-rules/

    ## User add delete modify. This is covered by pam. However, someone could
    ## open a file and directly create or modify a user, so we'll watch passwd and
    ## shadow for writes
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify

    ## User enable and disable. This is entirely handled by pam.

    ## Group add delete modify. This is covered by pam. However, someone could
    ## open a file and directly create or modify a user, so we'll watch group and
    ## gshadow for writes
    -a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify

    ## Use of special rights for config changes. This would be use of setuid
    ## programs that relate to user accts. This is not all setuid apps because
    ## requirements are only for ones that affect system configuration.
    -a always,exit -F arch=b32 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes

    ## Privilege escalation via su or sudo. This is entirely handled by pam.
    ## Special case for systemd-run. It is not audit aware, specifically watch it
    -a always,exit -F arch=b32 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
    -a always,exit -F arch=b64 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
    ## Special case for pkexec. It is not audit aware, specifically watch it
    -a always,exit -F arch=b32 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation
    -a always,exit -F arch=b64 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation

    ## Watch for configuration changes to privilege escalation.
    -a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes

    ## Audit log access
    -a always,exit -F arch=b32 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
    -a always,exit -F arch=b64 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
    ## Attempts to Alter Process and Session Initiation Information
    -a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session

    ## Attempts to modify MAC controls
    -a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy
    -a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy

    ## Software updates. This is entirely handled by rpm.

    ## System start and shutdown. This is entirely handled by systemd

    ## Kernel Module loading. This is handled in 43-module-load.rules

    ## Application invocation. The requirements list an optional requirement
    ## FPT_SRP_EXT.1 Software Restriction Policies. This event is intended to
    ## state results from that policy. This would be handled entirely by
    ## that daemon.
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of events listed in the description provides data for monitoring and investigation of potentially malicious events e.g. tampering with `Audit` logs, malicious access to files storing information about system users and groups etc.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_ospp_general
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000004-GPOS-00004, SRG-OS-000241-GPOS-00091, SRG-OS-000476-GPOS-00221, SRG-OS-000327-GPOS-00127, SRG-OS-000475-GPOS-00220, SRG-OS-000239-GPOS-00089, SRG-OS-000274-GPOS-00104, SRG-OS-000275-GPOS-00105, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42.rules
    ## The purpose of these rules is to meet the requirements for Operating
    ## System Protection Profile (OSPP)v4.2. These rules depends on having
    ## the following rule files copied to /etc/audit/rules.d:
    ##
    ## 10-base-config.rules, 11-loginuid.rules,
    ## 30-ospp-v42-1-create-failed.rules, 30-ospp-v42-1-create-success.rules,
    ## 30-ospp-v42-2-modify-failed.rules, 30-ospp-v42-2-modify-success.rules,
    ## 30-ospp-v42-3-access-failed.rules, 30-ospp-v42-3-access-success.rules,
    ## 30-ospp-v42-4-delete-failed.rules, 30-ospp-v42-4-delete-success.rules,
    ## 30-ospp-v42-5-perm-change-failed.rules,
    ## 30-ospp-v42-5-perm-change-success.rules,
    ## 30-ospp-v42-6-owner-change-failed.rules,
    ## 30-ospp-v42-6-owner-change-success.rules
    ##
    ## original copies may be found in /usr/share/audit/sample-rules/

    ## User add delete modify. This is covered by pam. However, someone could
    ## open a file and directly create or modify a user, so we'll watch passwd and
    ## shadow for writes
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify

    ## User enable and disable. This is entirely handled by pam.

    ## Group add delete modify. This is covered by pam. However, someone could
    ## open a file and directly create or modify a user, so we'll watch group and
    ## gshadow for writes
    -a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
    -a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
    -a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify

    ## Use of special rights for config changes. This would be use of setuid
    ## programs that relate to user accts. This is not all setuid apps because
    ## requirements are only for ones that affect system configuration.
    -a always,exit -F arch=b32 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b32 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes

    ## Privilege escalation via su or sudo. This is entirely handled by pam.
    ## Special case for systemd-run. It is not audit aware, specifically watch it
    -a always,exit -F arch=b32 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
    -a always,exit -F arch=b64 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
    ## Special case for pkexec. It is not audit aware, specifically watch it
    -a always,exit -F arch=b32 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation
    -a always,exit -F arch=b64 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation

    ## Watch for configuration changes to privilege escalation.
    -a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes
    -a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes

    ## Audit log access
    -a always,exit -F arch=b32 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
    -a always,exit -F arch=b64 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
    ## Attempts to Alter Process and Session Initiation Information
    -a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
    -a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session

    ## Attempts to modify MAC controls
    -a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy
    -a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy

    ## Software updates. This is entirely handled by rpm.

    ## System start and shutdown. This is entirely handled by systemd

    ## Kernel Module loading. This is handled in 43-module-load.rules

    ## Application invocation. The requirements list an optional requirement
    ## FPT_SRP_EXT.1 Software Restriction Policies. This event is intended to
    ## state results from that policy. This would be handled entirely by
    ## that daemon.

    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_ospp_general
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42.rules according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42.rules
        content: |+
          ## The purpose of these rules is to meet the requirements for Operating
          ## System Protection Profile (OSPP)v4.2. These rules depends on having
          ## the following rule files copied to /etc/audit/rules.d:
          ##
          ## 10-base-config.rules, 11-loginuid.rules,
          ## 30-ospp-v42-1-create-failed.rules, 30-ospp-v42-1-create-success.rules,
          ## 30-ospp-v42-2-modify-failed.rules, 30-ospp-v42-2-modify-success.rules,
          ## 30-ospp-v42-3-access-failed.rules, 30-ospp-v42-3-access-success.rules,
          ## 30-ospp-v42-4-delete-failed.rules, 30-ospp-v42-4-delete-success.rules,
          ## 30-ospp-v42-5-perm-change-failed.rules,
          ## 30-ospp-v42-5-perm-change-success.rules,
          ## 30-ospp-v42-6-owner-change-failed.rules,
          ## 30-ospp-v42-6-owner-change-success.rules
          ##
          ## original copies may be found in /usr/share/audit/sample-rules/

          ## User add delete modify. This is covered by pam. However, someone could
          ## open a file and directly create or modify a user, so we'll watch passwd and
          ## shadow for writes
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/passwd -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b32 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -S openat,open_by_handle_at -F a2&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b32 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -S open -F a1&03 -F path=/etc/shadow -F auid>=1000 -F auid!=unset -F key=user-modify

          ## User enable and disable. This is entirely handled by pam.

          ## Group add delete modify. This is covered by pam. However, someone could
          ## open a file and directly create or modify a user, so we'll watch group and
          ## gshadow for writes
          -a always,exit -F arch=b32 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b32 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=user-modify
          -a always,exit -F arch=b32 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
          -a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
          -a always,exit -F arch=b32 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify
          -a always,exit -F arch=b64 -F path=/etc/gshadow -F perm=wa -F auid>=1000 -F auid!=unset -F key=group-modify

          ## Use of special rights for config changes. This would be use of setuid
          ## programs that relate to user accts. This is not all setuid apps because
          ## requirements are only for ones that affect system configuration.
          -a always,exit -F arch=b32 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/sbin/usernetctl -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/sbin/userhelper -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/sbin/seunshare -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b32 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/usr/sbin/grub2-set-bootflag -F perm=x -F auid>=1000 -F auid!=unset -F key=special-config-changes

          ## Privilege escalation via su or sudo. This is entirely handled by pam.
          ## Special case for systemd-run. It is not audit aware, specifically watch it
          -a always,exit -F arch=b32 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
          -a always,exit -F arch=b64 -F path=/usr/bin/systemd-run -F perm=x -F auid!=unset -F key=maybe-escalation
          ## Special case for pkexec. It is not audit aware, specifically watch it
          -a always,exit -F arch=b32 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation
          -a always,exit -F arch=b64 -F path=/usr/bin/pkexec -F perm=x -F key=maybe-escalation

          ## Watch for configuration changes to privilege escalation.
          -a always,exit -F arch=b32 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
          -a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=special-config-changes
          -a always,exit -F arch=b32 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes
          -a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=special-config-changes

          ## Audit log access
          -a always,exit -F arch=b32 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
          -a always,exit -F arch=b64 -F dir=/var/log/audit/ -F perm=r -F auid>=1000 -F auid!=unset -F key=access-audit-trail
          ## Attempts to Alter Process and Session Initiation Information
          -a always,exit -F arch=b32 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
          -a always,exit -F arch=b64 -F path=/var/run/utmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
          -a always,exit -F arch=b32 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
          -a always,exit -F arch=b64 -F path=/var/log/btmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
          -a always,exit -F arch=b32 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session
          -a always,exit -F arch=b64 -F path=/var/log/wtmp -F perm=wa -F auid>=1000 -F auid!=unset -F key=session

          ## Attempts to modify MAC controls
          -a always,exit -F arch=b32 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy
          -a always,exit -F arch=b64 -F dir=/etc/selinux/ -F perm=wa -F auid>=1000 -F auid!=unset -F key=MAC-policy

          ## Software updates. This is entirely handled by rpm.

          ## System start and shutdown. This is entirely handled by systemd

          ## Kernel Module loading. This is handled in 43-module-load.rules

          ## Application invocation. The requirements list an optional requirement
          ## FPT_SRP_EXT.1 Software Restriction Policies. This event is intended to
          ## state results from that policy. This would be handled entirely by
          ## that daemon.

        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_ospp_general
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Perform general configuration of Audit for OSPP - Remove any permissions from
        group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_ospp_general
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of unsuccessful ownership changes [ref]
Ensure that unsuccessful attempts to change an ownership of files or directories are audited. The following rules configure audit as described above:
    ## Unsuccessful ownership change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful attempts to change an ownership of files or directories might be signs of a malicious activity. Having such events audited helps in monitoring and investigation of such activities.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_owner_change_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-6-owner-change-failed.rules
    ## Unsuccessful ownership change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-6-owner-change-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-6-owner-change-failed.rules
        according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-6-owner-change-failed.rules
        content: |
          ## Unsuccessful ownership change
          -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
          -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
          -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
          -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-owner-change
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful ownership changes - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-6-owner-change-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful ownership changes [ref]
Ensure that successful attempts to change an ownership of files or directories are audited. The following rules configure audit as described above:
    ## Successful ownership change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing of successful ownership changes of files or directories helps in monitoring or investigating of activities performed on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_owner_change_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-6-owner-change-success.rules
    ## Successful ownership change
    -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
    -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-6-owner-change-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-6-owner-change-success.rules
        according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-6-owner-change-success.rules
        content: |
          ## Successful ownership change
          -a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
          -a always,exit -F arch=b64 -S lchown,fchown,chown,fchownat -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-owner-change
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful ownership changes - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-6-owner-change-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_owner_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of unsuccessful permission changes [ref]
Ensure that unsuccessful attempts to change file or directory permissions are audited. The following rules configure audit as described above:
    ## Unsuccessful permission change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Unsuccessful attempts to change permissions of files or directories might be signs of malicious activity. Having such events audited helps in monitoring and investigation of such activities.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_perm_change_failed
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-5-perm-change-failed.rules
    ## Unsuccessful permission change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-5-perm-change-failed.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-5-perm-change-failed.rules
        according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-5-perm-change-failed.rules
        content: |
          ## Unsuccessful permission change
          -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
          -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EACCES -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
          -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
          -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F exit=-EPERM -F auid>=1000 -F auid!=unset -F key=unsuccessful-perm-change
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of unsuccessful permission changes - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-5-perm-change-failed.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_failed
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure auditing of successful permission changes [ref]
Ensure that successful attempts to modify permissions of files or directories are audited. The following rules configure audit as described above:
    ## Successful permission change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
Load new Audit rules into kernel by running:
    augenrules --load
Note: This rule uses a special set of Audit rules to comply with OSPP 4.2.1. You may reuse this rule in different profiles. If you decide to do so, it is recommended that you inspect contents of the file closely and make sure that they are aligned with your needs.
Rationale: | Auditing successful file or directory permission changes helps in monitoring and investigating of activities performed on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_perm_change_success
References: |
nist | AU-2(a)
ospp | FAU_GEN.1.1.c
os-srg | SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000474-GPOS-00219, SRG-OS-000475-GPOS-00220, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    cat << 'EOF' > /etc/audit/rules.d/30-ospp-v42-5-perm-change-success.rules
    ## Successful permission change
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
    EOF

    chmod o-rwx /etc/audit/rules.d/30-ospp-v42-5-perm-change-success.rules

    augenrules --load

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Put contents into /etc/audit/rules.d/30-ospp-v42-5-perm-change-success.rules
        according to policy
      ansible.builtin.copy:
        dest: /etc/audit/rules.d/30-ospp-v42-5-perm-change-success.rules
        content: |
          ## Successful permission change
          -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
          -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F success=1 -F auid>=1000 -F auid!=unset -F key=successful-perm-change
        force: true
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure auditing of successful permission changes - Remove any permissions
        from group and other
      ansible.builtin.file:
        path: /etc/audit/rules.d/30-ospp-v42-5-perm-change-success.rules
        mode: g-rwx,o-rwx
        state: touch
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - NIST-800-53-AU-2(a)
      - audit_perm_change_success
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure the audit Subsystem is Installed [ref]
The audit package should be installed.
Rationale: | The auditd service is an access monitoring and accounting daemon, watching system calls to audit any access, in comparison with potential local access control policy such as SELinux policy.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_audit_installed
References: |
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
nerc-cip | CIP-004-6 R3.3, CIP-007-3 R6.5
nist | AC-7(a), AU-7(1), AU-7(2), AU-14, AU-12(2), AU-2(a), CM-6(a)
ospp | FAU_GEN.1
pcidss | Req-10.1
os-srg | SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220
anssi | R33, R73
ism | 0582, 0846
pcidss4 | 10.2.1, 10.2
stigid | OL09-00-000440
stigref | SV-271519r1091269_rule
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "audit"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek; then

    if ! rpm -q --quiet "audit" ; then
        yum install -y "audit"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=audit
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000440
      - NIST-800-53-AC-7(a)
      - NIST-800-53-AU-12(2)
      - NIST-800-53-AU-14
      - NIST-800-53-AU-2(a)
      - NIST-800-53-AU-7(1)
      - NIST-800-53-AU-7(2)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.1
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_audit_installed

    - name: Ensure audit is installed
      ansible.builtin.package:
        name: audit
        state: present
      when: ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      tags:
      - DISA-STIG-OL09-00-000440
      - NIST-800-53-AC-7(a)
      - NIST-800-53-AU-12(2)
      - NIST-800-53-AU-14
      - NIST-800-53-AU-2(a)
      - NIST-800-53-AU-7(1)
      - NIST-800-53-AU-7(2)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.1
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_audit_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_audit

    class install_audit {
      package { 'audit':
        ensure => 'installed',
      }
    }
#### Rule Enable auditd Service [ref]
The `auditd` service is an essential userspace component of the Linux Auditing System, as it is responsible for writing audit records to disk. The `auditd` service can be enabled with the following command:
    $ sudo systemctl enable auditd.service
Rationale: | Without establishing what type of events occurred, it would be difficult to establish, correlate, and investigate the events leading up to an outage or attack. Ensuring the `auditd` service is active ensures audit records generated by the kernel are appropriately recorded. Additionally, a properly configured audit subsystem ensures that actions of individual system users can be uniquely traced to those users so they can be held accountable for their actions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_auditd_enabled
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis | 5.4.1.1
cobit5 | APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui | 3.3.1, 3.3.2, 3.3.6
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
isa-62443-2009 | 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013 | SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, 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 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013 | A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nerc-cip | CIP-004-6 R3.3, CIP-007-3 R6.5
nist | AC-2(g), AU-3, AU-10, AU-2(d), AU-12(c), AU-14(1), AC-6(9), CM-6(a), SI-4(23)
nist-csf | DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
ospp | FAU_GEN.1
pcidss | Req-10.1
os-srg | SRG-OS-000062-GPOS-00031, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000337-GPOS-00129, SRG-OS-000348-GPOS-00136, SRG-OS-000349-GPOS-00137, SRG-OS-000350-GPOS-00138, SRG-OS-000351-GPOS-00139, SRG-OS-000352-GPOS-00140, SRG-OS-000353-GPOS-00141, SRG-OS-000354-GPOS-00142, SRG-OS-000358-GPOS-00145, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000475-GPOS-00220
app-srg-ctr | SRG-APP-000095-CTR-000170, SRG-APP-000409-CTR-000990, SRG-APP-000508-CTR-001300, SRG-APP-000510-CTR-001310
anssi | R33, R73
ism | 1409
pcidss4 | 10.2.1, 10.2
stigid | OL09-00-000441
stigref | SV-271520r1091272_rule
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: auditd.service
            enabled: true
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["auditd"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q audit; }; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'auditd.service'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" start 'auditd.service'
    fi
    "$SYSTEMCTL_EXEC" enable 'auditd.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000441
      - NIST-800-171-3.3.1
      - NIST-800-171-3.3.2
      - NIST-800-171-3.3.6
      - NIST-800-53-AC-2(g)
      - NIST-800-53-AC-6(9)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-12(c)
      - NIST-800-53-AU-14(1)
      - NIST-800-53-AU-2(d)
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(23)
      - PCI-DSS-Req-10.1
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_auditd_enabled

    - name: Enable auditd Service - Enable service auditd
      block:

      - name: Gather the package facts
        ansible.builtin.package_facts:
          manager: auto

      - name: Enable auditd Service - Enable Service auditd
        ansible.builtin.systemd:
          name: auditd
          enabled: true
          state: started
          masked: false
        when:
        - '"audit" in ansible_facts.packages'
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000441
      - NIST-800-171-3.3.1
      - NIST-800-171-3.3.2
      - NIST-800-171-3.3.6
      - NIST-800-53-AC-2(g)
      - NIST-800-53-AC-6(9)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-12(c)
      - NIST-800-53-AU-14(1)
      - NIST-800-53-AU-2(d)
      - NIST-800-53-AU-3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-4(23)
      - PCI-DSS-Req-10.1
      - PCI-DSSv4-10.2
      - PCI-DSSv4-10.2.1
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_auditd_enabled
      - special_service_block
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"audit" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_auditd

    class enable_auditd {
      service {'auditd':
        enable => true,
        ensure => 'running',
      }
    }
#### Rule Enable Auditing for Processes Which Start Prior to the Audit Daemon [ref]
To ensure all processes can be audited, even those which start prior to the audit daemon, add the argument `audit=1` to the default GRUB 2 command line for the Linux operating system. To ensure that `audit=1` is added as a kernel command line argument to newly installed kernels, add `audit=1` to the default Grub2 command line for Linux operating systems. Modify the line within `/etc/default/grub` as shown below:
    GRUB_CMDLINE_LINUX="... audit=1 ..."
Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --args="audit=1"
Rationale: | Each process on the system carries an "auditable" flag which indicates whether its activities can be audited. Although `auditd` takes care of enabling this for all processes which launch after it does, adding the kernel argument ensures it is set for every process during boot.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_audit_argument
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 19, 3, 4, 5, 6, 7, 8
cjis | 5.4.1.1
cobit5 | APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS05.02, DSS05.03, DSS05.04, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01
cui | 3.3.1
hipaa | 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b)
isa-62443-2009 | 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013 | SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, 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 6.1, SR 7.1, SR 7.6
iso27001-2013 | A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2
nist | AC-17(1), AU-14(1), AU-10, CM-6(a), IR-5(1)
nist-csf | DE.AE-3, DE.AE-5, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4
ospp | FAU_GEN.1
pcidss | Req-10.3
os-srg | SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000473-GPOS-00218, SRG-OS-000254-GPOS-00095
pcidss4 | 10.7.2, 10.7
stigid | OL09-00-000750
stigref | SV-271577r1091443_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.kernel]
    append = "audit=1"
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q grub2-common; }; then

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        KARGS_DIR="/usr/lib/bootc/kargs.d/"
        if grep -q -E "audit" "$KARGS_DIR/*.toml" ; then
            sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"audit=[^\"]*\"(.*]\s*)/\1\"audit=1\"\2/" "$KARGS_DIR/*.toml"
        else
            echo "kargs = [\"audit=1\"]" >> "$KARGS_DIR/10-audit.toml"
        fi
    else

        grubby --update-kernel=ALL --args=audit=1

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000750
      - NIST-800-171-3.3.1
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-14(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IR-5(1)
      - PCI-DSS-Req-10.3
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

    - name: Check if audit argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000750
      - NIST-800-171-3.3.1
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-14(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IR-5(1)
      - PCI-DSS-Req-10.3
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

    - name: Check if audit argument is already present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000750
      - NIST-800-171-3.3.1
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-14(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IR-5(1)
      - PCI-DSS-Req-10.3
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --args="audit=1"
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      - (grubby_info.stdout is not search('audit=1')) or ((etc_default_grub['content']
        | b64decode) is not search('audit=1'))
      tags:
      - CJIS-5.4.1.1
      - DISA-STIG-OL09-00-000750
      - NIST-800-171-3.3.1
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AU-10
      - NIST-800-53-AU-14(1)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IR-5(1)
      - PCI-DSS-Req-10.3
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy
#### Rule Extend Audit Backlog Limit for the Audit Daemon [ref]
To improve the kernel capacity to queue all log events, even those which occurred prior to the audit daemon, add the argument `audit_backlog_limit=8192` to the default GRUB 2 command line for the Linux operating system. To ensure that `audit_backlog_limit=8192` is added as a kernel command line argument to newly installed kernels, add `audit_backlog_limit=8192` to the default Grub2 command line for Linux operating systems. Modify the line within `/etc/default/grub` as shown below:
    GRUB_CMDLINE_LINUX="... audit_backlog_limit=8192 ..."
Run the following command to update command line for already installed kernels:
    # grubby --update-kernel=ALL --args="audit_backlog_limit=8192"
Rationale: | audit_backlog_limit sets the queue length for audit events awaiting transfer to the audit daemon. Until the audit daemon is up and running, all log messages are stored in this queue. If the queue is overrun during boot process, the action defined by audit failure flag is taken.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_audit_backlog_limit_argument
References: |
nist | CM-6(a)
ospp | FAU_STG.1, FAU_STG.3
os-srg | SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000254-GPOS-00095, SRG-OS-000341-GPOS-00132, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215
pcidss4 | 10.7.2, 10.7
stigid | OL09-00-000830
stigref | SV-271592r1091488_rule
Remediation OSBuild Blueprint snippet ⇲
    [customizations.kernel]
    append = "audit_backlog_limit=8192"
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel || rpm --quiet -q kernel-uek && { rpm --quiet -q grub2-common; }; then

    var_audit_backlog_limit='8192'

    if { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        KARGS_DIR="/usr/lib/bootc/kargs.d/"
        if grep -q -E "audit_backlog_limit" "$KARGS_DIR/*.toml" ; then
            sed -i -E "s/^(\s*kargs\s*=\s*\[.*)\"audit_backlog_limit=[^\"]*\"(.*]\s*)/\1\"audit_backlog_limit=$var_audit_backlog_limit\"\2/" "$KARGS_DIR/*.toml"
        else
            echo "kargs = [\"audit_backlog_limit=$var_audit_backlog_limit\"]" >> "$KARGS_DIR/10-audit_backlog_limit.toml"
        fi
    else

        grubby --update-kernel=ALL --args=audit_backlog_limit=$var_audit_backlog_limit

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - DISA-STIG-OL09-00-000830
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_backlog_limit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy
    - name: XCCDF Value var_audit_backlog_limit # promote to variable
      set_fact:
        var_audit_backlog_limit: !!str 8192
      tags:
        - always

    - name: Check if audit_backlog_limit argument is already present in /etc/default/grub
      ansible.builtin.slurp:
        src: /etc/default/grub
      register: etc_default_grub
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000830
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_backlog_limit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

    - name: Check if audit_backlog_limit argument is already present
      ansible.builtin.command: /sbin/grubby --info=ALL
      register: grubby_info
      check_mode: false
      changed_when: false
      failed_when: false
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - DISA-STIG-OL09-00-000830
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_backlog_limit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

    - name: Update grub defaults and the bootloader menu
      ansible.builtin.command: /sbin/grubby --update-kernel=ALL --args="audit_backlog_limit={{
        var_audit_backlog_limit }}"
      when:
      - ("kernel" in ansible_facts.packages or "kernel-uek" in ansible_facts.packages)
      - '"grub2-common" in ansible_facts.packages'
      - (grubby_info.stdout is not search('audit_backlog_limit=' ~ var_audit_backlog_limit))
        or ((etc_default_grub['content'] | b64decode) is not search('audit_backlog_limit='
        ~ var_audit_backlog_limit))
      tags:
      - DISA-STIG-OL09-00-000830
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-10.7
      - PCI-DSSv4-10.7.2
      - grub2_audit_backlog_limit_argument
      - low_disruption
      - low_severity
      - medium_complexity
      - reboot_required
      - restrict_strategy

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.