Guide to the Secure Configuration of Red Hat Enterprise Linux 10 | OpenSCAP Security Guide

## Guide to the Secure Configuration of Red Hat Enterprise Linux 10

> with profile CIS Red Hat Enterprise Linux 10 Benchmark for Level 1 - Workstation
> This profile defines a baseline that aligns to the "Level 1 - Workstation" configuration from the Center for Internet Security® Red Hat Enterprise Linux 10 Benchmark™, v1.0.1, released 2025-09-30. This profile includes Center for Internet Security® Red Hat Enterprise Linux 10 CIS Benchmarks™ content.

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 Red Hat Enterprise Linux 10. It is a rendering of content structured in the eXtensible Configuration Checklist Description Format (XCCDF) in order to support security automation. The SCAP content is is available in the `scap-security-guide` package which is developed at https://www.open-scap.org/security-policies/scap-security-guide.
Providing system administrators with such guidance informs them how to securely configure systems under their control in a variety of network roles. Policy makers and baseline creators can use this catalog of settings, with its associated references to higher-level security control catalogs, in order to assist them in security baseline creation. This guide is a catalog, not a checklist, and satisfaction of every item is not likely to be possible or sensible in many operational scenarios. However, the XCCDF format enables granular selection and adjustment of settings, and their association with OVAL and OCIL content provides an automated checking capability. Transformations of this document, and its associated automated checking content, are capable of providing baselines that meet a diverse set of policy objectives. Some example XCCDF Profiles, which are selections of items that form checklists and can be used as baselines, are available with this guide. They can be processed, in an automated fashion, with tools that support the Security Content Automation Protocol (SCAP). The DISA STIG, which provides required settings for US Department of Defense systems, is one example of a baseline created from this guidance.

This benchmark is a direct port of a SCAP Security Guide benchmark developed for Red Hat Enterprise Linux. It has been modified through an automated process to remove specific dependencies on Red Hat Enterprise Linux and to function with CentOS. The result is a generally useful SCAP Security Guide benchmark with the following caveats:

- CentOS is not an exact copy of Red Hat Enterprise Linux. There may be configuration differences that produce false positives and/or false negatives. If this occurs please file a bug report.
- CentOS has its own build system, compiler options, patchsets, and is a community supported, non-commercial operating system. CentOS does not inherit certifications or evaluations from Red Hat Enterprise Linux. As such, some configuration rules (such as those requiring FIPS 140-2 encryption) will continue to fail on CentOS.

Members of the CentOS community are invited to participate in OpenSCAP and SCAP Security Guide development. Bug reports and patches can be sent to GitHub: https://github.com/ComplianceAsCode/content. The mailing list is at https://fedorahosted.org/mailman/listinfo/scap-security-guide.

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 | CIS Red Hat Enterprise Linux 10 Benchmark for Level 1 - Workstation
Profile ID | xccdf_org.ssgproject.content_profile_cis_workstation_l1

#### CPE Platforms

- cpe:/o:redhat:enterprise_linux:10
- cpe:/o:centos:centos:10

## 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. Configure Syslog
   5. Network Configuration and Firewalls
   6. File Permissions and Masks
   7. SELinux
2. Services
   1. Cron and At Daemons
   2. DHCP
   3. DNS Server
   4. FTP Server
   5. Web Server
   6. IMAP and POP3 Server
   7. Mail Server Software
   8. NFS and RPC
   9. Network Time Protocol
   10. Obsolete Services
   11. Proxy Server
   12. Samba(SMB) Microsoft Windows File Sharing Server
   13. SNMP Server
   14. SSH Server

## Checklist

Group Guide to the Secure Configuration of Red Hat Enterprise Linux 10 Group contains 87 groups and 314 rules
Group System Settings Group contains 52 groups and 226 rules
[ref] Contains rules that check correct system settings.
Group Installing and Maintaining Software Group contains 11 groups and 20 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 3 groups and 5 rules
[ref] System and software integrity can be gained by installing antivirus, increasing system encryption strength with FIPS, verifying installed software, enabling SELinux, installing an Intrusion Prevention System, etc. However, installing or enabling integrity checking tools cannot prevent intrusions, but they can detect that an intrusion may have occurred. Requirements for integrity checking may be highly dependent on the environment in which the system will be used. Snapshot-based approaches such as AIDE may induce considerable overhead in the presence of frequent software updates.
Group Software Integrity Checking Group contains 1 group and 4 rules
[ref] Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes. The RPM package management system can conduct integrity checks by comparing information in its metadata database with files installed on the system.
Group Verify Integrity with AIDE Group contains 4 rules
[ref] AIDE conducts integrity checks by comparing information about files with previously-gathered information. Ideally, the AIDE database is created immediately after initial system configuration, and then again after any software update. AIDE is highly configurable, with further configuration information located in `/usr/share/doc/aide-VERSION` .
#### Rule Install AIDE [ref]
The `aide` package can be installed with the following command:
    $ sudo dnf install aide
Rationale: | The AIDE package must be installed if it is to be available for integrity checking.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_aide_installed
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis | 5.10.1.3
cobit5 | APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-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 4.1, SR 6.2, SR 7.6
iso27001-2013 | A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nist | CM-6(a)
nist-csf | DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidss | Req-11.5
os-srg | SRG-OS-000445-GPOS-00199
anssi | R76, R79
ism | 1034, 1288, 1341, 1417
pcidss4 | 11.5.2
bsi | SYS.1.1.A27
cis | 6.1.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install aide
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "aide"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install aide
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=aide
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_aide_installed

    - name: Ensure aide is installed
      ansible.builtin.package:
        name: aide
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_aide_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_aide

    class install_aide {
      package { 'aide':
        ensure => 'installed',
      }
    }
#### Rule Build and Test AIDE Database [ref]
Run the following command to generate a new database:
    $ sudo /usr/sbin/aide --init
By default, the database will be written to the file `/var/lib/aide/aide.db.new.gz`. Storing the database, the configuration file `/etc/aide.conf`, and the binary `/usr/sbin/aide` (or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity. The newly-generated database can be installed as follows:
    $ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
To initiate a manual check, run the following command:
    $ sudo /usr/sbin/aide --check
If this check produces any unexpected output, investigate. Warning: In RHEL Image Mode (bootc) systems, the AIDE database must be regenerated after each system update. Image Mode systems receive updates through new container images that may include modified files. After applying system updates, run the following commands to regenerate the AIDE database:
    $ sudo /usr/sbin/aide --init
Then replace the existing database:
    $ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
Failure to regenerate the AIDE database after updates will result in false positive alerts for legitimate system changes introduced by the update process.
Rationale: | For AIDE to be effective, an initial database of "known-good" information about files must be captured and it should be able to be verified against the installed files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_aide_build_database
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis | 5.10.1.3
cobit5 | APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-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 4.1, SR 6.2, SR 7.6
iso27001-2013 | A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nist | CM-6(a)
nist-csf | DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidss | Req-11.5
os-srg | SRG-OS-000445-GPOS-00199
anssi | R76, R79
pcidss4 | 11.5.2
bsi | SYS.1.1.A27
cis | 6.1.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_build_database
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Build and Test AIDE Database - Ensure AIDE Is Installed
      ansible.builtin.package:
        name: '{{ item }}'
        state: present
      with_items:
      - aide
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_build_database
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Build and Test AIDE Database - Check Whether the Stock AIDE Database Exists
      ansible.builtin.stat:
        path: /var/lib/aide/aide.db.new.gz
      register: aide_database_stat
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_build_database
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Build and Test AIDE Database - Build and Test AIDE Database
      ansible.builtin.command: /usr/sbin/aide --init
      changed_when: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - not (aide_database_stat.stat.exists is defined and aide_database_stat.stat.exists)
      register: aide_database_init
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_build_database
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Build and Test AIDE Database - Stage AIDE Database
      ansible.builtin.copy:
        src: /var/lib/aide/aide.db.new.gz
        dest: /var/lib/aide/aide.db.gz
        backup: true
        remote_src: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - aide_database_init is changed
      - not ansible_check_mode
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_build_database
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure AIDE to Verify the Audit Tools [ref]
The operating system file integrity tool must be configured to protect the integrity of the audit tools.
Rationale: | Protecting the integrity of the tools used for auditing purposes is a critical step toward ensuring the integrity of audit information. Audit information includes all information (e.g., audit records, audit settings, and audit reports) needed to successfully audit information system activity. Audit tools include but are not limited to vendor-provided and open-source audit tools needed to successfully view and manipulate audit information system activity and records. Audit tools include custom queries and report generators. It is not uncommon for attackers to replace the audit tools or inject code into the existing tools to provide the capability to hide or erase system activity from the audit logs. To address this risk, audit tools must be cryptographically signed to provide the capability to identify when the audit tools have been modified, manipulated, or replaced. An example is a checksum hash of the file or files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_aide_check_audit_tools
References: |
nist | AU-9(3), AU-9(3).1
os-srg | SRG-OS-000278-GPOS-00108
cis | 6.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    if grep -i -E '^.*(/usr)?/sbin/auditctl.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/auditctl.*#/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/auditd.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/auditd.*#/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/ausearch.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/ausearch.*#/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/aureport.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/aureport.*#/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/autrace.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/autrace.*#/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/augenrules.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/augenrules.*#/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.conf
    fi

    if grep -i -E '^.*(/usr)?/sbin/rsyslogd.*$' /etc/aide.conf; then
    sed -i -r "s#.*(/usr)?/sbin/rsyslogd.*#/usr/sbin/rsyslogd p+i+n+u+g+s+b+acl+xattrs+sha512#" /etc/aide.conf
    else
    echo "/usr/sbin/rsyslogd p+i+n+u+g+s+b+acl+xattrs+sha512" >> /etc/aide.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:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure AIDE to Verify the Audit Tools - Gather List of Packages
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      ansible.builtin.package_facts:
        manager: auto
      when: '"kernel-core" in ansible_facts.packages'

    - name: Configure AIDE to Verify the Audit Tools - Ensure AIDE is Installed
      ansible.builtin.package:
        name: '{{ item }}'
        state: present
      with_items:
      - aide
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure AIDE to Verify the Audit Tools - Gather the Package Facts
      ansible.builtin.package_facts:
        manager: auto
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set "Configure AIDE to Verify the Audit Tools - audit_tools fact"
      ansible.builtin.set_fact:
        audit_tools:
        - /usr/sbin/auditctl
        - /usr/sbin/auditd
        - /usr/sbin/augenrules
        - /usr/sbin/aureport
        - /usr/sbin/ausearch
        - /usr/sbin/autrace
        - /usr/sbin/rsyslogd
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure AIDE to Verify the Audit Tools - Ensure Existing AIDE Configuration
        for Audit Tools are Correct
      ansible.builtin.lineinfile:
        path: /etc/aide.conf
        regexp: ^{{ item }}\s
        line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
        create: true
      with_items: '{{ audit_tools }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"aide" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure AIDE to Verify the Audit Tools - Configure AIDE to Properly Protect
        Audit Tools
      ansible.builtin.lineinfile:
        path: /etc/aide.conf
        line: '{{ item }} p+i+n+u+g+s+b+acl+xattrs+sha512'
        create: true
      with_items: '{{ audit_tools }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"aide" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-9(3)
      - NIST-800-53-AU-9(3).1
      - aide_check_audit_tools
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Configure Periodic Execution of AIDE [ref]
At a minimum, AIDE should be configured to run a weekly scan. To implement a daily execution of AIDE at 4:05am using cron, add the following line to `/etc/crontab`:
    05 4 * * * root /usr/sbin/aide --check
To implement a weekly execution of AIDE at 4:05am using cron, add the following line to `/etc/crontab`:
    05 4 * * 0 root /usr/sbin/aide --check
AIDE can be executed periodically through other means; this is merely one example. The usage of cron's special time codes, such as `@daily` and `@weekly` is acceptable.
Rationale: | By default, AIDE does not install itself for periodic execution. Periodically running AIDE is necessary to reveal unexpected changes in installed files. Unauthorized changes to the baseline configuration could make the system vulnerable to various attacks or allow unauthorized access to the operating system. Changes to operating system configurations can have unintended side effects, some of which may be relevant to security. Detecting such changes and providing an automated response can help avoid unintended, negative consequences that could ultimately affect the security state of the operating system. The operating system's Information Management Officer (IMO)/Information System Security Officer (ISSO) and System Administrators (SAs) must be notified via email and/or monitoring system trap when there is an unauthorized modification of a configuration item.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_aide_periodic_cron_checking
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis | 5.10.1.3
cobit5 | APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
isa-62443-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 4.1, SR 6.2, SR 7.6
iso27001-2013 | A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nist | SI-7, SI-7(1), CM-6(a)
nist-csf | DE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidss | Req-11.5
os-srg | SRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201
anssi | R76
pcidss4 | 11.5.2
bsi | SYS.1.1.A27
cis | 6.1.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    CRON_FILE="/etc/crontab"

    if ! grep -q "/usr/sbin/aide --check" "${CRON_FILE}" ; then
        echo "05 4 * * * root /usr/sbin/aide --check" >> "${CRON_FILE}"
    else
        sed -i '\!^.* --check.*$!d' "${CRON_FILE}"
        echo "05 4 * * * root /usr/sbin/aide --check" >> "${CRON_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:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-7
      - NIST-800-53-SI-7(1)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_periodic_cron_checking
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure Periodic Execution of AIDE - Ensure AIDE is installed
      ansible.builtin.package:
        name: aide
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-7
      - NIST-800-53-SI-7(1)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_periodic_cron_checking
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure Periodic Execution of AIDE - Install cron
      ansible.builtin.package:
        name: cronie
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-7
      - NIST-800-53-SI-7(1)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_periodic_cron_checking
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure Periodic Execution of AIDE - Gather List of Installed Packages
      ansible.builtin.package_facts:
        manager: auto
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-7
      - NIST-800-53-SI-7(1)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_periodic_cron_checking
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Configure Periodic Execution of AIDE - Setup Cron Tab
      ansible.builtin.cron:
        name: run AIDE check
        minute: 5
        hour: 4
        user: root
        job: /usr/sbin/aide --check
      register: crontab_check
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '''cronie'' in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.3
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SI-7
      - NIST-800-53-SI-7(1)
      - PCI-DSS-Req-11.5
      - PCI-DSSv4-11.5.2
      - aide_periodic_cron_checking
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group System Cryptographic Policies Group contains 1 rule
[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 Implement Custom Crypto Policy Modules for CIS Benchmark [ref]
Create a custom crypto policy module to enforce the use of strong ciphers and MACs in SSHD, disable CBC mode ciphers in SSHD and disable the use of weak MACs globally. Add the following line to the file `/etc/crypto-policies/policies/modules/NO-SSHCBC.pmod`:
    cipher@SSH = -*-CBC
Add the following line to the file `/etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod`:
    cipher@SSH = -3DES-CBC -AES-128-CBC -AES-192-CBC -AES-256-CBC
Add the following line to the file `/etc/crypto-policies/policies/modules/NO-SSHWEAKMACS.pmod`:
    mac@SSH = -HMAC-MD5* -UMAC-64* -UMAC-128* -HMAC-SHA1*
Add the following line to the file `/etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod`:
    mac = -*-128*
Then, set the system wide crypto policy to use the custom policy.
    $ sudo update-crypto-policies --set DEFAULT:NO-SSHCBC:NO-SSHWEAKCIPHERS:NO-SSHWEAKMACS:NO-WEAKMAC
Rationale: | CBC mode ciphers are vulnerable to certain attacks, such as the BEAST attack. Disabling CBC mode ciphers helps protect against these attacks and ensures that only strong, proven cryptographic algorithms are used to protect SSH communications. Weak ciphers that are used for authentication to the cryptographic module cannot be relied upon to provide confidentiality or integrity, and system data may be compromised. Message Authentication Codes (MACs) are cryptographic mechanisms used to verify the integrity and authenticity of data transmitted over SSH connections. Weak MACs that are used for authentication to the cryptographic module cannot be relied upon to provide integrity, and system data may be compromised. Implementing a custom crypto policy that disables weak MAC algorithms helps ensure that only strong, proven cryptographic algorithms are used to protect SSH communications.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_configure_custom_crypto_policy_cis
References: |
cis | 1.6.1, 1.6.2, 1.6.3, 1.6.4, 5.1.6, 5.1.15
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | configure
    expected_crypto_policy="DEFAULT"

    expected_crypto_policy="${expected_crypto_policy}:NO-SSHCBC"
    cat << 'EOF' > /etc/crypto-policies/policies/modules/NO-SSHCBC.pmod
    cipher@SSH = -*-CBC
    EOF

    expected_crypto_policy="${expected_crypto_policy}:NO-SSHWEAKCIPHERS"
    cat << 'EOF' > /etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod
    cipher@SSH = -3DES-CBC -AES-128-CBC -AES-192-CBC -AES-256-CBC
    EOF

    expected_crypto_policy="${expected_crypto_policy}:NO-SSHWEAKMACS"
    cat << 'EOF' > /etc/crypto-policies/policies/modules/NO-SSHWEAKMACS.pmod
    mac@SSH = -HMAC-MD5* -UMAC-64* -UMAC-128* -HMAC-SHA1*
    EOF

    expected_crypto_policy="${expected_crypto_policy}:NO-WEAKMAC"
    cat << 'EOF' > /etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod
    mac = -*-128*
    EOF

    current_crypto_policy=$(update-crypto-policies --show)

    if [[ "$current_crypto_policy" != "$expected_crypto_policy" ]] ; then
        update-crypto-policies --set "$expected_crypto_policy"
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | configure
    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Set the base crypto
        policy
      ansible.builtin.set_fact:
        expected_crypto_policy: DEFAULT
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Create custom crypto
        policy module NO-SSHCBC
      ansible.builtin.lineinfile:
        path: /etc/crypto-policies/policies/modules/NO-SSHCBC.pmod
        owner: root
        group: root
        mode: '0644'
        line: cipher@SSH = -*-CBC
        create: true
        regexp: cipher@SSH
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Update the expected
        policy
      ansible.builtin.set_fact:
        expected_crypto_policy: '{{ expected_crypto_policy + '':NO-SSHCBC'' }}'
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Create custom crypto
        policy module NO-SSHWEAKCIPHERS
      ansible.builtin.lineinfile:
        path: /etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod
        owner: root
        group: root
        mode: '0644'
        line: cipher@SSH = -3DES-CBC -AES-128-CBC -AES-192-CBC -AES-256-CBC
        create: true
        regexp: cipher@SSH
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Update the expected
        policy
      ansible.builtin.set_fact:
        expected_crypto_policy: '{{ expected_crypto_policy + '':NO-SSHWEAKCIPHERS'' }}'
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Create custom crypto
        policy module NO-SSHWEAKMACS
      ansible.builtin.lineinfile:
        path: /etc/crypto-policies/policies/modules/NO-SSHWEAKMACS.pmod
        owner: root
        group: root
        mode: '0644'
        line: mac@SSH = -HMAC-MD5* -UMAC-64* -UMAC-128* -HMAC-SHA1*
        create: true
        regexp: mac@SSH
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Update the expected
        policy
      ansible.builtin.set_fact:
        expected_crypto_policy: '{{ expected_crypto_policy + '':NO-SSHWEAKMACS'' }}'
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Create custom crypto
        policy module NO-WEAKMAC
      ansible.builtin.lineinfile:
        path: /etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod
        owner: root
        group: root
        mode: '0644'
        line: mac = -*-128*
        create: true
        regexp: mac
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Update the expected
        policy
      ansible.builtin.set_fact:
        expected_crypto_policy: '{{ expected_crypto_policy + '':NO-WEAKMAC'' }}'
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Check current crypto
        policy
      ansible.builtin.command: update-crypto-policies --show
      register: current_crypto_policy
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required

    - name: Implement Custom Crypto Policy Modules for CIS Benchmark - Update crypto-policies
      ansible.builtin.command: update-crypto-policies --set {{ expected_crypto_policy
        }}
      when: current_crypto_policy.stdout.strip() != expected_crypto_policy
      tags:
      - configure_custom_crypto_policy_cis
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - reboot_required
Group Disk Partitioning Group contains 2 rules
[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 /dev/shm is configured [ref]
The `/dev/shm` is a traditional shared memory concept. One program will create a memory portion, which other processes (if permitted) can access. If `/dev/shm` is not configured, tmpfs will be mounted to /dev/shm by systemd. Warning: This rule does not have a remediation. It is expected that this will be managed by systemd and will be a tmpfs partition.
Rationale: | Any user can upload and execute files inside the `/dev/shm` similar to the `/tmp` partition. Configuring `/dev/shm` allows an administrator to set the noexec option on the mount, making /dev/shm useless for an attacker to install executable code. It would also prevent an attacker from establishing a hardlink to a system setuid program and wait for it to be updated. Once the program was updated, the hardlink would be broken and the attacker would have his own copy of the program. If the program happened to have a security vulnerability, the attacker could continue to exploit the known flaw.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_dev_shm
References: |
cis | 1.1.2.2.1
#### Rule Ensure /tmp Located On Separate Partition [ref]
The `/tmp` directory is a world-writable directory used for temporary file storage. Ensure it has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale: | The `/tmp` partition is used as temporary storage by many programs. Placing `/tmp` in its own partition enables the setting of more restrictive mount options, which can help protect programs which use it.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_partition_for_tmp
References: |
cis-csc | 12, 15, 8
cobit5 | APO13.01, DSS05.02
isa-62443-2013 | 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.13.1.1, A.13.2.1, A.14.1.3
nist | CM-6(a), SC-5(2)
nist-csf | PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.1.A6
cis | 1.1.2.1.1
Remediation OSBuild Blueprint snippet ⇲
    [[customizations.filesystem]]
    mountpoint = "/tmp"
    size = 1073741824
Remediation script ⇲
    logvol /tmp 1024
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | enable
    part /tmp
Group GNOME Desktop Environment Group contains 3 groups and 7 rules
[ref] GNOME is a graphical desktop environment bundled with many Linux distributions that allow users to easily interact with the operating system graphically rather than textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user switching contexts as well as display server management. GNOME is developed by the GNOME Project and is considered the default Red Hat Graphical environment. For more information on GNOME and the GNOME Project, see https://www.gnome.org.
Group Configure GNOME Login Screen Group contains 1 rule
[ref] In the default GNOME desktop, the login is displayed after system boot and can display user accounts, allow users to reboot the system, and allow users to login automatically and/or with a guest account. The login screen should be configured to prevent such behavior. For more information about enforcing preferences in the GNOME3 environment using the DConf configuration system, see https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/desktop_migration_and_administration_guide/> and the man page `dconf(1)`.
#### Rule Disable the GNOME3 Login User List [ref]
In the default graphical environment, users logging directly into the system are greeted with a login screen that displays all known users. This functionality should be disabled by setting `disable-user-list` to `true`. To disable, add or edit `disable-user-list` to `/etc/dconf/db/distro.d/00-security-settings`. For example:
    [org/gnome/login-screen]
    disable-user-list=true
Once the setting has been added, add a lock to `/etc/dconf/db/distro.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/login-screen/disable-user-list
After the settings have been set, run `dconf update`.
Rationale: | Leaving the user list enabled is a security risk since it allows anyone with physical access to the system to quickly enumerate known user accounts without logging in.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_disable_user_list
References: |
nist | CM-6(a), AC-23
os-srg | SRG-OS-000480-GPOS-00227
cis | 1.8.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*disable-user-list\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)disable-user-list(\s*=)/#\1disable-user-list\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    mkdir -p "${LOCKSFOLDER}"

    # Comment out the configurations in databases different from the target one
    if [[ ! -z "${LOCKFILES}" ]]
    then
        sed -i -E "s|^/org/gnome/login-screen/disable-user-list$|#&|" "${LOCKFILES[@]}"
    fi

    if ! grep -qr "^/org/gnome/login-screen/disable-user-list$" /etc/dconf/db/distro.d/
    then
        echo "/org/gnome/login-screen/disable-user-list" >> "/etc/dconf/db/distro.d/locks/00-security-settings-lock"
    fi
    dconf update

    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:
      - NIST-800-53-AC-23
      - NIST-800-53-CM-6(a)
      - dconf_gnome_disable_user_list
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Disable the GNOME3 Login User List
      community.general.ini_file:
        dest: /etc/dconf/db/distro.d/00-security-settings
        section: org/gnome/login-screen
        option: disable-user-list
        value: 'true'
        no_extra_spaces: true
        create: true
      register: result_ini
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-23
      - NIST-800-53-CM-6(a)
      - dconf_gnome_disable_user_list
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Prevent user modification of GNOME3 disablement of Login User List
      ansible.builtin.lineinfile:
        path: /etc/dconf/db/distro.d/locks/00-security-settings-lock
        regexp: ^/org/gnome/login-screen/disable-user-list$
        line: /org/gnome/login-screen/disable-user-list
        create: true
      register: result_lineinfile
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-23
      - NIST-800-53-CM-6(a)
      - dconf_gnome_disable_user_list
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed or result_lineinfile is changed
      tags:
      - NIST-800-53-AC-23
      - NIST-800-53-CM-6(a)
      - dconf_gnome_disable_user_list
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
Group GNOME Media Settings Group contains 1 rule
[ref] GNOME media settings that apply to the graphical interface.
#### Rule Disable GNOME3 Automount running [ref]
The system's default desktop environment, GNOME3, will mount devices and removable media (such as DVDs, CDs and USB flash drives) whenever they are inserted into the system. To disable autorun-never within GNOME3, add or set `autorun-never` to `true` in `/etc/dconf/db/local.d/00-security-settings`. For example:
    [org/gnome/desktop/media-handling]
    autorun-never=true
Once the settings have been added, add a lock to `/etc/dconf/db/local.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/desktop/media-handling/autorun-never
After the settings have been set, run `dconf update`.
Rationale: | Automatically mounting file systems permits easy introduction of unknown devices, thereby facilitating malicious activity. Disabling automatic mount running in GNOME3 can prevent the introduction of malware via removable media. It will, however, also prevent desktop users from legitimate use of removable media.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_disable_autorun
References: |
cis-csc | 12, 16
cobit5 | APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03
cui | 3.1.7
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.2, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.13, SR 1.2, SR 1.4, SR 1.5, SR 1.9, SR 2.1, SR 2.6
iso27001-2013 | A.11.2.6, A.13.1.1, A.13.2.1, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-3, PR.AC-6
os-srg | SRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
cis | 1.8.5
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*autorun-never\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)autorun-never(\s*=)/#\1autorun-never\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    mkdir -p "${LOCKSFOLDER}"

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

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

    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:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - dconf_gnome_disable_autorun
      - low_complexity
      - low_severity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Disable GNOME3 Automounting - autorun-never
      community.general.ini_file:
        dest: /etc/dconf/db/local.d/00-security-settings
        section: org/gnome/desktop/media-handling
        option: autorun-never
        value: 'true'
        create: true
        no_extra_spaces: true
      register: result_ini
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - dconf_gnome_disable_autorun
      - low_complexity
      - low_severity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Prevent user modification of GNOME3 Automounting - autorun-never
      ansible.builtin.lineinfile:
        path: /etc/dconf/db/local.d/locks/00-security-settings-lock
        regexp: ^/org/gnome/desktop/media-handling/autorun-never$
        line: /org/gnome/desktop/media-handling/autorun-never
        create: true
      register: result_lineinfile
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - dconf_gnome_disable_autorun
      - low_complexity
      - low_severity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed or result_lineinfile is changed
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - dconf_gnome_disable_autorun
      - low_complexity
      - low_severity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy
Group Configure GNOME Screen Locking Group contains 4 rules
[ref] In the default GNOME3 desktop, the screen can be locked by selecting the user name in the far right corner of the main panel and selecting Lock. The following sections detail commands to enforce idle activation of the screensaver, screen locking, a blank-screen screensaver, and an idle activation time. Because users should be trained to lock the screen when they step away from the computer, the automatic locking feature is only meant as a backup. The root account can be screen-locked; however, the root account should never be used to log into an X Windows environment and should only be used to for direct login via console in emergency circumstances. For more information about enforcing preferences in the GNOME3 environment using the DConf configuration system, see http://wiki.gnome.org/dconf and the man page `dconf(1)`.
#### Rule Set GNOME3 Screensaver Inactivity Timeout [ref]
The idle time-out value for inactivity in the GNOME3 desktop is configured via the `idle-delay` setting must be set under an appropriate configuration file(s) in the `/etc/dconf/db/local.d` directory and locked in `/etc/dconf/db/local.d/locks` directory to prevent user modification. For example, to configure the system for a 15 minute delay, add the following to `/etc/dconf/db/local.d/00-security-settings`:
    [org/gnome/desktop/session]
    idle-delay=uint32 900
Rationale: | A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME3 can be configured to identify when a user's session has idled and take action to initiate a session lock.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_idle_delay
References: |
cis-csc | 1, 12, 15, 16
cjis | 5.5.5
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.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 | AC-11(a), CM-6(a)
nist-csf | PR.AC-7
pcidss | Req-8.1.8
os-srg | SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
pcidss4 | 8.2.8, 8.2
cis | 1.8.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

    inactivity_timeout_value='900'

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*idle-delay\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)idle-delay(\s*=)/#\1idle-delay\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.5.5
      - NIST-800-171-3.1.10
      - NIST-800-53-AC-11(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_screensaver_idle_delay
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
    - name: XCCDF Value inactivity_timeout_value # promote to variable
      set_fact:
        inactivity_timeout_value: !!str 900
      tags:
        - always

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

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed
      tags:
      - CJIS-5.5.5
      - NIST-800-171-3.1.10
      - NIST-800-53-AC-11(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_screensaver_idle_delay
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Set GNOME3 Screensaver Lock Delay After Activation Period [ref]
To activate the locking delay of the screensaver in the GNOME3 desktop when the screensaver is activated, add or set `lock-delay` to `uint32 5` in `/etc/dconf/db/local.d/00-security-settings`. For example:
    [org/gnome/desktop/screensaver]
    lock-delay=uint32 5
After the settings have been set, run `dconf update`.
Rationale: | A session lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not want to logout because of the temporary nature of the absence.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_delay
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.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 | AC-11(a), CM-6(a)
nist-csf | PR.AC-7
pcidss | Req-8.1.8
os-srg | SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
pcidss4 | 8.2.8, 8.2
cis | 1.8.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

    # apply fix for enable_dconf_user_profile, OVAL checks it

    var_screensaver_lock_delay='5'

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*lock-delay\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)lock-delay(\s*=)/#\1lock-delay\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-AC-11(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_screensaver_lock_delay
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
    - name: XCCDF Value var_screensaver_lock_delay # promote to variable
      set_fact:
        var_screensaver_lock_delay: !!str 5
      tags:
        - always

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

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-AC-11(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_screensaver_lock_delay
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Ensure Users Cannot Change GNOME3 Screensaver Settings [ref]
If not already configured, ensure that users cannot change GNOME3 screensaver lock settings by adding `/org/gnome/desktop/screensaver/lock-delay` to `/etc/dconf/db/local.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/desktop/screensaver/lock-delay
After the settings have been set, run `dconf update`.
Rationale: | A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. As such, users should not be allowed to change session settings.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_user_locks
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.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)
nist-csf | PR.AC-7
os-srg | SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
cis | 1.8.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

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

    mkdir -p "${LOCKSFOLDER}"

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-CM-6(a)
      - dconf_gnome_screensaver_user_locks
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Prevent user modification of GNOME lock-delay
      ansible.builtin.lineinfile:
        path: /etc/dconf/db/local.d/locks/00-security-settings-lock
        regexp: ^/org/gnome/desktop/screensaver/lock-delay$
        line: /org/gnome/desktop/screensaver/lock-delay
        create: true
      register: result_lineinfile
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-CM-6(a)
      - dconf_gnome_screensaver_user_locks
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_lineinfile is changed
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-CM-6(a)
      - dconf_gnome_screensaver_user_locks
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Ensure Users Cannot Change GNOME3 Session Idle Settings [ref]
If not already configured, ensure that users cannot change GNOME3 session idle settings by adding `/org/gnome/desktop/session/idle-delay` to `/etc/dconf/db/local.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/desktop/session/idle-delay
After the settings have been set, run `dconf update`.
Rationale: | A session time-out lock is a temporary action taken when a user stops work and moves away from the immediate physical vicinity of the information system but does not logout because of the temporary nature of the absence. Rather than relying on the user to manually lock their operating system session prior to vacating the vicinity, GNOME desktops can be configured to identify when a user's session has idled and take action to initiate the session lock. As such, users should not be allowed to change session settings.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_session_idle_user_locks
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.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)
nist-csf | PR.AC-7
pcidss | Req-8.1.8
os-srg | SRG-OS-000029-GPOS-00010, SRG-OS-000031-GPOS-00012
pcidss4 | 8.2.8, 8.2
cis | 1.8.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

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

    mkdir -p "${LOCKSFOLDER}"

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | unknown
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_session_idle_user_locks
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

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

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_lineinfile is changed
      tags:
      - NIST-800-171-3.1.10
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_gnome_session_idle_user_locks
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Make sure that the dconf databases are up-to-date with regards to respective keyfiles [ref]
By default, DConf uses a binary database as a data backend. The system-level database is compiled from keyfiles in the /etc/dconf/db/ directory by the
    dconf update
command. More specifically, content present in the following directories:
    /etc/dconf/db/distro.d
    /etc/dconf/db/local.d
Rationale: | Unlike text-based keyfiles, the binary database is impossible to check by OVAL. Therefore, in order to evaluate dconf configuration, both have to be true at the same time - configuration files have to be compliant, and the database needs to be more recent than those keyfiles, which gives confidence that it reflects them.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_db_up_to_date
References: |
hipaa | 164.308(a)(1)(ii)(B), 164.308(a)(5)(ii)(A)
pcidss | Req-6.2
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 8.2.8, 8.2
cis | reload_dconf_db
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm && { rpm --quiet -q kernel-core; }; then

    dconf update

    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:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Get database modification time for distro
      ansible.builtin.stat:
        path: /etc/dconf/db/distro
      register: distro_db
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Get keyfiles for distro
      ansible.builtin.find:
        paths: /etc/dconf/db/distro.d/
      register: distro_keyfiles
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Run dconf update for distro
      ansible.builtin.command:
        cmd: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      - not distro_db.stat.exists or distro_keyfiles.files | length > 0 and distro_keyfiles.files
        | map(attribute='mtime') | max > distro_db.stat.mtime
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Get database modification time for local
      ansible.builtin.stat:
        path: /etc/dconf/db/local
      register: local_db
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Get keyfiles for local
      ansible.builtin.find:
        paths: /etc/dconf/db/local.d/
      register: local_keyfiles
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy

    - name: Make sure that the dconf databases are up-to-date with regards to respective
        keyfiles - Run dconf update for local
      ansible.builtin.command:
        cmd: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - '"kernel-core" in ansible_facts.packages'
      - not local_db.stat.exists or local_keyfiles.files | length > 0 and local_keyfiles.files
        | map(attribute='mtime') | max > local_db.stat.mtime
      tags:
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - dconf_db_up_to_date
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
      - unknown_strategy
Group Sudo Group contains 5 rules
[ref] `Sudo`, which stands for "su 'do'", provides the ability to delegate authority to certain users, groups of users, or system administrators. When configured for system users and/or groups, `Sudo` can allow a user or group to execute privileged commands that normally only `root` is allowed to execute. For more information on `Sudo` and addition `Sudo` configuration options, see https://www.sudo.ws.
#### Rule Install sudo Package [ref]
The `sudo` package can be installed with the following command:
    $ sudo dnf install sudo
Rationale: | `sudo` is a program designed to allow a system administrator to give limited root privileges to users and log root activity. The basic philosophy is to give as few privileges as possible but still allow system users to get their work done.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_sudo_installed
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
cis | 5.2.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install sudo
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-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install sudo
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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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',
      }
    }
#### Rule Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_pty [ref]
The sudo `use_pty` tag, when specified, will only execute sudo commands from users logged in to a real tty. This should be enabled by making sure that the `use_pty` tag exists in `/etc/sudoers` configuration file or any sudo configuration snippets in `/etc/sudoers.d/`.
Rationale: | Requiring that sudo commands be run in a pseudo-terminal can prevent an attacker from retaining access to the user's terminal after the main program has finished executing.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sudo_add_use_pty
References: |
pcidss | Req-10.2.5
anssi | R39
pcidss4 | 2.2.6, 2.2
cis | 5.2.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q sudo; }; then

    if /usr/sbin/visudo -qcf /etc/sudoers; then
        cp /etc/sudoers /etc/sudoers.bak
        if ! grep -P '^[\s]*Defaults\b[^!\n]*\buse_pty.*$' /etc/sudoers; then
            # sudoers file doesn't define Option use_pty
            echo "Defaults use_pty" >> /etc/sudoers
        fi

        # Check validity of sudoers and cleanup bak
        if /usr/sbin/visudo -qcf /etc/sudoers; then
            rm -f /etc/sudoers.bak
        else
            echo "Fail to validate remediated /etc/sudoers, reverting to original file."
            mv /etc/sudoers.bak /etc/sudoers
            false
        fi
    else
        echo "Skipping remediation, /etc/sudoers failed to validate"
        false
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSS-Req-10.2.5
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_add_use_pty

    - name: Ensure use_pty is enabled in /etc/sudoers
      ansible.builtin.lineinfile:
        path: /etc/sudoers
        regexp: ^[\s]*Defaults.*\buse_pty\b.*$
        line: Defaults use_pty
        validate: /usr/sbin/visudo -cf %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"sudo" in ansible_facts.packages'
      tags:
      - PCI-DSS-Req-10.2.5
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_add_use_pty
#### Rule Ensure Sudo Logfile Exists - sudo logfile [ref]
A custom log sudo file can be configured with the 'logfile' tag. This rule configures a sudo custom logfile at the default location suggested by CIS, which uses /var/log/sudo.log.
Rationale: | A sudo log file simplifies auditing of sudo commands.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_sudo_custom_logfile
References: |
pcidss | Req-10.2.5
pcidss4 | 2.2.6, 2.2
cis | 5.2.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q sudo; }; then

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

    if /usr/sbin/visudo -qcf /etc/sudoers; then
        cp /etc/sudoers /etc/sudoers.bak
        if ! grep -P '^[\s]*Defaults\b[^!\n]*\blogfile\s*=\s*(?:"?([^",\s]+)"?).*$' /etc/sudoers; then
            # sudoers file doesn't define Option logfile
            echo "Defaults logfile=${var_sudo_logfile}" >> /etc/sudoers
        else
            # sudoers file defines Option logfile, remediate if appropriate value is not set
            if ! grep -P "^[\s]*Defaults.*\blogfile=${var_sudo_logfile}\b.*$" /etc/sudoers; then

                escaped_variable=${var_sudo_logfile//$'/'/$'\/'}
                sed -Ei "s/(^[\s]*Defaults.*\blogfile=)[-]?.+(\b.*$)/\1$escaped_variable\2/" /etc/sudoers
            fi
        fi

        # Check validity of sudoers and cleanup bak
        if /usr/sbin/visudo -qcf /etc/sudoers; then
            rm -f /etc/sudoers.bak
        else
            echo "Fail to validate remediated /etc/sudoers, reverting to original file."
            mv /etc/sudoers.bak /etc/sudoers
            false
        fi
    else
        echo "Skipping remediation, /etc/sudoers failed to validate"
        false
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSS-Req-10.2.5
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_custom_logfile
    - name: XCCDF Value var_sudo_logfile # promote to variable
      set_fact:
        var_sudo_logfile: !!str /var/log/sudo.log
      tags:
        - always

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

    - name: Enable logfile option with appropriate value in /etc/sudoers
      ansible.builtin.lineinfile:
        path: /etc/sudoers
        line: Defaults logfile={{ var_sudo_logfile }}
        validate: /usr/sbin/visudo -cf %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"sudo" in ansible_facts.packages'
      - edit_sudoers_logfile_option is defined and not edit_sudoers_logfile_option.changed
      tags:
      - PCI-DSS-Req-10.2.5
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_custom_logfile
#### Rule Ensure Users Re-Authenticate for Privilege Escalation - sudo !authenticate [ref]
The sudo `!authenticate` option, when specified, allows a user to execute commands using sudo without having to authenticate. This should be disabled by making sure that the `!authenticate` option does not exist in `/etc/sudoers` configuration file or any sudo configuration snippets in `/etc/sudoers.d/`.
Rationale: | Without re-authentication, users may access resources or perform tasks for which they do not have authorization. When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sudo_remove_no_authenticate
References: |
cis-csc | 1, 12, 15, 16, 5
cobit5 | DSS05.04, DSS05.10, DSS06.03, DSS06.10
isa-62443-2009 | 4.3.3.5.1, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-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
iso27001-2013 | A.18.1.4, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nist | IA-11, CM-6(a)
nist-csf | PR.AC-1, PR.AC-7
os-srg | SRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158
ism | 1546
cis | 5.2.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    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-CM-6(a)
      - NIST-800-53-IA-11
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_remove_no_authenticate

    - name: Find /etc/sudoers.d/ files
      ansible.builtin.find:
        paths:
        - /etc/sudoers.d/
      register: sudoers
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-11
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_remove_no_authenticate

    - name: Remove lines containing !authenticate from sudoers files
      ansible.builtin.replace:
        regexp: (^(?!#).*[\s]+\!authenticate.*$)
        replace: '# \g<1>'
        path: '{{ item.path }}'
        validate: /usr/sbin/visudo -cf %s
      with_items:
      - path: /etc/sudoers
      - '{{ sudoers.files }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-11
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_remove_no_authenticate
#### Rule Require Re-Authentication When Using the sudo Command [ref]
The sudo `timestamp_timeout` tag sets the amount of time sudo password prompt waits. The default `timestamp_timeout` value is 5 minutes. The timestamp_timeout should be configured by making sure that the `timestamp_timeout` tag exists in `/etc/sudoers` configuration file or any sudo configuration snippets in `/etc/sudoers.d/`. If the value is set to an integer less than 0, the user's time stamp will not expire and the user will not have to re-authenticate for privileged actions until the user's session is terminated.
Rationale: | Without re-authentication, users may access resources or perform tasks for which they do not have authorization. When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sudo_require_reauthentication
References: |
nist | IA-11
os-srg | SRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158
pcidss4 | 2.2.6, 2.2
cis | 5.2.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q sudo; }; then

    var_sudo_timestamp_timeout='15'

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

    if /usr/sbin/visudo -qcf /etc/sudoers; then
        cp /etc/sudoers /etc/sudoers.bak
        if ! grep -P '^[\s]*Defaults.*timestamp_timeout[\s]*=[\s]*[-]?\w+.*$' /etc/sudoers; then
            # sudoers file doesn't define Option timestamp_timeout
            echo "Defaults timestamp_timeout=${var_sudo_timestamp_timeout}" >> /etc/sudoers
        else
            # sudoers file defines Option timestamp_timeout, remediate wrong values if present
            if grep -qP "^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!${var_sudo_timestamp_timeout}\b)[-]?\w+\b.*$" /etc/sudoers; then
                sed -Ei "s/(^[[:blank:]]*Defaults.*timestamp_timeout[[:blank:]]*=)[[:blank:]]*[-]?\w+(.*$)/\1${var_sudo_timestamp_timeout}\2/" /etc/sudoers
            fi
        fi

        # Check validity of sudoers and cleanup bak
        if /usr/sbin/visudo -qcf /etc/sudoers; then
            rm -f /etc/sudoers.bak
        else
            echo "Fail to validate remediated /etc/sudoers, reverting to original file."
            mv /etc/sudoers.bak /etc/sudoers
            false
        fi
    else
        echo "Skipping remediation, /etc/sudoers failed to validate"
        false
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-IA-11
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_require_reauthentication
    - name: XCCDF Value var_sudo_timestamp_timeout # promote to variable
      set_fact:
        var_sudo_timestamp_timeout: !!str 15
      tags:
        - always

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

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

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

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

    - name: Require Re-Authentication When Using the sudo Command - Remove timestamp_timeout
        wrong values in /etc/sudoers
      ansible.builtin.lineinfile:
        path: /etc/sudoers
        regexp: ^[\s]*Defaults\s.*\btimestamp_timeout[\s]*=[\s]*(?!{{ var_sudo_timestamp_timeout
          }}\b)[-]?\w+\b.*$
        state: absent
        validate: /usr/sbin/visudo -cf %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"sudo" in ansible_facts.packages'
      tags:
      - NIST-800-53-IA-11
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sudo_require_reauthentication
Group Updating Software Group contains 1 rule
[ref] The `dnf` command line tool is used to install and update software packages. The system also provides a graphical software update tool in the System menu, in the Administration submenu, called Software Update. Red Hat Enterprise Linux 10 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `dnf` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.
#### Rule Ensure gpgcheck Enabled In Main dnf Configuration [ref]
The `gpgcheck` option controls whether RPM packages' signatures are always checked prior to installation. To configure dnf to check package signatures before installing them, ensure the following line appears in `/etc/dnf/dnf.conf` in the `[main]` section:
    gpgcheck=1
Rationale: | Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization. Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
References: |
cis-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
cis | 1.2.1.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q dnf; then

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

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^gpgcheck\\>" "/etc/dnf/dnf.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^gpgcheck\\>.*/$escaped_formatted_output/gi" "/etc/dnf/dnf.conf"
    else
        if [[ -s "/etc/dnf/dnf.conf" ]] && [[ -n "$(tail -c 1 -- "/etc/dnf/dnf.conf" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/dnf/dnf.conf"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/dnf/dnf.conf" >> "/etc/dnf/dnf.conf"
        printf '%s\n' "$formatted_output" >> "/etc/dnf/dnf.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
      - 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/dnf/dnf.conf
        section: main
        option: gpgcheck
        value: 1
        no_extra_spaces: true
        create: false
      when: '"dnf" in ansible_facts.packages'
      tags:
      - CJIS-5.10.4.1
      - NIST-800-171-3.4.8
      - NIST-800-53-CM-11(a)
      - NIST-800-53-CM-11(b)
      - NIST-800-53-CM-5(3)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SA-12
      - NIST-800-53-SA-12(10)
      - NIST-800-53-SC-12
      - NIST-800-53-SC-12(3)
      - NIST-800-53-SI-7
      - PCI-DSS-Req-6.2
      - PCI-DSSv4-6.3
      - PCI-DSSv4-6.3.3
      - configure_strategy
      - ensure_gpgcheck_globally_activated
      - high_severity
      - low_complexity
      - medium_disruption
      - no_reboot_needed
Group Account and Access Control Group contains 15 groups and 80 rules
[ref] In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Red Hat Enterprise Linux 10.
Group Warning Banners for System Accesses Group contains 1 group and 14 rules
[ref] Each system should expose as little information about itself as possible. System banners, which are typically displayed just before a login prompt, give out information about the service or the host's operating system. This might include the distribution name and the system kernel version, and the particular version of a network service. This information can assist intruders in gaining access to the system as it can reveal whether the system is running vulnerable software. Most network services can be configured to limit what information is displayed. Many organizations implement security policies that require a system banner provide notice of the system's ownership, provide warning to unauthorized users, and remind authorized users of their consent to monitoring.
Group Implement a GUI Warning Banner Group contains 2 rules
[ref] In the default graphical environment, users logging directly into the system are greeted with a login screen provided by the GNOME Display Manager (GDM). The warning banner should be displayed in this graphical environment for these users. The following sections describe how to configure the GDM login banner.
#### Rule Enable GNOME3 Login Warning Banner [ref]
In the default graphical environment, displaying a login warning banner in the GNOME Display Manager's login screen can be enabled on the login screen by setting `banner-message-enable` to `true`. To enable, add or edit `banner-message-enable` to `/etc/dconf/db/distro.d/00-security-settings`. For example:
    [org/gnome/login-screen]
    banner-message-enable=true
Once the setting has been added, add a lock to `/etc/dconf/db/distro.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/login-screen/banner-message-enable
After the settings have been set, run `dconf update`. The banner text must also be set.
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. For U.S. Government systems, system use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_banner_enabled
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.9
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(b), AC-8(c)
nist-csf | PR.AC-7
os-srg | SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis | 1.8.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*banner-message-enable\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)banner-message-enable(\s*=)/#\1banner-message-enable\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    mkdir -p "${LOCKSFOLDER}"

    # Comment out the configurations in databases different from the target one
    if [[ ! -z "${LOCKFILES}" ]]
    then
        sed -i -E "s|^/org/gnome/login-screen/banner-message-enable$|#&|" "${LOCKFILES[@]}"
    fi

    if ! grep -qr "^/org/gnome/login-screen/banner-message-enable$" /etc/dconf/db/distro.d/
    then
        echo "/org/gnome/login-screen/banner-message-enable" >> "/etc/dconf/db/distro.d/locks/00-security-settings-lock"
    fi
    dconf update

    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:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(b)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_banner_enabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Enable GNOME3 Login Warning Banner
      community.general.ini_file:
        dest: /etc/dconf/db/distro.d/00-security-settings
        section: org/gnome/login-screen
        option: banner-message-enable
        value: 'true'
        create: true
        no_extra_spaces: true
      register: result_ini
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(b)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_banner_enabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Prevent user modification of GNOME banner-message-enabled
      ansible.builtin.lineinfile:
        path: /etc/dconf/db/distro.d/locks/00-security-settings-lock
        regexp: ^/org/gnome/login-screen/banner-message-enable$
        line: /org/gnome/login-screen/banner-message-enable
        create: true
      register: result_lineinfile
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(b)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_banner_enabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed or result_lineinfile is changed
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(b)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_banner_enabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Set the GNOME3 Login Warning Banner Text [ref]
In the default graphical environment, configuring the login warning banner text in the GNOME Display Manager's login screen can be configured on the login screen by setting `banner-message-text` to `'APPROVED_BANNER'` where APPROVED_BANNER is the approved banner for your environment. To enable, add or edit `banner-message-text` to `/etc/dconf/db/distro.d/00-security-settings`. For example:
    [org/gnome/login-screen]
    banner-message-text='APPROVED_BANNER'
Once the setting has been added, add a lock to `/etc/dconf/db/distro.d/locks/00-security-settings-lock` to prevent user modification. For example:
    /org/gnome/login-screen/banner-message-text
After the settings have been set, run `dconf update`. When entering a warning banner that spans several lines, remember to begin and end the string with `'` and use `\n` for new lines.
Rationale: | An appropriate warning message reinforces policy awareness during the logon process and facilitates possible legal action against attackers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dconf_gnome_login_banner_text
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.9
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)
nist-csf | PR.AC-7
os-srg | SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis | 1.8.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q gdm; then

    login_banner_text='^(Authorized[\s\n]+users[\s\n]+only\.[\s\n]+All[\s\n]+activity[\s\n]+may[\s\n]+be[\s\n]+monitored[\s\n]+and[\s\n]+reported\.|^(?!.*(\\|fedora|rhel|sle|ubuntu)).*)$'

    # Multiple regexes transform the banner regex into a usable banner
    # 0 - Remove anchors around the banner text
    login_banner_text=$(echo "$login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
    # 1 - Keep only the first banners if there are multiple
    #    (dod_banners contains the long and short banner)
    login_banner_text=$(echo "$login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
    # 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
    login_banner_text=$(echo "$login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
    # 3 - Adds newline "tokens". (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "(n)*")
    login_banner_text=$(echo "$login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/(n)*/g')
    # 4 - Remove any leftover backslash. (From any parenthesis in the banner, for example).
    login_banner_text=$(echo "$login_banner_text" | sed 's/\\//g')
    # 5 - Removes the newline "token." (Transforms them into newline escape sequences "\n").
    #    ( Needs to be done after 4, otherwise the escapce sequence will become just "n".
    login_banner_text=$(echo "$login_banner_text" | sed 's/(n)\*/\\n/g')

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

    mkdir -p "${DBDIR}"

    # Comment out the configurations in databases different from the target one
    if [ "${#SETTINGSFILES[@]}" -ne 0 ]
    then
        if grep -q "^\\s*banner-message-text\\s*=" "${SETTINGSFILES[@]}"
        then

            sed -Ei "s/(^\s*)banner-message-text(\s*=)/#\1banner-message-text\2/g" "${SETTINGSFILES[@]}"
        fi
    fi

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

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

    mkdir -p "${LOCKSFOLDER}"

    # Comment out the configurations in databases different from the target one
    if [[ ! -z "${LOCKFILES}" ]]
    then
        sed -i -E "s|^/org/gnome/login-screen/banner-message-text$|#&|" "${LOCKFILES[@]}"
    fi

    if ! grep -qr "^/org/gnome/login-screen/banner-message-text$" /etc/dconf/db/distro.d/
    then
        echo "/org/gnome/login-screen/banner-message-text" >> "/etc/dconf/db/distro.d/locks/00-security-settings-lock"
    fi
    dconf update

    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:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
    - name: XCCDF Value login_banner_text # promote to variable
      set_fact:
        login_banner_text: !!str ^(Authorized[\s\n]+users[\s\n]+only\.[\s\n]+All[\s\n]+activity[\s\n]+may[\s\n]+be[\s\n]+monitored[\s\n]+and[\s\n]+reported\.|^(?!.*(\\|fedora|rhel|sle|ubuntu)).*)$
      tags:
        - always

    - name: Set the GNOME3 Login Warning Banner Text
      ansible.builtin.file:
        path: /etc/dconf/db/{{ item }}
        owner: root
        group: root
        mode: 493
        state: directory
      with_items:
      - distro.d
      - distro.d/locks
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Set the GNOME3 Login Warning Banner Text
      ansible.builtin.file:
        path: /etc/dconf/db/distro.d/{{ item }}
        owner: root
        group: root
        mode: 420
        state: touch
      with_items:
      - 00-security-settings
      - locks/00-security-settings-lock
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Set the GNOME3 Login Warning Banner Text
      community.general.ini_file:
        dest: /etc/dconf/db/distro.d/00-security-settings
        section: org/gnome/login-screen
        option: banner-message-text
        value: '''{{ login_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
          "\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
          "(n)*") | regex_replace("\\", "") | regex_replace("\(n\)\*", "\\n") }}'''
        create: true
        no_extra_spaces: true
      register: result_ini
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Prevent user modification of the GNOME3 Login Warning Banner Text
      ansible.builtin.lineinfile:
        path: /etc/dconf/db/distro.d/locks/00-security-settings-lock
        regexp: ^/org/gnome/login-screen/banner-message-text$
        line: /org/gnome/login-screen/banner-message-text
        create: true
        state: present
      register: result_lineinfile
      when: '"gdm" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy

    - name: Dconf Update
      ansible.builtin.command: dconf update
      when:
      - '"gdm" in ansible_facts.packages'
      - result_ini is changed or result_lineinfile is changed
      tags:
      - NIST-800-171-3.1.9
      - NIST-800-53-AC-8(a)
      - NIST-800-53-AC-8(c)
      - dconf_gnome_login_banner_text
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - unknown_strategy
#### Rule Ensure Local Login Warning Banner Is Configured Properly [ref]
To configure the system local login warning banner edit the `/etc/issue` file. The contents of this file is displayed to users prior to login to local terminals. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value `xccdf_org.ssgproject.content_value_cis_banner_text`:
    Authorized users only. All activity may be monitored and reported.
Rationale: | Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the `uname -a` command once they have logged in.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_banner_etc_issue_cis
References: |
cis | 1.7.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    cis_banner_text='Authorized users only. All activity may be monitored and reported.'

    echo "$cis_banner_text" > "/etc/issue"

    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:
      - banner_etc_issue_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value cis_banner_text # promote to variable
      set_fact:
        cis_banner_text: !!str Authorized users only. All activity may be monitored and reported.
      tags:
        - always

    - name: Ensure Local Login Warning Banner Is Configured Properly - Copy using inline
        content
      ansible.builtin.copy:
        content: '{{ cis_banner_text }}'
        dest: /etc/issue
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - banner_etc_issue_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure Remote Login Warning Banner Is Configured Properly [ref]
To configure the system remote login warning banner edit the `/etc/issue.net` file. The contents of this file is displayed to users prior to login from remote connections. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value `xccdf_org.ssgproject.content_value_cis_banner_text`:
    Authorized users only. All activity may be monitored and reported.
Rationale: | Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the `uname -a` command once they have logged in.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_banner_etc_issue_net_cis
References: |
cis | 1.7.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    cis_banner_text='Authorized users only. All activity may be monitored and reported.'

    echo "$cis_banner_text" > "/etc/issue.net"

    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:
      - banner_etc_issue_net_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value cis_banner_text # promote to variable
      set_fact:
        cis_banner_text: !!str Authorized users only. All activity may be monitored and reported.
      tags:
        - always

    - name: Ensure Remote Login Warning Banner Is Configured Properly - Copy using inline
        content
      ansible.builtin.copy:
        content: '{{ cis_banner_text }}'
        dest: /etc/issue.net
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - banner_etc_issue_net_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure Message Of The Day Is Configured Properly [ref]
To configure the system message of the day banner edit the `/etc/motd` file. Replace the default text with a message compliant with the local site policy. The message should not contain information about operating system version, release, kernel version or patch level. The recommended banner text can be tailored in the XCCDF Value `xccdf_org.ssgproject.content_value_cis_banner_text`:
    Authorized users only. All activity may be monitored and reported.
Rationale: | Warning messages inform users who are attempting to login to the system of their legal status regarding the system and must include the name of the organization that owns the system and any monitoring policies that are in place. Displaying OS and patch level information in login banners also has the side effect of providing detailed system information to attackers attempting to target specific exploits of a system. Authorized users can easily get this information by running the `uname -a` command once they have logged in.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_banner_etc_motd_cis
References: |
cis | 1.7.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    cis_banner_text='Authorized users only. All activity may be monitored and reported.'

    echo "$cis_banner_text" > "/etc/motd"

    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:
      - banner_etc_motd_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value cis_banner_text # promote to variable
      set_fact:
        cis_banner_text: !!str Authorized users only. All activity may be monitored and reported.
      tags:
        - always

    - name: Ensure Message Of The Day Is Configured Properly - Copy using inline content
      ansible.builtin.copy:
        content: '{{ cis_banner_text }}'
        dest: /etc/motd
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - banner_etc_motd_cis
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Verify Group Ownership of System Login Banner [ref]
To properly set the group owner of `/etc/issue`, run the command:
    $ sudo chgrp root /etc/issue
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper group ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_issue
References: |
bsi | SYS.1.3.A14
cis | 1.7.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/issue" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/issue
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_issue_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_issue_newgroup: '0'
      tags:
      - configure_strategy
      - file_groupowner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/issue
      ansible.builtin.stat:
        path: /etc/issue
      register: file_exists
      tags:
      - configure_strategy
      - file_groupowner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/issue
      ansible.builtin.file:
        path: /etc/issue
        follow: false
        group: '{{ file_groupowner_etc_issue_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_groupowner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Ownership of System Login Banner for Remote Connections [ref]
To properly set the group owner of `/etc/issue.net`, run the command:
    $ sudo chgrp root /etc/issue.net
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper group ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_issue_net
References: |
pcidss4 | 1.2.8, 1.2
bsi | SYS.1.3.A14
cis | 1.7.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/issue.net" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/issue.net
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_issue_net_newgroup variable if represented by
        gid
      ansible.builtin.set_fact:
        file_groupowner_etc_issue_net_newgroup: '0'
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_groupowner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/issue.net
      ansible.builtin.stat:
        path: /etc/issue.net
      register: file_exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_groupowner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/issue.net
      ansible.builtin.file:
        path: /etc/issue.net
        follow: false
        group: '{{ file_groupowner_etc_issue_net_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_groupowner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Ownership of Message of the Day Banner [ref]
To properly set the group owner of `/etc/motd`, run the command:
    $ sudo chgrp root /etc/motd
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper group ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_motd
References: |
bsi | SYS.1.3.A14
cis | 1.7.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/motd" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/motd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_motd_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_motd_newgroup: '0'
      tags:
      - configure_strategy
      - file_groupowner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/motd
      ansible.builtin.stat:
        path: /etc/motd
      register: file_exists
      tags:
      - configure_strategy
      - file_groupowner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/motd
      ansible.builtin.file:
        path: /etc/motd
        follow: false
        group: '{{ file_groupowner_etc_motd_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_groupowner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify ownership of System Login Banner [ref]
To properly set the owner of `/etc/issue`, run the command:
    $ sudo chown root /etc/issue
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_issue
References: |
bsi | SYS.1.3.A14
cis | 1.7.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/issue" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/issue
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_issue_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_issue_newown: '0'
      tags:
      - configure_strategy
      - file_owner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/issue
      ansible.builtin.stat:
        path: /etc/issue
      register: file_exists
      tags:
      - configure_strategy
      - file_owner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/issue
      ansible.builtin.file:
        path: /etc/issue
        follow: false
        owner: '{{ file_owner_etc_issue_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_owner_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify ownership of System Login Banner for Remote Connections [ref]
To properly set the owner of `/etc/issue.net`, run the command:
    $ sudo chown root /etc/issue.net
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_issue_net
References: |
pcidss4 | 1.2.8, 1.2
bsi | SYS.1.3.A14
cis | 1.7.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/issue.net" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/issue.net
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_issue_net_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_issue_net_newown: '0'
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_owner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/issue.net
      ansible.builtin.stat:
        path: /etc/issue.net
      register: file_exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_owner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/issue.net
      ansible.builtin.file:
        path: /etc/issue.net
        follow: false
        owner: '{{ file_owner_etc_issue_net_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_owner_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify ownership of Message of the Day Banner [ref]
To properly set the owner of `/etc/motd`, run the command:
    $ sudo chown root /etc/motd
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper ownership will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_motd
References: |
bsi | SYS.1.3.A14
cis | 1.7.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/motd" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/motd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_motd_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_motd_newown: '0'
      tags:
      - configure_strategy
      - file_owner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/motd
      ansible.builtin.stat:
        path: /etc/motd
      register: file_exists
      tags:
      - configure_strategy
      - file_owner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/motd
      ansible.builtin.file:
        path: /etc/motd
        follow: false
        owner: '{{ file_owner_etc_motd_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_owner_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify permissions on System Login Banner [ref]
To properly set the permissions of `/etc/issue`, run the command:
    $ sudo chmod 0644 /etc/issue
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper permissions will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_issue
References: |
bsi | SYS.1.3.A14
cis | 1.7.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/issue
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/issue
      ansible.builtin.stat:
        path: /etc/issue
      register: file_exists
      tags:
      - configure_strategy
      - file_permissions_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/issue
      ansible.builtin.file:
        path: /etc/issue
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_permissions_etc_issue
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify permissions on System Login Banner for Remote Connections [ref]
To properly set the permissions of `/etc/issue.net`, run the command:
    $ sudo chmod 0644 /etc/issue.net
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper permissions will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_issue_net
References: |
pcidss4 | 1.2.8, 1.2
bsi | SYS.1.3.A14
cis | 1.7.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/issue.net
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/issue.net
      ansible.builtin.stat:
        path: /etc/issue.net
      register: file_exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_permissions_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/issue.net
      ansible.builtin.file:
        path: /etc/issue.net
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.8
      - configure_strategy
      - file_permissions_etc_issue_net
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify permissions on Message of the Day Banner [ref]
To properly set the permissions of `/etc/motd`, run the command:
    $ sudo chmod 0644 /etc/motd
Rationale: | Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance. Proper permissions will ensure that only root user can modify the banner.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_motd
References: |
bsi | SYS.1.3.A14
cis | 1.7.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/motd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/motd
      ansible.builtin.stat:
        path: /etc/motd
      register: file_exists
      tags:
      - configure_strategy
      - file_permissions_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/motd
      ansible.builtin.file:
        path: /etc/motd
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_permissions_etc_motd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Group Protect Accounts by Configuring PAM Group contains 4 groups and 24 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 9 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 Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth File. [ref]
The pam_faillock.so module must be loaded in preauth in /etc/pam.d/password-auth.
Rationale: | If the pam_faillock.so module is not loaded the system will not correctly lockout accounts to prevent password guessing attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_account_password_pam_faillock_password_auth
References: |
nist | AC-7 (a)
os-srg | SRG-OS-000021-GPOS-00005
cis | 5.3.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    authselect select "$(authselect current --raw)" --force
    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

    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-AC-7 (a)
      - account_password_pam_faillock_password_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
        File. - Force reselect authselect profile
      ansible.builtin.shell:
        cmd: authselect select "$(authselect current --raw)" --force
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_password_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
        File. - Check if system relies on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_password_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
        File. - Remediation where authselect tool is present
      block:

      - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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-core" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_password_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
        File. - Remediation where authselect tool is not present
      block:

      - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/password-auth
          File. - 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-core" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_password_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth File. [ref]
The pam_faillock.so module must be loaded in preauth in /etc/pam.d/system-auth.
Rationale: | If the pam_faillock.so module is not loaded the system will not correctly lockout accounts to prevent password guessing attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_account_password_pam_faillock_system_auth
References: |
nist | AC-7 (a)
os-srg | SRG-OS-000021-GPOS-00005
cis | 5.3.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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 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

    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-AC-7 (a)
      - account_password_pam_faillock_system_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
        File. - Check if system relies on authselect tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_system_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
        File. - Remediation where authselect tool is present
      block:

      - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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-core" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_system_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
        File. - Remediation where authselect tool is not present
      block:

      - name: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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: Configure the Use of the pam_faillock.so Module in the /etc/pam.d/system-auth
          File. - 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-core" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - NIST-800-53-AC-7 (a)
      - account_password_pam_faillock_system_auth
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure Password History Is Enforced for the Root User [ref]
The `enforce_for_root` option enforces password history for the root user. Enable the `enforce_for_root` setting in `/etc/security/pwhistory.conf` to require the `root` user to use a password that has not been used recently.
Rationale: | Requiring users not to reuse their passwords make it less likely that an attacker will be able to guess the password or use a compromised password.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_enforce_for_root
References: |
cis | 5.3.2.3.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    if [ -e "/etc/security/pwhistory.conf" ] ; then

        LC_ALL=C sed -i "/^\s*enforce_for_root/Id" "/etc/security/pwhistory.conf"
    else
        touch "/etc/security/pwhistory.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/security/pwhistory.conf"

    cp "/etc/security/pwhistory.conf" "/etc/security/pwhistory.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "enforce_for_root" >> "/etc/security/pwhistory.conf"
    # Clean up after ourselves.
    rm "/etc/security/pwhistory.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:
      - accounts_password_pam_pwhistory_enforce_for_root
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Password History Is Enforced for the Root User
      ansible.builtin.lineinfile:
        path: /etc/security/pwhistory.conf
        create: true
        regexp: ''
        line: enforce_for_root
        state: present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_pwhistory_enforce_for_root
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Limit Password Reuse: password-auth [ref]
Do not allow users to reuse recent passwords. This can be accomplished by using the `remember` option for the `pam_pwhistory` PAM module. On systems with newer versions of `authselect`, the `pam_pwhistory` PAM module can be enabled via authselect feature:
    authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile. Newer systems also have the `/etc/security/pwhistory.conf` file for setting `pam_pwhistory` module options. This file should be used whenever available. Otherwise, the `pam_pwhistory` module options can be set in PAM files. The value for `remember` option must be equal or greater than 24 Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. Warning: Newer versions of `authselect` contain an authselect feature to easily and properly enable `pam_pwhistory.so` module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files. If a custom profile was created and used in the system before this authselect feature was available, the new feature can't be used with this custom profile and the remediation will fail. In this case, the custom profile should be recreated or manually updated.
Rationale: | Preventing reuse of previous passwords helps ensure that a compromised password is not reused by a user.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth
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
cui | 3.5.8
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(f), IA-5(1)(e)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.5
os-srg | SRG-OS-000077-GPOS-00045
pcidss4 | 8.3.7, 8.3
cis | 5.3.2.3.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_password_pam_remember='24'
    var_password_pam_remember_control_flag='requisite,required'

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

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

            authselect apply-changes -b
        else

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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

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

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

    fi

    PWHISTORY_CONF="/etc/security/pwhistory.conf"
    if [ -f $PWHISTORY_CONF ]; then
        regex="^\s*remember\s*="
        line="remember = $var_password_pam_remember"
        if ! grep -q $regex $PWHISTORY_CONF; then
            echo $line >> $PWHISTORY_CONF
        else
            sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
        fi
        if [ -e "/etc/pam.d/password-auth" ] ; then
            PAM_FILE_PATH="/etc/pam.d/password-auth"
            if [ -f /usr/bin/authselect ]; then

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

                CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                # If not already in use, a custom profile is created preserving the enabled features.
                if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                    # 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_pwhistory.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "s/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
        fi
            if [ -f /usr/bin/authselect ]; then

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

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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+requisite\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
            else
                echo "password    requisite    pam_pwhistory.so" >> "$PAM_FILE_PATH"
            fi
        fi
        # Check the option
        if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*\sremember\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "/\s*password\s+requisite\s+pam_pwhistory.so.*/ s/$/ remember=$var_password_pam_remember/" "$PAM_FILE_PATH"
        else
            sed -i -E --follow-symlinks "s/(\s*password\s+requisite\s+pam_pwhistory.so\s+.*)(remember=)[[:alnum:]]*\s*(.*)/\1\2$var_password_pam_remember \3/" "$PAM_FILE_PATH"
        fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-171-3.5.8
      - NIST-800-53-IA-5(1)(e)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.5
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.7
      - accounts_password_pam_pwhistory_remember_password_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
    - name: XCCDF Value var_password_pam_remember # promote to variable
      set_fact:
        var_password_pam_remember: !!str 24
      tags:
        - always
    - name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
      set_fact:
        var_password_pam_remember_control_flag: !!str requisite,required
      tags:
        - always

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
            based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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: 'Limit Password Reuse: password-auth - 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: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: 'Limit Password Reuse: password-auth - Define a fact for control already
          filtered in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: '{{ var_password_pam_remember_control_flag.split(",")[0]
            }}'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            - name: 'Limit Password Reuse: password-auth - Create an authselect custom
                profile based on the current profile'
              ansible.builtin.command:
                cmd: authselect create-profile hardening -b {{ authselect_current_profile
                  }}
              when:
              - result_authselect_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: 'Limit Password Reuse: password-auth - 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: 'Limit Password Reuse: password-auth - Ensure authselect changes are
                applied'
              ansible.builtin.command:
                cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
              when:
              - result_authselect_check_cmd is success
              - result_authselect_profile is not skipped
              - authselect_current_profile is not match("custom/")
              - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

          - name: 'Limit Password Reuse: password-auth - Define a fact for control already
              filtered in case filters are used'
            ansible.builtin.set_fact:
              pam_module_control: ''

          - name: 'Limit Password Reuse: password-auth - Check if {{ pam_file_path }}
              file is present'
            ansible.builtin.stat:
              path: '{{ pam_file_path }}'
            register: result_pam_file_present

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

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

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

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

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

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

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

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

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

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

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

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

        - name: 'Limit Password Reuse: password-auth - Create an authselect custom profile
            based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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: 'Limit Password Reuse: password-auth - 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: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: 'Limit Password Reuse: password-auth - Define a fact for control already
          filtered in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: requisite

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

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

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

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

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

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

      - name: 'Limit Password Reuse: password-auth - Define a fact for control already
          filtered in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: requisite

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

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

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

      - name: 'Limit Password Reuse: password-auth - Ensure authselect changes are applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
          (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_pwhistory_conf_check.stat.exists
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-171-3.5.8
      - NIST-800-53-IA-5(1)(e)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.5
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.7
      - accounts_password_pam_pwhistory_remember_password_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Limit Password Reuse: system-auth [ref]
Do not allow users to reuse recent passwords. This can be accomplished by using the `remember` option for the `pam_pwhistory` PAM module. On systems with newer versions of `authselect`, the `pam_pwhistory` PAM module can be enabled via authselect feature:
    authselect enable-feature with-pwhistory
Otherwise, it should be enabled using an authselect custom profile. Newer systems also have the `/etc/security/pwhistory.conf` file for setting `pam_pwhistory` module options. This file should be used whenever available. Otherwise, the `pam_pwhistory` module options can be set in PAM files. The value for `remember` option must be equal or greater than 24 Warning: If the system relies on `authselect` tool to manage PAM settings, the remediation will also use `authselect` tool. However, if any manual modification was made in PAM files, the `authselect` integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. Warning: Newer versions of `authselect` contain an authselect feature to easily and properly enable `pam_pwhistory.so` module. If this feature is not yet available in your system, an authselect custom profile must be used to avoid integrity issues in PAM files.
Rationale: | Preventing reuse of previous passwords helps ensure that a compromised password is not reused by a user.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth
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
cui | 3.5.8
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(f), IA-5(1)(e)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.5
os-srg | SRG-OS-000077-GPOS-00045
pcidss4 | 8.3.7, 8.3
cis | 5.3.2.3.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_password_pam_remember='24'
    var_password_pam_remember_control_flag='requisite,required'

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

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

            authselect apply-changes -b
        else

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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

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

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

    fi

    PWHISTORY_CONF="/etc/security/pwhistory.conf"
    if [ -f $PWHISTORY_CONF ]; then
        regex="^\s*remember\s*="
        line="remember = $var_password_pam_remember"
        if ! grep -q $regex $PWHISTORY_CONF; then
            echo $line >> $PWHISTORY_CONF
        else
            sed -i --follow-symlinks 's|^\s*\(remember\s*=\s*\)\(\S\+\)|\1'"$var_password_pam_remember"'|g' $PWHISTORY_CONF
        fi
        if [ -e "/etc/pam.d/system-auth" ] ; then
            PAM_FILE_PATH="/etc/pam.d/system-auth"
            if [ -f /usr/bin/authselect ]; then

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

                CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
                # If not already in use, a custom profile is created preserving the enabled features.
                if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                    ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                    # 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_pwhistory.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "s/(.*password.*pam_pwhistory.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
        fi
            if [ -f /usr/bin/authselect ]; then

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

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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+requisite\s+pam_pwhistory.so\s*.*" "$PAM_FILE_PATH"; then
            # Line matching group + control + module was not found. Check group + module.
            if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwhistory.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
                # The control is updated only if one single line matches.
                sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwhistory.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
            else
                echo "password    requisite    pam_pwhistory.so" >> "$PAM_FILE_PATH"
            fi
        fi
        # Check the option
        if ! grep -qP "^\s*password\s+requisite\s+pam_pwhistory.so\s*.*\sremember\b" "$PAM_FILE_PATH"; then
            sed -i -E --follow-symlinks "/\s*password\s+requisite\s+pam_pwhistory.so.*/ s/$/ remember=$var_password_pam_remember/" "$PAM_FILE_PATH"
        else
            sed -i -E --follow-symlinks "s/(\s*password\s+requisite\s+pam_pwhistory.so\s+.*)(remember=)[[:alnum:]]*\s*(.*)/\1\2$var_password_pam_remember \3/" "$PAM_FILE_PATH"
        fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-171-3.5.8
      - NIST-800-53-IA-5(1)(e)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.5
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.7
      - accounts_password_pam_pwhistory_remember_system_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
    - name: XCCDF Value var_password_pam_remember # promote to variable
      set_fact:
        var_password_pam_remember: !!str 24
      tags:
        - always
    - name: XCCDF Value var_password_pam_remember_control_flag # promote to variable
      set_fact:
        var_password_pam_remember_control_flag: !!str requisite,required
      tags:
        - always

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
            based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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: 'Limit Password Reuse: system-auth - 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: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
          in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: '{{ var_password_pam_remember_control_flag.split(",")[0]
            }}'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
                based on the current profile'
              ansible.builtin.command:
                cmd: authselect create-profile hardening -b {{ authselect_current_profile
                  }}
              when:
              - result_authselect_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: 'Limit Password Reuse: system-auth - 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: 'Limit Password Reuse: system-auth - Ensure authselect changes are
                applied'
              ansible.builtin.command:
                cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
              when:
              - result_authselect_check_cmd is success
              - result_authselect_profile is not skipped
              - authselect_current_profile is not match("custom/")
              - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

          - name: 'Limit Password Reuse: system-auth - Define a fact for control already
              filtered in case filters are used'
            ansible.builtin.set_fact:
              pam_module_control: ''

          - name: 'Limit Password Reuse: system-auth - Check if {{ pam_file_path }} file
              is present'
            ansible.builtin.stat:
              path: '{{ pam_file_path }}'
            register: result_pam_file_present

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

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

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

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

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

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

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

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

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

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

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

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

        - name: 'Limit Password Reuse: system-auth - Create an authselect custom profile
            based on the current profile'
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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: 'Limit Password Reuse: system-auth - 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: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
          in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: requisite

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

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

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

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

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

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

      - name: 'Limit Password Reuse: system-auth - Define a fact for control already filtered
          in case filters are used'
        ansible.builtin.set_fact:
          pam_module_control: requisite

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

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

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

      - name: 'Limit Password Reuse: system-auth - Ensure authselect changes are applied'
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - (result_pam_remember_add is defined and result_pam_remember_add.changed) or
          (result_pam_remember_edit is defined and result_pam_remember_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_pwhistory_conf_check.stat.exists
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-171-3.5.8
      - NIST-800-53-IA-5(1)(e)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.5
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.7
      - accounts_password_pam_pwhistory_remember_system_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Enforce Password History with use_authtok [ref]
The `use_authtok` option ensures the pam_pwhistory module uses the new password provided by a previously stacked PAM module during password changes, rather than prompting the user again.
Rationale: | The `use_authtok` option allows multiple PAM modules to validate the new password before it is accepted, ensuring it meets all security requirements without requiring the user to re-enter it multiple times.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_use_authtok
References: |
cis | 5.3.2.3.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    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_profile="$(head -1 /etc/authselect/authselect.conf)"
    if grep -Pq -- '^custom\/' <<< "$pam_profile"; then
        pam_profile_path="/etc/authselect/$pam_profile"
    else
        pam_profile_path="/usr/share/authselect/default/$pam_profile"
    fi

    for authselect_file in "$pam_profile_path"/password-auth "$pam_profile_path"/system-auth; do
        if ! grep -Pq '^\h*password\h+([^#\n\r]+)\h+pam_pwhistory\.so\h+([^#\n\r]+\h+)?use_authtok\b' "$authselect_file"; then
            sed -ri 's/(^\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so\s+.*)$/& use_authtok/g' "$authselect_file"
        fi
    done

    authselect apply-changes

    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:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Check if system relies on authselect
        tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Ensure authselect custom profile
        is used if authselect is present
      block:

      - name: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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: Enforce Password History with use_authtok - 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
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Get authselect current profile
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Define the PAM profile path based
        on the authselect profile
      ansible.builtin.set_fact:
        pam_profile_path: '{%- if result_authselect_profile.stdout is match("^custom/")
          -%} /etc/authselect/{{ result_authselect_profile.stdout }} {%- else -%} /usr/share/authselect/default/{{
          result_authselect_profile.stdout }} {%- endif -%}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Check if "use_authtok" option
        is present in pam_pwhistory.so in /password-auth
      ansible.builtin.lineinfile:
        path: '{{ pam_profile_path }}/password-auth'
        regexp: ^\s*password\s+([^#\n\r]+)\s+pam_pwhistory\.so\s+([^#\n\r]+\s+)?use_authtok\b
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_pwhistory_password_auth_option_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - pam_profile_path is defined
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Ensure "use_authtok" option is
        added to pam_pwhistory.so in /password-auth
      ansible.builtin.replace:
        path: '{{ pam_profile_path }}/password-auth'
        regexp: (^\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so\s+.*)$
        replace: \1 use_authtok
      register: result_pam_pwhistory_password_auth_add
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - pam_profile_path is defined
      - result_pam_pwhistory_password_auth_option_present.found is defined
      - result_pam_pwhistory_password_auth_option_present.found == 0
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Check if "use_authtok" option
        is present in pam_pwhistory.so in /system-auth
      ansible.builtin.lineinfile:
        path: '{{ pam_profile_path }}/system-auth'
        regexp: ^\s*password\s+([^#\n\r]+)\s+pam_pwhistory\.so\s+([^#\n\r]+\s+)?use_authtok\b
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_pwhistory_system_auth_option_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - pam_profile_path is defined
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Ensure "use_authtok" option is
        added to pam_pwhistory.so in /system-auth
      ansible.builtin.replace:
        path: '{{ pam_profile_path }}/system-auth'
        regexp: (^\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so\s+.*)$
        replace: \1 use_authtok
      register: result_pam_pwhistory_system_auth_add
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - pam_profile_path is defined
      - result_pam_pwhistory_system_auth_option_present.found is defined
      - result_pam_pwhistory_system_auth_option_present.found == 0
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Enforce Password History with use_authtok - Ensure authselect changes are
        applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - |-
        (result_pam_pwhistory_password_auth_add is defined and result_pam_pwhistory_password_auth_add.changed)
         or (result_pam_pwhistory_system_auth_add is defined and result_pam_pwhistory_system_auth_add.changed)
      tags:
      - accounts_password_pam_pwhistory_use_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Require use_authtok for pam_unix.so [ref]
When password changing enforce the module to set the new password to the one provided by a previously stacked password module
Rationale: | Require use_authtok in pam_unix.so configuration
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_authtok
References: |
cis | 5.3.2.4.4
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

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

    if ! grep -qP "^\s*password\s+sufficient\s+pam_unix.so\s*.*" "/etc/pam.d/password-auth"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_unix.so\s*' "/etc/pam.d/password-auth")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_unix.so.*)/\1sufficient \2/" "/etc/pam.d/password-auth"
        else
            echo "password    sufficient    pam_unix.so" >> "/etc/pam.d/password-auth"
        fi
    fi
    # Check the option
    if ! grep -qP "^\s*password\s+sufficient\s+pam_unix.so\s*.*\suse_authtok\b" "/etc/pam.d/password-auth"; then
        sed -i -E --follow-symlinks "/\s*password\s+sufficient\s+pam_unix.so.*/ s/$/ use_authtok/" "/etc/pam.d/password-auth"
    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:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Check if system relies on authselect
        tool
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Define a fact for control already filtered
        in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: sufficient
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Check if expected PAM module line is
        present in /etc/pam.d/system-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/system-auth
        regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Include or update the PAM module line
        in /etc/pam.d/system-auth
      block:

      - name: Require use_authtok for pam_unix.so - Check if required PAM module line
          is present in /etc/pam.d/system-auth with different control
        ansible.builtin.lineinfile:
          path: /etc/pam.d/system-auth
          regexp: ^\s*password\s+.*\s+pam_unix.so\s*
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_line_other_control_present

      - name: Require use_authtok for pam_unix.so - Ensure the correct control for the
          required PAM module line in /etc/pam.d/system-auth
        ansible.builtin.replace:
          dest: /etc/pam.d/system-auth
          regexp: ^(\s*password\s+).*(\bpam_unix.so.*)
          replace: \1{{ pam_module_control }} \2
        register: result_pam_module_edit
        when:
        - result_pam_line_other_control_present.found == 1

      - name: Require use_authtok for pam_unix.so - Ensure the required PAM module line
          is included in /etc/pam.d/system-auth
        ansible.builtin.lineinfile:
          dest: /etc/pam.d/system-auth
          line: password    {{ pam_module_control }}    pam_unix.so
        register: result_pam_module_add
        when:
        - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
          > 1

      - name: Require use_authtok for pam_unix.so - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present is defined
        - result_authselect_present.stat.exists
        - |-
          (result_pam_module_add is defined and result_pam_module_add.changed)
           or (result_pam_module_edit is defined and result_pam_module_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_line_present.found is defined
      - result_pam_line_present.found == 0
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Define a fact for control already filtered
        in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: sufficient
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Check if the required PAM module option
        is present in /etc/pam.d/system-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/system-auth
        regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*\suse_authtok\b
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_module_accounts_password_pam_unix_authtok_option_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Ensure the "use_authtok" PAM option
        for "pam_unix.so" is included in /etc/pam.d/system-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/system-auth
        backrefs: true
        regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so.*)
        line: \1 use_authtok
        state: present
      register: result_pam_accounts_password_pam_unix_authtok_add
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_module_accounts_password_pam_unix_authtok_option_present.found is defined
      - result_pam_module_accounts_password_pam_unix_authtok_option_present.found == 0
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Define a fact for control already filtered
        in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: sufficient
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Check if expected PAM module line is
        present in /etc/pam.d/password-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/password-auth
        regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_line_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Include or update the PAM module line
        in /etc/pam.d/password-auth
      block:

      - name: Require use_authtok for pam_unix.so - Check if required PAM module line
          is present in /etc/pam.d/password-auth with different control
        ansible.builtin.lineinfile:
          path: /etc/pam.d/password-auth
          regexp: ^\s*password\s+.*\s+pam_unix.so\s*
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_line_other_control_present

      - name: Require use_authtok for pam_unix.so - Ensure the correct control for the
          required PAM module line in /etc/pam.d/password-auth
        ansible.builtin.replace:
          dest: /etc/pam.d/password-auth
          regexp: ^(\s*password\s+).*(\bpam_unix.so.*)
          replace: \1{{ pam_module_control }} \2
        register: result_pam_module_edit
        when:
        - result_pam_line_other_control_present.found == 1

      - name: Require use_authtok for pam_unix.so - Ensure the required PAM module line
          is included in /etc/pam.d/password-auth
        ansible.builtin.lineinfile:
          dest: /etc/pam.d/password-auth
          line: password    {{ pam_module_control }}    pam_unix.so
        register: result_pam_module_add
        when:
        - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
          > 1

      - name: Require use_authtok for pam_unix.so - Ensure authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present is defined
        - result_authselect_present.stat.exists
        - |-
          (result_pam_module_add is defined and result_pam_module_add.changed)
           or (result_pam_module_edit is defined and result_pam_module_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_line_present.found is defined
      - result_pam_line_present.found == 0
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Define a fact for control already filtered
        in case filters are used
      ansible.builtin.set_fact:
        pam_module_control: sufficient
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Check if the required PAM module option
        is present in /etc/pam.d/password-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/password-auth
        regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*\suse_authtok\b
        state: absent
      check_mode: true
      changed_when: false
      register: result_pam_module_accounts_password_pam_unix_authtok_option_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Require use_authtok for pam_unix.so - Ensure the "use_authtok" PAM option
        for "pam_unix.so" is included in /etc/pam.d/password-auth
      ansible.builtin.lineinfile:
        path: /etc/pam.d/password-auth
        backrefs: true
        regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so.*)
        line: \1 use_authtok
        state: present
      register: result_pam_accounts_password_pam_unix_authtok_add
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_module_accounts_password_pam_unix_authtok_option_present.found is defined
      - result_pam_module_accounts_password_pam_unix_authtok_option_present.found == 0
      tags:
      - accounts_password_pam_unix_authtok
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### 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 5 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
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.4, 8.3
cis | 5.3.2.1.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_accounts_passwords_pam_faillock_deny='5'

    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
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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 5
      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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_faillock_conf_check.stat.exists
      tags:
      - CJIS-5.5.3
      - NIST-800-171-3.1.8
      - NIST-800-53-AC-7(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.1.6
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.4
      - accounts_passwords_pam_faillock_deny
      - 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 set to `0` or is set to `900` 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_with_zero
References: |
cis | 5.3.2.1.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_accounts_passwords_pam_faillock_unlock_time='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*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:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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 900
      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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_faillock_conf_check.stat.exists
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not result_faillock_conf_check.stat.exists
      tags:
      - accounts_passwords_pam_faillock_unlock_time_with_zero
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group Set Password Quality Requirements Group contains 1 group and 9 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 9 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 - Prevent the Use of Dictionary Words [ref]
The pam_pwquality module's `dictcheck` check if passwords contains dictionary words. When `dictcheck` is set to `1` passwords will be checked for dictionary words.
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. Passwords with dictionary words may be more vulnerable to password-guessing attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck
References: |
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
os-srg | SRG-OS-000480-GPOS-00225, SRG-OS-000072-GPOS-00040
cis | 5.3.2.2.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_dictcheck='1'

    if grep -sq dictcheck /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/dictcheck/d" /etc/security/pwquality.conf.d/*.conf
    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' <<< "^dictcheck")

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

    # 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 "^dictcheck\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^dictcheck\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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:
      - 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_dictcheck
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_dictcheck # promote to variable
      set_fact:
        var_password_pam_dictcheck: !!str 1
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
        Words - Find pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_dictcheck
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
        Words - Ensure dictcheck is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bdictcheck\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_dictcheck
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Prevent the Use of Dictionary
        Words - Ensure PAM variable dictcheck is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*dictcheck
        line: dictcheck = {{ var_password_pam_dictcheck }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_dictcheck
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Different Characters [ref]
The pam_pwquality module's `difok` parameter sets the number of characters in a password that must not be present in and old password during a password change. Modify the `difok` setting in `/etc/security/pwquality.conf` to equal 2 to require differing characters when changing 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 different characters during password changes ensures that newly changed passwords should not resemble previously compromised ones. Note that passwords which are changed on compromised systems will still be compromised, however.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_difok
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)(b), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
os-srg | SRG-OS-000072-GPOS-00040
cis | 5.3.2.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_difok='2'

    if grep -sq difok /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/difok/d" /etc/security/pwquality.conf.d/*.conf
    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' <<< "^difok")

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

    # 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 "^difok\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^difok\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(b)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_difok
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_difok # promote to variable
      set_fact:
        var_password_pam_difok: !!str 2
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
        Find pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(b)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_difok
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
        Ensure difok is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bdifok\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(b)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_difok
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Characters -
        Ensure PAM variable difok is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*difok
        line: difok = {{ var_password_pam_difok }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(b)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_difok
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Enforce for root User [ref]
The pam_pwquality module's `enforce_for_root` parameter controls requirements for enforcing password complexity for the root user. Enable the `enforce_for_root` setting in `/etc/security/pwquality.conf` to require the `root` user to use complex 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_enforce_root
References: |
nist | IA-5(c), IA-5(1)(a), CM-6(a), IA-5(4)
os-srg | SRG-OS-000072-GPOS-00040, SRG-OS-000071-GPOS-00039, SRG-OS-000070-GPOS-00038, SRG-OS-000266-GPOS-00101, SRG-OS-000078-GPOS-00046, SRG-OS-000480-GPOS-00225, SRG-OS-000069-GPOS-00037
cis | 5.3.2.2.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    if [ -e "/etc/security/pwquality.conf" ] ; then

        LC_ALL=C sed -i "/^\s*enforce_for_root/Id" "/etc/security/pwquality.conf"
    else
        touch "/etc/security/pwquality.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/security/pwquality.conf"

    cp "/etc/security/pwquality.conf" "/etc/security/pwquality.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "enforce_for_root" >> "/etc/security/pwquality.conf"
    # Clean up after ourselves.
    rm "/etc/security/pwquality.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:
      - 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_enforce_root
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Enforce for root User
      ansible.builtin.lineinfile:
        path: /etc/security/pwquality.conf
        create: true
        regexp: ''
        line: enforce_for_root
        state: present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_enforce_root
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set Password Maximum Consecutive Repeating Characters [ref]
The pam_pwquality module's `maxrepeat` parameter controls requirements for consecutive repeating characters. When set to a positive number, it will reject passwords which contain more than that number of consecutive characters. Modify the `maxrepeat` setting in `/etc/security/pwquality.conf` to equal 3 to prevent a run of (3 + 1) or more identical characters.
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. Passwords with excessive repeating characters may be more vulnerable to password-guessing attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat
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), CM-6(a), IA-5(4)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
os-srg | SRG-OS-000072-GPOS-00040
cis | 5.3.2.2.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_maxrepeat='3'

    if grep -sq maxrepeat /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/maxrepeat/d" /etc/security/pwquality.conf.d/*.conf
    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' <<< "^maxrepeat")

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

    # 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 "^maxrepeat\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^maxrepeat\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_maxrepeat
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_maxrepeat # promote to variable
      set_fact:
        var_password_pam_maxrepeat: !!str 3
      tags:
        - always

    - name: Set Password Maximum Consecutive Repeating Characters - Find pwquality.conf.d
        files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_maxrepeat
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Password Maximum Consecutive Repeating Characters - Ensure maxrepeat is
        not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bmaxrepeat\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_maxrepeat
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Password Maximum Consecutive Repeating Characters - Ensure PAM variable
        maxrepeat is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*maxrepeat
        line: maxrepeat = {{ var_password_pam_maxrepeat }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_maxrepeat
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Limit the maximum number of sequential characters in passwords [ref]
The `pwquality maxsequence` setting defines the maximum allowable length for consecutive character sequences in a new password. Such sequences can be, e.g., 123 or abc. If the value is set to 0, this check will be turned off. Note: Passwords that consist mainly of such sequences are unlikely to meet the simplicity criteria unless the sequence constitutes only a small portion of the overall password.
Rationale: | Use of a strong 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 important factor that determines the duration required to crack it. A more intricate password results in a larger number of potential combinations that must be tested before successfully compromising the password.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_maxsequence
References: |
cis | 5.3.2.2.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_maxsequence='3'

    if grep -sq maxsequence /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/maxsequence/d" /etc/security/pwquality.conf.d/*.conf
    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' <<< "^maxsequence")

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

    # 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 "^maxsequence\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^maxsequence\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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:
      - accounts_password_pam_maxsequence
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_maxsequence # promote to variable
      set_fact:
        var_password_pam_maxsequence: !!str 3
      tags:
        - always

    - name: Limit the maximum number of sequential characters in passwords - Find pwquality.conf.d
        files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - accounts_password_pam_maxsequence
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Limit the maximum number of sequential characters in passwords - Ensure maxsequence
        is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bmaxsequence\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - accounts_password_pam_maxsequence
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Limit the maximum number of sequential characters in passwords - Ensure PAM
        variable maxsequence is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*maxsequence
        line: maxsequence = {{ var_password_pam_maxsequence }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - accounts_password_pam_maxsequence
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM Enforces Password Requirements - Minimum Different Categories [ref]
The pam_pwquality module's `minclass` parameter controls requirements for usage of different character classes, or types, of character that must exist in a password before it is considered valid. For example, setting this value to three (3) requires that any password must have characters from at least three different categories in order to be approved. The default value is zero (0), meaning there are no required classes. There are four categories available:
    * Upper-case characters
    * Lower-case characters
    * Digits
    * Special characters (for example, punctuation)
Modify the `minclass` setting in `/etc/security/pwquality.conf` entry to require 4 differing categories of characters when changing 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 character categories makes password guessing attacks more difficult by ensuring a larger search space.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass
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
os-srg | SRG-OS-000072-GPOS-00040
anssi | R68
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
cis | 5.3.2.2.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_minclass='4'

    if grep -sq minclass /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/minclass/d" /etc/security/pwquality.conf.d/*.conf
    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' <<< "^minclass")

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

    # 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 "^minclass\\>" "/etc/security/pwquality.conf"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^minclass\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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:
      - 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_minclass
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_password_pam_minclass # promote to variable
      set_fact:
        var_password_pam_minclass: !!str 4
      tags:
        - always

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
        Find pwquality.conf.d files
      ansible.builtin.find:
        paths: /etc/security/pwquality.conf.d/
        patterns: '*.conf'
      register: pwquality_conf_d_files
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_minclass
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
        Ensure minclass is not set in pwquality.conf.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        regexp: ^\s*\bminclass\b.*
        state: absent
      with_items: '{{ pwquality_conf_d_files.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_minclass
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure PAM Enforces Password Requirements - Minimum Different Categories -
        Ensure PAM variable minclass is set accordingly
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/security/pwquality.conf
        regexp: ^#?\s*minclass
        line: minclass = {{ var_password_pam_minclass }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - accounts_password_pam_minclass
      - 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=14` 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
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 8.3.6, 8.3
cis | 5.3.2.2.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    var_password_pam_minlen='14'

    if grep -sq minlen /etc/security/pwquality.conf.d/*.conf ; then
        sed -i "/minlen/d" /etc/security/pwquality.conf.d/*.conf
    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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/security/pwquality.conf" >> "/etc/security/pwquality.conf"
        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
      - 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 14
      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-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(4)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.6
      - accounts_password_pam_minlen
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure PAM password complexity module is enabled in password-auth [ref]
To enable PAM password complexity in password-auth file: Edit the `password` section in `/etc/pam.d/password-auth` to show `password requisite pam_pwquality.so`. The `pam_pwquality` module should be enabled using the `authselect` tool. By default, `authselect` always configures `pam_pwquality local_users_only` as a part of `local`, `sssd`, and `winbind` profiles. No additional authselect feature is needed to be enabled.
Rationale: | Enabling PAM password complexity permits to enforce strong passwords and consequently makes the system less prone to dictionary attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwquality_password_auth
References: |
os-srg | SRG-OS-000069-GPOS-00037, SRG-OS-000070-GPOS-00038, SRG-OS-000480-GPOS-00227
cis | 5.3.1.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

    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+requisite\s+pam_pwquality.so\s*.*" "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwquality.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwquality.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
        else
            LAST_MATCH_LINE=$(grep -nP "^account.*required.*pam_permit\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE" a password     requisite    pam_pwquality.so" "$PAM_FILE_PATH"
            else
                echo "password    requisite    pam_pwquality.so" >> "$PAM_FILE_PATH"
            fi
        fi
    fi
        if [ -f /usr/bin/authselect ]; then

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

    else
        >&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:
      - accounts_password_pam_pwquality_password_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure PAM password complexity module is enabled in password-auth - 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-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - accounts_password_pam_pwquality_password_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure PAM password complexity module is enabled in password-auth - Check
        the proper remediation for the system
      block:

      - name: Ensure PAM password complexity module is enabled in password-auth - Define
          the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Ensure PAM password complexity module is enabled in password-auth - Check
          if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
          authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM password complexity module is enabled in password-auth - 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 password complexity module is enabled in password-auth - 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 password complexity module is enabled in password-auth - Get
            authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM password complexity module is enabled in password-auth - Define
            the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

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

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

        - name: Ensure PAM password complexity module is enabled in password-auth - Check
            if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM password complexity module is enabled in password-auth - Create
            an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 password complexity module is enabled in password-auth - 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 password complexity module is enabled in password-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
            the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

        - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM password complexity module is enabled in password-auth - Change
            the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM password complexity module is enabled in password-auth - Define
          a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: requisite

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

      - name: Ensure PAM password complexity module is enabled in password-auth - Include
          or update the PAM module line in {{ pam_file_path }}
        block:

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

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

        - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
            the required PAM module line is included in {{ pam_file_path }}
          ansible.builtin.lineinfile:
            dest: '{{ pam_file_path }}'
            insertafter: ^account.*required.*pam_permit\.so
            line: password    {{ pam_module_control }}    pam_pwquality.so
          register: result_pam_module_add
          when:
          - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
            > 1

        - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present is defined
          - result_authselect_present.stat.exists
          - |-
            (result_pam_module_add is defined and result_pam_module_add.changed)
             or (result_pam_module_edit is defined and result_pam_module_edit.changed)
        when:
        - result_pam_line_present.found is defined
        - result_pam_line_present.found == 0

      - name: Ensure PAM password complexity module is enabled in password-auth - Ensure
          authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - |-
          (result_pam_accounts_password_pam_pwquality_password_auth_add is defined and result_pam_accounts_password_pam_pwquality_password_auth_add.changed)
           or (result_pam_accounts_password_pam_pwquality_password_auth_edit is defined and result_pam_accounts_password_pam_pwquality_password_auth_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - accounts_password_pam_pwquality_password_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure PAM password complexity module is enabled in system-auth [ref]
To enable PAM password complexity in system-auth file: Edit the `password` section in `/etc/pam.d/system-auth` to show `password requisite pam_pwquality.so`. The `pam_pwquality` module should be enabled using the `authselect` tool. By default, `authselect` always configures `pam_pwquality local_users_only` as a part of `local`, `sssd`, and `winbind` profiles. No additional authselect feature is needed to be enabled.
Rationale: | Enabling PAM password complexity permits to enforce strong passwords and consequently makes the system less prone to dictionary attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_pwquality_system_auth
References: |
os-srg | SRG-OS-000480-GPOS-00227
cis | 5.3.1.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q libpwquality; }; then

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

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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+requisite\s+pam_pwquality.so\s*.*" "$PAM_FILE_PATH"; then
        # Line matching group + control + module was not found. Check group + module.
        if [ "$(grep -cP '^\s*password\s+.*\s+pam_pwquality.so\s*' "$PAM_FILE_PATH")" -eq 1 ]; then
            # The control is updated only if one single line matches.
            sed -i -E --follow-symlinks "s/^(\s*password\s+).*(\bpam_pwquality.so.*)/\1requisite \2/" "$PAM_FILE_PATH"
        else
            LAST_MATCH_LINE=$(grep -nP "^account.*required.*pam_permit\.so" "$PAM_FILE_PATH" | tail -n 1 | cut -d: -f 1)
            if [ ! -z $LAST_MATCH_LINE ]; then
                sed -i --follow-symlinks $LAST_MATCH_LINE" a password     requisite    pam_pwquality.so" "$PAM_FILE_PATH"
            else
                echo "password    requisite    pam_pwquality.so" >> "$PAM_FILE_PATH"
            fi
        fi
    fi
        if [ -f /usr/bin/authselect ]; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - accounts_password_pam_pwquality_system_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure PAM password complexity module is enabled in system-auth - 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-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      tags:
      - accounts_password_pam_pwquality_system_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure PAM password complexity module is enabled in system-auth - Check the
        proper remediation for the system
      block:

      - name: Ensure PAM password complexity module is enabled in system-auth - Define
          the PAM file to be edited as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Ensure PAM password complexity module is enabled in system-auth - Check
          if system relies on authselect tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
          authselect custom profile is used if authselect is present
        block:

        - name: Ensure PAM password complexity module is enabled in system-auth - 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 password complexity module is enabled in system-auth - 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 password complexity module is enabled in system-auth - Get
            authselect current profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

        - name: Ensure PAM password complexity module is enabled in system-auth - Define
            the current authselect profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: '{{ result_authselect_profile.stdout }}'
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is match("custom/")

        - name: Ensure PAM password complexity module is enabled in system-auth - Define
            the new authselect custom profile as a local fact
          ansible.builtin.set_fact:
            authselect_current_profile: '{{ result_authselect_profile.stdout }}'
            authselect_custom_profile: custom/hardening
          when:
          - result_authselect_profile is not skipped
          - result_authselect_profile.stdout is not match("custom/")

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

        - name: Ensure PAM password complexity module is enabled in system-auth - Check
            if any custom profile with the same name was already created
          ansible.builtin.stat:
            path: /etc/authselect/{{ authselect_custom_profile }}
          register: result_authselect_custom_profile_present
          changed_when: false
          when:
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")

        - name: Ensure PAM password complexity module is enabled in system-auth - Create
            an authselect custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 password complexity module is enabled in system-auth - 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 password complexity module is enabled in system-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
            the authselect custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Ensure PAM password complexity module is enabled in system-auth - Restore
            the authselect features in the custom profile
          ansible.builtin.command:
            cmd: authselect enable-feature {{ item }}
          loop: '{{ result_authselect_features.stdout_lines }}'
          register: result_pam_authselect_restore_features
          when:
          - result_authselect_profile is not skipped
          - result_authselect_features is not skipped
          - result_pam_authselect_select_profile is not skipped

        - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Ensure PAM password complexity module is enabled in system-auth - Change
            the PAM file to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Ensure PAM password complexity module is enabled in system-auth - Define
          a fact for control already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: requisite

      - name: Ensure PAM password complexity module is enabled in system-auth - Check
          if expected PAM module line is present in {{ pam_file_path }}
        ansible.builtin.lineinfile:
          path: '{{ pam_file_path }}'
          regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_pwquality.so\s*.*
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_line_present

      - name: Ensure PAM password complexity module is enabled in system-auth - Include
          or update the PAM module line in {{ pam_file_path }}
        block:

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

        - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
            the correct control for the required PAM module line in {{ pam_file_path }}
          ansible.builtin.replace:
            dest: '{{ pam_file_path }}'
            regexp: ^(\s*password\s+).*(\bpam_pwquality.so.*)
            replace: \1{{ pam_module_control }} \2
          register: result_pam_module_edit
          when:
          - result_pam_line_other_control_present.found == 1

        - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
            the required PAM module line is included in {{ pam_file_path }}
          ansible.builtin.lineinfile:
            dest: '{{ pam_file_path }}'
            insertafter: ^account.*required.*pam_permit\.so
            line: password    {{ pam_module_control }}    pam_pwquality.so
          register: result_pam_module_add
          when:
          - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
            > 1

        - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
            authselect changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b
          when:
          - result_authselect_present is defined
          - result_authselect_present.stat.exists
          - |-
            (result_pam_module_add is defined and result_pam_module_add.changed)
             or (result_pam_module_edit is defined and result_pam_module_edit.changed)
        when:
        - result_pam_line_present.found is defined
        - result_pam_line_present.found == 0

      - name: Ensure PAM password complexity module is enabled in system-auth - Ensure
          authselect changes are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - |-
          (result_pam_accounts_password_pam_pwquality_system_auth_add is defined and result_pam_accounts_password_pam_pwquality_system_auth_add.changed)
           or (result_pam_accounts_password_pam_pwquality_system_auth_edit is defined and result_pam_accounts_password_pam_pwquality_system_auth_edit.changed)
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"libpwquality" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - accounts_password_pam_pwquality_system_auth
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
Group Set Password Hashing Algorithm Group contains 3 rules
[ref] The system's default algorithm for storing password hashes in `/etc/shadow` is SHA-512. This can be configured in several locations.
#### Rule Set Password Hashing Algorithm in /etc/login.defs [ref]
In `/etc/login.defs`, add or update the following line to ensure the system will use YESCRYPT|SHA512 as the hashing algorithm:
    ENCRYPT_METHOD YESCRYPT|SHA512
Rationale: | Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text. Using a stronger hashing algorithm makes password cracking attacks more difficult.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_logindefs
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.6.2.2
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.13.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.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)(c), CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.1
os-srg | SRG-OS-000073-GPOS-00041
ism | 0418, 1055, 1402
pcidss4 | 8.3.2, 8.3
cis | 5.4.1.4
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q shadow-utils; }; then

    var_password_hashing_algorithm='YESCRYPT|SHA512'

    # Allow multiple algorithms, but choose the first one for remediation
    #
    var_password_hashing_algorithm="$(echo $var_password_hashing_algorithm | cut -d \| -f 1)"

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

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^ENCRYPT_METHOD\\>" "/etc/login.defs"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^ENCRYPT_METHOD\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
    else
        if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/login.defs" >> "/etc/login.defs"
        printf '%s\n' "$formatted_output" >> "/etc/login.defs"
    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.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - set_password_hashing_algorithm_logindefs
    - name: XCCDF Value var_password_hashing_algorithm # promote to variable
      set_fact:
        var_password_hashing_algorithm: !!str YESCRYPT|SHA512
      tags:
        - always

    - name: Set Password Hashing Algorithm in /etc/login.defs
      ansible.builtin.lineinfile:
        dest: /etc/login.defs
        regexp: ^#?ENCRYPT_METHOD
        line: ENCRYPT_METHOD {{ var_password_hashing_algorithm.split('|')[0] }}
        state: present
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - set_password_hashing_algorithm_logindefs
#### Rule Set PAM Password Hashing Algorithm - password-auth [ref]
The PAM system service can be configured to only store encrypted representations of passwords. In `/etc/pam.d/password-auth`, the `password` section of the file controls which PAM modules to execute during a password change. Set the `pam_unix.so` module in the `password` section to include the option `yescrypt|sha512` and no other hashing algorithms as shown below:
    password    sufficient    pam_unix.so yescrypt|sha512
                        other arguments...
This will help ensure that new passwords for local users will be stored using the yescrypt|sha512 algorithm. Warning: The hashing algorithms to be used with pam_unix.so are defined with independent module options. There are at least 7 possible algorithms and likely more algorithms will be introduced along the time. Due the the number of options and its possible combinations, the use of multiple hashing algorithm options may bring unexpected behaviors to the system. For this reason the check will pass only when one hashing algorithm option is defined and is aligned to the "var_password_hashing_algorithm_pam" variable. The remediation will ensure the correct option and remove any other extra hashing algorithm option. Warning: The CIS Red Hat Enterprise Linux 10 Benchmark permits both "sha512" and "yescrypt" algorithms for password hashing. This profile is configured to check for "yescrypt|sha512", but a system using either algorithm is compliant with the CIS Benchmark requirements. To use a different algorithm, create a tailored profile and select your preferred value for the `var_password_hashing_algorithm_pam` variable.
Rationale: | Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text. This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the `crypt_style` configuration option in `/etc/libuser.conf` ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_passwordauth
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.6.2.2
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.13.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.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)(c), CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.1
os-srg | SRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
ism | 0418, 1055, 1402
cis | 5.3.2.4.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_password_hashing_algorithm_pam='yescrypt|sha512'

    # Allow multiple algorithms, but choose the first one for remediation
    var_password_hashing_algorithm_pam="$(echo $var_password_hashing_algorithm_pam | cut -d \| -f 1)"

    PAM_FILE_PATH="/etc/pam.d/password-auth"

    if [ -e "$PAM_FILE_PATH" ] ; then
        PAM_FILE_PATH="$PAM_FILE_PATH"
        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_PATH")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

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

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

    # Ensure only the correct hashing algorithm option is used.
    declare -a HASHING_ALGORITHMS_OPTIONS=("sha512" "yescrypt" "gost_yescrypt" "blowfish" "sha256" "md5" "bigcrypt")

    for hash_option in "${HASHING_ALGORITHMS_OPTIONS[@]}"; do
      if [ "$hash_option" != "$var_password_hashing_algorithm_pam" ]; then
        if grep -qP "^\s*password\s+.*\s+pam_unix.so\s+.*\b$hash_option\b" "$PAM_FILE_PATH"; then
          if [ -e "$PAM_FILE_PATH" ] ; then
        PAM_FILE_PATH="$PAM_FILE_PATH"
        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_PATH")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

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

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

    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.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_passwordauth
    - name: XCCDF Value var_password_hashing_algorithm_pam # promote to variable
      set_fact:
        var_password_hashing_algorithm_pam: !!str yescrypt|sha512
      tags:
        - always

    - name: Set PAM Password Hashing Algorithm - password-auth - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_passwordauth

    - name: Set PAM Password Hashing Algorithm - password-auth - Check the proper remediation
        for the system
      block:

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

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

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

        - name: Set PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - Get authselect current
            profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - password-auth - Create an authselect
            custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: Set PAM Password Hashing Algorithm - password-auth - Define a fact for control
          already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: sufficient

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - password-auth - Ensure the required
            PAM module line is included in {{ pam_file_path }}
          ansible.builtin.lineinfile:
            dest: '{{ pam_file_path }}'
            line: password    {{ pam_module_control }}    pam_unix.so
          register: result_pam_module_add
          when:
          - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
            > 1

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

      - name: Set PAM Password Hashing Algorithm - password-auth - Define a fact for control
          already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: sufficient

      - name: Set PAM Password Hashing Algorithm - password-auth - Check if the required
          PAM module option is present in {{ pam_file_path }}
        ansible.builtin.lineinfile:
          path: '{{ pam_file_path }}'
          regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*\s{{
            var_password_hashing_algorithm_pam.split("|")[0] }}\b
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_module_set_password_hashing_algorithm_passwordauth_option_present

      - name: Set PAM Password Hashing Algorithm - password-auth - Ensure the "{{ var_password_hashing_algorithm_pam.split("|")[0]
          }}" PAM option for "pam_unix.so" is included in {{ pam_file_path }}
        ansible.builtin.lineinfile:
          path: '{{ pam_file_path }}'
          backrefs: true
          regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so.*)
          line: \1 {{ var_password_hashing_algorithm_pam.split("|")[0] }}
          state: present
        register: result_pam_set_password_hashing_algorithm_passwordauth_add
        when:
        - result_pam_module_set_password_hashing_algorithm_passwordauth_option_present.found
          is defined
        - result_pam_module_set_password_hashing_algorithm_passwordauth_option_present.found
          == 0

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

    - name: Set PAM Password Hashing Algorithm - password-auth - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_passwordauth

    - name: Set PAM Password Hashing Algorithm - password-auth - Check The Proper Remediation
        For The System
      block:

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

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

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

        - name: Set PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - Get authselect current
            profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - password-auth - Create an authselect
            custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 PAM Password Hashing Algorithm - password-auth - 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 PAM Password Hashing Algorithm - password-auth - Ensure authselect
            changes are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

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

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

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

      - name: Set PAM Password Hashing Algorithm - password-auth - Check if "{{ pam_file_path
          }}" File is Present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: pam_file_path_present

      - name: Set PAM Password Hashing Algorithm - password-auth - Ensure That Only the
          Correct Hashing Algorithm Option For pam_unix.so Is Used in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (^\s*password.*pam_unix\.so.*)\b{{ item }}\b\s*(.*)
          replace: \1\2
        when:
        - item != var_password_hashing_algorithm_pam.split('|')[0]
        - pam_file_path_present.stat.exists
        loop:
        - sha512
        - yescrypt
        - gost_yescrypt
        - blowfish
        - sha256
        - md5
        - bigcrypt
        register: result_pam_hashing_options_removal

      - name: Set PAM Password Hashing Algorithm - password-auth - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_hashing_options_removal is changed
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_passwordauth
#### Rule Set PAM Password Hashing Algorithm - system-auth [ref]
The PAM system service can be configured to only store encrypted representations of passwords. In "/etc/pam.d/system-auth", the `password` section of the file controls which PAM modules to execute during a password change. Set the `pam_unix.so` module in the `password` section to include the option `yescrypt|sha512` and no other hashing algorithms as shown below:
    password    sufficient    pam_unix.so yescrypt|sha512
                        other arguments...
This will help ensure that new passwords for local users will be stored using the yescrypt|sha512 algorithm. Warning: The hashing algorithms to be used with pam_unix.so are defined with independent module options. There are at least 7 possible algorithms and likely more algorithms will be introduced along the time. Due the the number of options and its possible combinations, the use of multiple hashing algorithm options may bring unexpected behaviors to the system. For this reason the check will pass only when one hashing algorithm option is defined and is aligned to the "var_password_hashing_algorithm_pam" variable. The remediation will ensure the correct option and remove any other extra hashing algorithm option. Warning: The CIS Red Hat Enterprise Linux 10 Benchmark permits both "sha512" and "yescrypt" algorithms for password hashing. This profile is configured to check for "yescrypt|sha512", but a system using either algorithm is compliant with the CIS Benchmark requirements. To use a different algorithm, create a tailored profile and select your preferred value for the `var_password_hashing_algorithm_pam` variable.
Rationale: | Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text. This setting ensures user and group account administration utilities are configured to store only encrypted representations of passwords. Additionally, the `crypt_style` configuration option in `/etc/libuser.conf` ensures the use of a strong hashing algorithm that makes password cracking attacks more difficult.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_systemauth
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.6.2.2
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.13.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.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)(c), CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.1
os-srg | SRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
anssi | R68
ism | 0418, 1055, 1402
pcidss4 | 8.3.2, 8.3
cis | 5.3.2.4.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    var_password_hashing_algorithm_pam='yescrypt|sha512'

    # Allow multiple algorithms, but choose the first one for remediation
    var_password_hashing_algorithm_pam="$(echo $var_password_hashing_algorithm_pam | cut -d \| -f 1)"

    PAM_FILE_PATH="/etc/pam.d/system-auth"

    # Ensure all the hashing algorithm option is removed.
    declare -a HASHING_ALGORITHMS_OPTIONS=("sha512" "yescrypt" "gost_yescrypt" "blowfish" "sha256" "md5" "bigcrypt")

    for hash_option in "${HASHING_ALGORITHMS_OPTIONS[@]}"; do
      if grep -qP "^\s*password\s+.*\s+pam_unix.so\s+.*\b$hash_option\b" "$PAM_FILE_PATH"; then
        if [ -e "$PAM_FILE_PATH" ] ; then
        PAM_FILE_PATH="$PAM_FILE_PATH"
        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_PATH")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

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

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

    if [ -e "$PAM_FILE_PATH" ] ; then
        PAM_FILE_PATH="$PAM_FILE_PATH"
        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_PATH")
            PAM_FILE_PATH="/etc/authselect/$CURRENT_PROFILE/$PAM_FILE_NAME"

            authselect apply-changes -b
        fi

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

            authselect apply-changes -b
        fi
    else
        echo "$PAM_FILE_PATH was not found" >&2
    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.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_systemauth
    - name: XCCDF Value var_password_hashing_algorithm_pam # promote to variable
      set_fact:
        var_password_hashing_algorithm_pam: !!str yescrypt|sha512
      tags:
        - always

    - name: Set PAM Password Hashing Algorithm - system-auth - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_systemauth

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - Get authselect current
            profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - system-auth - Create an authselect
            custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Set PAM Password Hashing Algorithm - system-auth - Ensure the authselect
            custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

        - name: Set PAM Password Hashing Algorithm - system-auth - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Set PAM Password Hashing Algorithm - system-auth - Change the PAM file
            to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Set PAM Password Hashing Algorithm - system-auth - Define a fact for control
          already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: sufficient

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - system-auth - Ensure the required
            PAM module line is included in {{ pam_file_path }}
          ansible.builtin.lineinfile:
            dest: '{{ pam_file_path }}'
            line: password    {{ pam_module_control }}    pam_unix.so
          register: result_pam_module_add
          when:
          - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
            > 1

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

      - name: Set PAM Password Hashing Algorithm - system-auth - Define a fact for control
          already filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: sufficient

      - name: Set PAM Password Hashing Algorithm - system-auth - Check if the required
          PAM module option is present in {{ pam_file_path }}
        ansible.builtin.lineinfile:
          path: '{{ pam_file_path }}'
          regexp: ^\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so\s*.*\s{{
            var_password_hashing_algorithm_pam.split("|")[0] }}\b
          state: absent
        check_mode: true
        changed_when: false
        register: result_pam_module_set_password_hashing_algorithm_systemauth_option_present

      - name: Set PAM Password Hashing Algorithm - system-auth - Ensure the "{{ var_password_hashing_algorithm_pam.split("|")[0]
          }}" PAM option for "pam_unix.so" is included in {{ pam_file_path }}
        ansible.builtin.lineinfile:
          path: '{{ pam_file_path }}'
          backrefs: true
          regexp: ^(\s*password\s+{{ pam_module_control | regex_escape() }}\s+pam_unix.so.*)
          line: \1 {{ var_password_hashing_algorithm_pam.split("|")[0] }}
          state: present
        register: result_pam_set_password_hashing_algorithm_systemauth_add
        when:
        - result_pam_module_set_password_hashing_algorithm_systemauth_option_present.found
          is defined
        - result_pam_module_set_password_hashing_algorithm_systemauth_option_present.found
          == 0

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

    - name: Set PAM Password Hashing Algorithm - system-auth - 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-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_systemauth

    - name: Set PAM Password Hashing Algorithm - system-auth - Check The Proper Remediation
        For The System
      block:

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

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

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

        - name: Set PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - Get authselect current
            profile
          ansible.builtin.shell:
            cmd: authselect current -r | awk '{ print $1 }'
          register: result_authselect_profile
          changed_when: false
          when:
          - result_authselect_check_cmd is success

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

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

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

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

        - name: Set PAM Password Hashing Algorithm - system-auth - Create an authselect
            custom profile based on the current profile
          ansible.builtin.command:
            cmd: authselect create-profile hardening -b {{ authselect_current_profile
              }}
          when:
          - result_authselect_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 PAM Password Hashing Algorithm - system-auth - 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 PAM Password Hashing Algorithm - system-auth - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

        - name: Set PAM Password Hashing Algorithm - system-auth - Ensure the authselect
            custom profile is selected
          ansible.builtin.command:
            cmd: authselect select {{ authselect_custom_profile }}
          register: result_pam_authselect_select_profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - authselect_current_profile is not match("custom/")
          - authselect_custom_profile is not match(authselect_current_profile)

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

        - name: Set PAM Password Hashing Algorithm - system-auth - Ensure authselect changes
            are applied
          ansible.builtin.command:
            cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
          when:
          - result_authselect_check_cmd is success
          - result_authselect_profile is not skipped
          - result_pam_authselect_restore_features is not skipped

        - name: Set PAM Password Hashing Algorithm - system-auth - Change the PAM file
            to be edited according to the custom authselect profile
          ansible.builtin.set_fact:
            pam_file_path: /etc/authselect/{{ authselect_custom_profile }}/{{ pam_file_path
              | basename }}
          when:
          - authselect_custom_profile is defined
        when:
        - result_authselect_present.stat.exists

      - name: Set PAM Password Hashing Algorithm - system-auth - Check if "{{ pam_file_path
          }}" File is Present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: pam_file_path_present

      - name: Set PAM Password Hashing Algorithm - system-auth - Ensure That Only the
          Correct Hashing Algorithm Option For pam_unix.so Is Used in {{ pam_file_path
          }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (^\s*password.*pam_unix\.so.*)\b{{ item }}\b\s*(.*)
          replace: \1\2
        when:
        - item != var_password_hashing_algorithm_pam.split('|')[0]
        - pam_file_path_present.stat.exists
        loop:
        - sha512
        - yescrypt
        - gost_yescrypt
        - blowfish
        - sha256
        - md5
        - bigcrypt
        register: result_pam_hashing_options_removal

      - name: Set PAM Password Hashing Algorithm - system-auth - Ensure authselect changes
          are applied
        ansible.builtin.command:
          cmd: authselect apply-changes -b
        when:
        - result_authselect_present.stat.exists
        - result_pam_hashing_options_removal is changed
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - CJIS-5.6.2.2
      - NIST-800-171-3.13.11
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.1
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - set_password_hashing_algorithm_systemauth
#### Rule Install pam_pwquality Package [ref]
The `libpwquality` package can be installed with the following command:
    $ sudo dnf install libpwquality
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. "pwquality" enforces complex password construction configuration and has the ability to limit brute-force attacks on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_pam_pwquality_installed
References: |
os-srg | SRG-OS-000480-GPOS-00225
cis | 5.3.1.3
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install libpwquality
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "libpwquality"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q pam; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install libpwquality
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=libpwquality
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_pam_pwquality_installed

    - name: Ensure libpwquality is installed
      ansible.builtin.package:
        name: libpwquality
        state: present
      when: '"pam" in ansible_facts.packages'
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_pam_pwquality_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_libpwquality

    class install_libpwquality {
      package { 'libpwquality':
        ensure => 'installed',
      }
    }
#### Rule Ensure Active Authselect Profile Includes PAM Modules [ref]
The active authselect profile must include the required PAM modules: `pam_pwquality.so`, `pam_pwhistory.so`, `pam_faillock.so`, and `pam_unix.so` in both `system-auth` and `password-auth` files. A custom authselect profile can be created by copying and customizing one of the default profiles. The default profiles include: `local`, `sssd`, and `winbind`. These profiles can be customized to follow site specific requirements. Warning: If local site customizations have been made to the authselect template or files in /etc/pam.d, these custom entries should be added to the newly created custom profile before it's applied to the system. The order within the PAM stacks is important when adding these entries.
Rationale: | A custom profile is required to customize many of the PAM options. Modifications made to a default profile may be overwritten during an update. When you deploy a profile, the profile is applied to every user logging into the given host.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_modules_in_authselect_profile
References: |
cis | 5.3.1.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q pam; 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

    # Function to add a missing PAM module to a file
    # This function handles module placement based on typical PAM stack ordering
    add_pam_module() {
        local authselect_file="$1"
        local module="$2"

        # Check if module already exists in any group
        if grep -Pq "^\s*\S+\s+\S+\s+$module" "$authselect_file"; then
            return 0
        fi

        # Special handling for pam_faillock.so - needs entries in auth and account groups
        if [ "$module" = "pam_faillock.so" ]; then
            # Add preauth entry in auth section (at the beginning of auth section)
            if ! grep -Pq "^\s*auth\s+\S+\s+$module" "$authselect_file"; then
                if grep -qP "^auth" "$authselect_file"; then
                    FIRST_AUTH_LINE=$(grep -nP "^auth" "$authselect_file" | head -n 1 | cut -d: -f 1)
                    if [ ! -z "$FIRST_AUTH_LINE" ]; then
                        sed -i --follow-symlinks "${FIRST_AUTH_LINE}i auth     required    pam_faillock.so preauth" "$authselect_file"
                    fi
                else
                    # If no auth section exists, create it at the beginning
                    sed -i --follow-symlinks "1i auth     required    pam_faillock.so preauth" "$authselect_file"
                fi
            fi
            # Add entry in account section
            if ! grep -Pq "^\s*account\s+\S+\s+$module" "$authselect_file"; then
                if grep -qP "^account" "$authselect_file"; then
                    FIRST_ACCOUNT_LINE=$(grep -nP "^account" "$authselect_file" | head -n 1 | cut -d: -f 1)
                    if [ ! -z "$FIRST_ACCOUNT_LINE" ]; then
                        sed -i --follow-symlinks "${FIRST_ACCOUNT_LINE}a account     required    pam_faillock.so" "$authselect_file"
                    fi
                else
                    # If no account section exists, add it after auth section
                    if grep -qP "^auth" "$authselect_file"; then
                        LAST_AUTH_LINE=$(grep -nP "^auth" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                        if [ ! -z "$LAST_AUTH_LINE" ]; then
                            sed -i --follow-symlinks "${LAST_AUTH_LINE}a account     required    pam_faillock.so" "$authselect_file"
                        fi
                    else
                        echo "account     required    pam_faillock.so" >> "$authselect_file"
                    fi
                fi
            fi
            return 0
        fi

        # Handle pam_pwquality.so - goes in password section, before other password modules
        if [ "$module" = "pam_pwquality.so" ]; then
            if grep -qP "^password" "$authselect_file"; then
                FIRST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | head -n 1 | cut -d: -f 1)
                if [ ! -z "$FIRST_PASSWORD_LINE" ]; then
                    sed -i --follow-symlinks "${FIRST_PASSWORD_LINE}i password     requisite    pam_pwquality.so" "$authselect_file"
                fi
            else
                # If no password section exists, add it after account section
                if grep -qP "^account" "$authselect_file"; then
                    LAST_ACCOUNT_LINE=$(grep -nP "^account" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                    if [ ! -z "$LAST_ACCOUNT_LINE" ]; then
                        sed -i --follow-symlinks "${LAST_ACCOUNT_LINE}a password     requisite    pam_pwquality.so" "$authselect_file"
                    fi
                else
                    echo "password     requisite    pam_pwquality.so" >> "$authselect_file"
                fi
            fi
            return 0
        fi

        # Handle pam_pwhistory.so - goes in password section, after pam_pwquality
        if [ "$module" = "pam_pwhistory.so" ]; then
            if grep -qP "pam_pwquality\.so" "$authselect_file"; then
                # Add after pam_pwquality
                PWQUALITY_LINE=$(grep -nP "pam_pwquality\.so" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                if [ ! -z "$PWQUALITY_LINE" ]; then
                    sed -i --follow-symlinks "${PWQUALITY_LINE}a password     requisite    pam_pwhistory.so" "$authselect_file"
                fi
            elif grep -qP "^password" "$authselect_file"; then
                # Add at the beginning of password section if pam_pwquality not found
                FIRST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | head -n 1 | cut -d: -f 1)
                if [ ! -z "$FIRST_PASSWORD_LINE" ]; then
                    sed -i --follow-symlinks "${FIRST_PASSWORD_LINE}i password     requisite    pam_pwhistory.so" "$authselect_file"
                fi
            else
                echo "password     requisite    pam_pwhistory.so" >> "$authselect_file"
            fi
            return 0
        fi

        # Handle pam_unix.so - typically appears in multiple groups (auth, account, password, session)
        # We'll add it to password group if missing, as that's most critical for this rule
        if [ "$module" = "pam_unix.so" ]; then
            # Check if it exists in password group
            if ! grep -Pq "^\s*password\s+\S+\s+$module" "$authselect_file"; then
                if grep -qP "pam_pwhistory\.so" "$authselect_file"; then
                    # Add after pam_pwhistory
                    PWHISTORY_LINE=$(grep -nP "pam_pwhistory\.so" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                    if [ ! -z "$PWHISTORY_LINE" ]; then
                        sed -i --follow-symlinks "${PWHISTORY_LINE}a password     sufficient    pam_unix.so" "$authselect_file"
                    fi
                elif grep -qP "^password" "$authselect_file"; then
                    # Add at the end of password section
                    LAST_PASSWORD_LINE=$(grep -nP "^password" "$authselect_file" | tail -n 1 | cut -d: -f 1)
                    if [ ! -z "$LAST_PASSWORD_LINE" ]; then
                        sed -i --follow-symlinks "${LAST_PASSWORD_LINE}a password     sufficient    pam_unix.so" "$authselect_file"
                    fi
                else
                    echo "password     sufficient    pam_unix.so" >> "$authselect_file"
                fi
            fi
            return 0
        fi
    }

    # Check and ensure modules are present in both system-auth and password-auth
    pam_profile="$(head -1 /etc/authselect/authselect.conf)"
    pam_profile_path="/etc/authselect/$pam_profile"
    for authselect_file in "$pam_profile_path"/system-auth "$pam_profile_path"/password-auth; do
        if [ ! -f "$authselect_file" ]; then
            echo "Warning: $authselect_file not found"
            continue
        fi
        for module in pam_pwquality.so pam_pwhistory.so pam_faillock.so pam_unix.so; do
            if ! grep -Pq "^\s*\S+\s+\S+\s+$module" "$authselect_file"; then
                add_pam_module "$authselect_file" "$module"
            fi
        done
    done

    authselect apply-changes

    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:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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
      when: '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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
      when: '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Get authselect current
        profile
      ansible.builtin.shell:
        cmd: authselect current -r | awk '{ print $1 }'
      register: result_authselect_profile
      changed_when: false
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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:
      - '"pam" in ansible_facts.packages'
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is match("custom/")
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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:
      - '"pam" in ansible_facts.packages'
      - result_authselect_profile is not skipped
      - result_authselect_profile.stdout is not match("custom/")
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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:
      - '"pam" in ansible_facts.packages'
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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:
      - '"pam" in ansible_facts.packages'
      - result_authselect_profile is not skipped
      - authselect_current_profile is not match("custom/")
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Create an authselect
        custom profile based on the current profile
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b {{ authselect_current_profile }}
      when:
      - '"pam" in ansible_facts.packages'
      - 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
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Create an authselect
        custom profile based on sssd profile
      ansible.builtin.command:
        cmd: authselect create-profile hardening -b sssd
      when:
      - '"pam" in ansible_facts.packages'
      - 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
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
        changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=before-hardening-custom-profile
      when:
      - '"pam" in ansible_facts.packages'
      - 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)
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Ensure the authselect
        custom profile is selected
      ansible.builtin.command:
        cmd: authselect select {{ authselect_custom_profile }}
      register: result_pam_authselect_select_profile
      when:
      - '"pam" in ansible_facts.packages'
      - 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)
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - 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:
      - '"pam" in ansible_facts.packages'
      - result_authselect_profile is not skipped
      - result_authselect_features is not skipped
      - result_pam_authselect_select_profile is not skipped
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
        changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b --backup=after-hardening-custom-profile
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile is not skipped
      - result_pam_authselect_restore_features is not skipped
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Get authselect current
        profile
      ansible.builtin.command: head -1 /etc/authselect/authselect.conf
      register: result_authselect_profile_name
      changed_when: false
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Determine PAM profile
        path
      ansible.builtin.set_fact:
        pam_profile_path: '{%- if result_authselect_profile_name.stdout is match(''^custom/'')
          -%} /etc/authselect/{{ result_authselect_profile_name.stdout }} {%- else -%}
          /usr/share/authselect/default/{{ result_authselect_profile_name.stdout }} {%-
          endif %}'
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - result_authselect_profile_name is not skipped
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Ensure PAM modules
        are present in system-auth and password-auth
      block:

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if {{ item
          }} file exists
        ansible.builtin.stat:
          path: '{{ pam_profile_path }}/{{ item }}'
        register: pam_file_stat
        loop:
        - system-auth
        - password-auth
        when:
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Set list of PAM
          files to process
        ansible.builtin.set_fact:
          pam_files_to_process: '{{ pam_file_stat.results | default([]) | selectattr(''stat.exists'',
            ''equalto'', true) | map(attribute=''item'') | list }}'

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_faillock.so
          exists in auth section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*auth\s+\S+\s+pam_faillock\.so\s+preauth
          state: absent
        check_mode: true
        changed_when: false
        register: pam_faillock_auth_check_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_faillock.so
          preauth entry in auth section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*auth\s+\S+\s+pam_faillock\.so\s+preauth
          insertbefore: ^auth
          line: auth     required    pam_faillock.so preauth
          state: present
        register: pam_faillock_auth_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_faillock_auth_check_result.results | selectattr('item', 'equalto', item)
          | map(attribute='found') | first | default(1) == 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_faillock.so
          exists in account section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*account\s+\S+\s+pam_faillock\.so
          state: absent
        check_mode: true
        changed_when: false
        register: pam_faillock_account_check_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_faillock.so
          entry in account section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*account\s+\S+\s+pam_faillock\.so
          insertafter: ^account
          line: account     required    pam_faillock.so
          state: present
        register: pam_faillock_account_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_faillock_account_check_result.results | selectattr('item', 'equalto', item)
          | map(attribute='found') | first | default(1) == 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_pwquality.so
          exists in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_pwquality\.so
          state: absent
        check_mode: true
        changed_when: false
        register: pam_pwquality_check_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwquality.so
          entry in password section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_pwquality\.so
          insertbefore: ^password
          line: password     requisite    pam_pwquality.so
          state: present
        register: pam_pwquality_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_pwhistory.so
          exists in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
          state: absent
        check_mode: true
        changed_when: false
        register: pam_pwhistory_check_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwhistory.so
          entry after pam_pwquality in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
          insertafter: ^.*pam_pwquality\.so.*
          line: password     requisite    pam_pwhistory.so
          state: present
        register: pam_pwhistory_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0
        - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(0) > 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_pwhistory.so
          entry at beginning of password section in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_pwhistory\.so
          insertbefore: ^password
          line: password     requisite    pam_pwhistory.so
          state: present
        register: pam_pwhistory_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0
        - pam_pwquality_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Check if pam_unix.so
          exists in password section of {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_unix\.so
          state: absent
        check_mode: true
        changed_when: false
        register: pam_unix_check_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_unix.so
          entry after pam_pwhistory in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_unix\.so
          insertafter: ^.*pam_pwhistory\.so.*
          line: password     sufficient    pam_unix.so
          state: present
        register: pam_unix_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_unix_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0
        - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(0) > 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Add pam_unix.so
          entry at end of password section in {{ item }}
        ansible.builtin.lineinfile:
          path: '{{ pam_profile_path }}/{{ item }}'
          regexp: ^\s*password\s+\S+\s+pam_unix\.so
          insertafter: ^password.*
          line: password     sufficient    pam_unix.so
          state: present
        register: pam_unix_add_result
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
        - pam_unix_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0
        - pam_pwhistory_check_result.results | selectattr('item', 'equalto', item) | map(attribute='found')
          | first | default(1) == 0

      - name: Ensure Active Authselect Profile Includes PAM Modules - Store results for
          {{ item }}
        ansible.builtin.set_fact:
          pam_changes_{{ item | replace('-', '_') }}: |-
            {{ ((pam_faillock_auth_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
               (pam_faillock_account_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
               (pam_pwquality_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
               (pam_pwhistory_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false)) or
               (pam_unix_add_result.results | selectattr('item', 'equalto', item) | map(attribute='changed') | first | default(false))) }}
        loop: '{{ pam_files_to_process | default([]) }}'
        when:
        - item is defined
        - pam_profile_path is defined
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - pam_profile_path is defined
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure Active Authselect Profile Includes PAM Modules - Ensure authselect
        changes are applied
      ansible.builtin.command:
        cmd: authselect apply-changes -b
      when:
      - '"pam" in ansible_facts.packages'
      - result_authselect_check_cmd is success
      - (pam_changes_system_auth is defined and pam_changes_system_auth) or (pam_changes_password_auth
        is defined and pam_changes_password_auth)
      tags:
      - accounts_password_pam_modules_in_authselect_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Verify pam_unix module is activated [ref]
`pam_unix` is the standard Unix authentication module. It uses standard calls from the system's libraries to retrieve and set account information as well as authentication. Usually this is obtained from the `/etc/passwd` and if shadow is enabled, the `/etc/shadow` file as well. The account component performs the task of establishing the status of the user's account and password based on the following shadow elements: `expire, last_change, max_change, min_change, warn_change`. In the case of the latter, it may offer advice to the user on changing their password or, through the `PAM_AUTHTOKEN_REQD` return, delay giving service to the user until they have established a new password. The entries listed above are documented in the shadow(5) manual page. Should the user's record not contain one or more of these entries, the corresponding shadow check is not performed. The authentication component performs the task of checking the users credentials (password). The default action of this module is to not permit the user access to a service if their official password is blank. Warning: Automated remediation of this rule isn't available, you should use authselect to manage PAM settings.
Rationale: | The system should only provide access after performing authentication of a user.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_enabled
References: |
cis | 5.3.1.5
Group Protect Accounts by Restricting Password-Based Login Group contains 4 groups and 28 rules
[ref] Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the `/etc/passwd` and `/etc/shadow` files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.
Group Set Account Expiration Parameters Group contains 2 rules
[ref] Accounts can be configured to be automatically disabled after a certain time period, meaning that they will require administrator interaction to become usable again. Expiration of accounts after inactivity can be set for all accounts by default and also on a per-account basis, such as for accounts that are known to be temporary. To configure automatic expiration of an account following the expiration of its password (that is, after the password has expired and not been changed), run the following command, substituting `NUM_DAYS` and `USER` appropriately:
    $ sudo chage -I NUM_DAYS USER
Accounts, such as temporary accounts, can also be configured to expire on an explicitly-set date with the `-E` option. The file `/etc/default/useradd` controls default settings for all newly-created accounts created with the system's normal command line utilities. Warning: This will only apply to newly created accounts
#### Rule Set Account Expiration Following Inactivity [ref]
To specify the number of days after a password expires (which signifies inactivity) until an account is permanently disabled, add or correct the following line in `/etc/default/useradd`:
    INACTIVE=45
If a password is currently on the verge of expiration, then `45` day(s) remain(s) until the account is automatically disabled. However, if the password will not expire for another 60 days, then 60 days plus `45` day(s) could elapse until the account would be automatically disabled. See the `useradd` man page for more information.
Rationale: | Inactive identifiers pose a risk to systems and applications because attackers may exploit an inactive identifier and potentially obtain undetected access to the system. Disabling inactive accounts ensures that accounts which may not have been responsibly removed are not available to attackers who may have compromised their credentials. Owners of inactive accounts will not notice if unauthorized access to their user account has been obtained.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cjis | 5.6.2.1.1
cobit5 | DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.5.6
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 6.2
iso27001-2013 | A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cip | CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nist | IA-4(e), AC-2(3), CM-6(a)
nist-csf | DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidss | Req-8.1.4
os-srg | SRG-OS-000118-GPOS-00060
pcidss4 | 8.2.6, 8.2
cis | 5.4.1.5
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q shadow-utils; }; then

    var_account_disable_post_pw_expiration='45'

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

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

    # 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 "^INACTIVE\\>" "/etc/default/useradd"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^INACTIVE\\>.*/$escaped_formatted_output/gi" "/etc/default/useradd"
    else
        if [[ -s "/etc/default/useradd" ]] && [[ -n "$(tail -c 1 -- "/etc/default/useradd" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/default/useradd"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/default/useradd" >> "/etc/default/useradd"
        printf '%s\n' "$formatted_output" >> "/etc/default/useradd"
    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
      - NIST-800-171-3.5.6
      - NIST-800-53-AC-2(3)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-4(e)
      - PCI-DSS-Req-8.1.4
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.6
      - account_disable_post_pw_expiration
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
      set_fact:
        var_account_disable_post_pw_expiration: !!str 45
      tags:
        - always

    - name: Set Account Expiration Following Inactivity
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/default/useradd
        regexp: ^INACTIVE
        line: INACTIVE={{ var_account_disable_post_pw_expiration }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1.1
      - NIST-800-171-3.5.6
      - NIST-800-53-AC-2(3)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-4(e)
      - PCI-DSS-Req-8.1.4
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.6
      - account_disable_post_pw_expiration
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure All Accounts on the System Have Unique Names [ref]
Ensure accounts on the system have unique names. To ensure all accounts have unique names, run the following command:
    $ sudo getent passwd | awk -F: '{ print $1}' | uniq -d
If a username is returned, change or delete the username.
Rationale: | Unique usernames allow for accountability on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_account_unique_name
References: |
cjis | 5.5.2
pcidss | Req-8.1.1
pcidss4 | 8.2.1, 8.2
bsi | SYS.1.3.A2
cis | 7.2.6
Group Set Password Expiration Parameters Group contains 5 rules
[ref] The file `/etc/login.defs` controls several password-related settings. Programs such as `passwd`, `su`, and `login` consult `/etc/login.defs` to determine behavior with regard to password aging, expiration warnings, and length. See the man page `login.defs(5)` for more information. Users should be forced to change their passwords, in order to decrease the utility of compromised passwords. However, the need to change passwords often should be balanced against the risk that users will reuse or write down passwords if forced to change them too often. Forcing password changes every 90-360 days, depending on the environment, is recommended. Set the appropriate value as `PASS_MAX_DAYS` and apply it to existing accounts with the `-M` flag. The `PASS_MIN_DAYS` (`-m`) setting prevents password changes for 7 days after the first change, to discourage password cycling. If you use this setting, train users to contact an administrator for an emergency password change in case a new password becomes compromised. The `PASS_WARN_AGE` (`-W`) setting gives users 7 days of warnings at login time that their passwords are about to expire. For example, for each existing human user USER, expiration parameters could be adjusted to a 180 day maximum password age, 7 day minimum password age, and 7 day warning period with the following command:
    $ sudo chage -M 180 -m 7 -W 7 USER
#### Rule Set Password Maximum Age [ref]
To specify password maximum age for new accounts, edit the file `/etc/login.defs` and add or correct the following line:
    PASS_MAX_DAYS 365
The profile requirement is `365`.
Rationale: | Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised. Setting the password maximum age ensures users are required to periodically change their passwords. Requiring shorter password lifetimes increases the risk of users writing down the password in a convenient location subject to physical compromise.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.6.2.1
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.5.6
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(f), IA-5(1)(d), CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.4
os-srg | SRG-OS-000076-GPOS-00044
ism | 0418, 1055, 1402
pcidss4 | 8.3.9, 8.3
cis | 5.4.1.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q shadow-utils; }; then

    var_accounts_maximum_age_login_defs='365'

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

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

    # 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 "^PASS_MAX_DAYS\\>" "/etc/login.defs"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^PASS_MAX_DAYS\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
    else
        if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/login.defs" >> "/etc/login.defs"
        printf '%s\n' "$formatted_output" >> "/etc/login.defs"
    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
      - NIST-800-171-3.5.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.4
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_maximum_age_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
      set_fact:
        var_accounts_maximum_age_login_defs: !!str 365
      tags:
        - always

    - name: Set Password Maximum Age
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/login.defs
        regexp: ^#?PASS_MAX_DAYS
        line: PASS_MAX_DAYS {{ var_accounts_maximum_age_login_defs }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      tags:
      - CJIS-5.6.2.1
      - NIST-800-171-3.5.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.4
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_maximum_age_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set Existing Passwords Maximum Age [ref]
Configure non-compliant accounts to enforce a 365-day maximum password lifetime restriction by running the following command:
    $ sudo chage -M 365
                        USER
Rationale: | Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_set_max_life_existing
References: |
nist | IA-5(f), IA-5(1)(d), CM-6(a)
os-srg | SRG-OS-000076-GPOS-00044
pcidss4 | 8.3.9, 8.3
cis | 5.4.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_accounts_maximum_age_login_defs='365'

    while IFS= read -r i; do

        chage -M $var_accounts_maximum_age_login_defs $i

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

    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-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_set_max_life_existing
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
      set_fact:
        var_accounts_maximum_age_login_defs: !!str 365
      tags:
        - always

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

    - name: Change the maximum time period between password changes
      ansible.builtin.user:
        user: '{{ item }}'
        password_expire_max: '{{ var_accounts_maximum_age_login_defs }}'
      with_items: '{{ user_names.stdout_lines }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - user_names.stdout_lines | length > 0
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_set_max_life_existing
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set Existing Passwords Warning Age [ref]
To configure how many days prior to password expiration that a warning will be issued to users, run the command:
    $ sudo chage --warndays 7
                        USER
This profile requirement is `7`.
Rationale: | Providing an advance warning that a password will be expiring gives users time to think of a secure password. Users caught unaware may choose a simple password or write it down where it may be discovered.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_set_warn_age_existing
References: |
nist | IA-5(f), IA-5(1)(d), CM-6(a)
pcidss4 | 8.3.9, 8.3
cis | 5.4.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_accounts_password_warn_age_login_defs='7'

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

    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-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_set_warn_age_existing
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
    - name: XCCDF Value var_accounts_password_warn_age_login_defs # promote to variable
      set_fact:
        var_accounts_password_warn_age_login_defs: !!str 7
      tags:
        - always

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

    - name: Set Existing Passwords Warning Age - Ensure the Number of Days of Warning
        Before Password Expires
      ansible.builtin.command:
        cmd: chage --warndays {{ var_accounts_password_warn_age_login_defs }} {{ item
          }}
      with_items: '{{ result_pass_warn_age_user_names.stdout_lines }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - result_pass_warn_age_user_names is not skipped and result_pass_warn_age_user_names.stdout_lines
        | length > 0
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_set_warn_age_existing
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Set Password Warning Age [ref]
To specify how many days prior to password expiration that a warning will be issued to users, edit the file `/etc/login.defs` and add or correct the following line:
    PASS_WARN_AGE 7
The profile requirement is `7`.
Rationale: | Setting the password warning age enables users to make the change at a practical time.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_warn_age_login_defs
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cobit5 | DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.5.8
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 6.2
iso27001-2013 | A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nist | IA-5(f), IA-5(1)(d), CM-6(a)
nist-csf | DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidss | Req-8.2.4
ism | 0418, 1055, 1402
pcidss4 | 8.3.9, 8.3
cis | 5.4.1.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q shadow-utils; }; then

    var_accounts_password_warn_age_login_defs='7'

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

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

    # 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 "^PASS_WARN_AGE\\>" "/etc/login.defs"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^PASS_WARN_AGE\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
    else
        if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/login.defs" >> "/etc/login.defs"
        printf '%s\n' "$formatted_output" >> "/etc/login.defs"
    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.5.8
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.4
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_warn_age_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_password_warn_age_login_defs # promote to variable
      set_fact:
        var_accounts_password_warn_age_login_defs: !!str 7
      tags:
        - always

    - name: Set Password Warning Age
      ansible.builtin.lineinfile:
        dest: /etc/login.defs
        regexp: ^PASS_WARN_AGE *[0-9]*
        state: present
        line: PASS_WARN_AGE        {{ var_accounts_password_warn_age_login_defs }}
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.5.8
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(d)
      - NIST-800-53-IA-5(f)
      - PCI-DSS-Req-8.2.4
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.9
      - accounts_password_warn_age_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Set existing passwords a period of inactivity before they been locked [ref]
Configure user accounts that have been inactive for over a given period of time to be automatically disabled by running the following command:
    $ sudo chage --inactive 30 USER
Rationale: | Inactive accounts pose a threat to system security since the users are not logging in to notice failed login attempts or other anomalies.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_set_post_pw_existing
References: |
cobit5 | DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.5.6
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 6.2
iso27001-2013 | A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cip | CIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nist | IA-4(e), AC-2(3), CM-6(a)
nist-csf | DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidss | Req-8.1.4
os-srg | SRG-OS-000118-GPOS-00060
pcidss4 | 8.2.6, 8.2
cis | 5.4.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_account_disable_post_pw_expiration='45'

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

    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.5.6
      - NIST-800-53-AC-2(3)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-4(e)
      - PCI-DSS-Req-8.1.4
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.6
      - accounts_set_post_pw_existing
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
      set_fact:
        var_account_disable_post_pw_expiration: !!str 45
      tags:
        - always

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

    - name: Change the period of inactivity
      ansible.builtin.command:
        cmd: chage --inactive {{ var_account_disable_post_pw_expiration }} {{ item }}
      with_items: '{{ user_names.stdout_lines }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - user_names is not skipped and user_names.stdout_lines | length > 0
      tags:
      - NIST-800-171-3.5.6
      - NIST-800-53-AC-2(3)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-4(e)
      - PCI-DSS-Req-8.1.4
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.6
      - accounts_set_post_pw_existing
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group Verify Proper Storage and Existence of Password Hashes Group contains 9 rules
[ref] By default, password hashes for local accounts are stored in the second field (colon-separated) in `/etc/shadow`. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as `/etc/passwd`, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.
#### Rule Verify All Account Password Hashes are Shadowed [ref]
If any password hashes are stored in `/etc/passwd` (in the second field, instead of an `x` or `*`), the cause of this misconfiguration should be investigated. The account should have its password reset and the hash should be properly stored, or the account should be deleted entirely.
Rationale: | The hashes for all user account passwords should be stored in the file `/etc/shadow` and never in `/etc/passwd`, which is readable by all users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.5.2
cobit5 | DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui | 3.5.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(h), CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.2.1
ism | 1402
pcidss4 | 8.3.2, 8.3
cis | 7.2.1
#### Rule Ensure all users last password change date is in the past [ref]
All users should have a password change date in the past. Warning: Automatic remediation is not available, in order to avoid any system disruption.
Rationale: | If a user recorded password change date is in the future then they could bypass any set password expiration.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_last_change_is_in_past
References: |
pcidss4 | 8.3.5, 8.3
cis | 5.4.1.6
#### Rule Avoid using remember in pam_unix module [ref]
The `remember` option stores the last n passwords for each user in `/etc/security/opasswd`, enforcing password history and preventing users from reusing the same passwords. However, this feature relies on the MD5 password hash algorithm, which is less secure. Instead, the `pam_pwhistory` module should be used. This module also stores the last n passwords in `/etc/security/opasswd` and it uses the password hash algorithm configured in the pam_unix module, such as yescrypt or SHA512, offering enhanced security. The `remember` option should be removed from the PAM configuration in `/etc/pam.d/system-auth` and `/etc/pam.d/password-auth` files. 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.
Rationale: | Removing the `remember` argument ensures the use of a stronger password hashing algorithm. A more robust hash algorithm increases the difficulty for attackers to crack stored passwords in `/etc/security/opasswd`, thereby improving system security and protecting user credentials.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_no_remember
References: |
cis | 5.3.2.4.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q pam; then

    # RHEL-based systems: Use authselect-aware approach
    if [ -f /usr/bin/authselect ]; then
        if [ -e "/etc/pam.d/system-auth" ] ; then
        PAM_FILE_PATH="/etc/pam.d/system-auth"
        if [ -f /usr/bin/authselect ]; then

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

            CURRENT_PROFILE=$(authselect current -r | awk '{ print $1 }')
            # If not already in use, a custom profile is created preserving the enabled features.
            if [[ ! $CURRENT_PROFILE == custom/* ]]; then
                ENABLED_FEATURES=$(authselect current | tail -n+3 | awk '{ print $2 }')
                # 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+.*\s+pam_unix.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*.*.*pam_unix.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

            authselect apply-changes -b
        fi
    else
        echo "/etc/pam.d/system-auth was not found" >&2
    fi
        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+.*\s+pam_unix.so\s.*\bremember\b" "$PAM_FILE_PATH"; then
        sed -i -E --follow-symlinks "s/(.*password.*.*.*pam_unix.so.*)\bremember\b=?[[:alnum:]]*(.*)/\1\2/g" "$PAM_FILE_PATH"
    fi
        if [ -f /usr/bin/authselect ]; then

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

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

    if grep -qP "^\s*password\s+.*\s+pam_unix.so\s.*\bremember\b" "/etc/pam.d/password-auth"; then
        sed -i -E --follow-symlinks "s/(.*password.*.*.*pam_unix.so.*)\bremember\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:
      - accounts_password_pam_unix_no_remember
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Avoid using remember in pam_unix module - 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: '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_no_remember
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Avoid using remember in pam_unix module - Check the proper remediation for
        the system
      block:

      - name: Avoid using remember in pam_unix module - Define the PAM file to be edited
          as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/system-auth

      - name: Avoid using remember in pam_unix module - Check if system relies on authselect
          tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Avoid using remember in pam_unix module - Ensure authselect custom profile
          is used if authselect is present
        block:

        - name: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - Define a fact for control already
          filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Avoid using remember in pam_unix module - Check if {{ pam_file_path }} file
          is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Avoid using remember in pam_unix module - Ensure the "remember" option from
          "pam_unix.so" is not present in {{ pam_file_path }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_unix.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Avoid using remember in pam_unix module - 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:
      - '"pam" in ansible_facts.packages'
      - result_pam_auth_file_present.stat.exists
      tags:
      - accounts_password_pam_unix_no_remember
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Avoid using remember in pam_unix module - 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: '"pam" in ansible_facts.packages'
      tags:
      - accounts_password_pam_unix_no_remember
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed

    - name: Avoid using remember in pam_unix module - Check the proper remediation for
        the system
      block:

      - name: Avoid using remember in pam_unix module - Define the PAM file to be edited
          as a local fact
        ansible.builtin.set_fact:
          pam_file_path: /etc/pam.d/password-auth

      - name: Avoid using remember in pam_unix module - Check if system relies on authselect
          tool
        ansible.builtin.stat:
          path: /usr/bin/authselect
        register: result_authselect_present

      - name: Avoid using remember in pam_unix module - Ensure authselect custom profile
          is used if authselect is present
        block:

        - name: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - 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: Avoid using remember in pam_unix module - Define a fact for control already
          filtered in case filters are used
        ansible.builtin.set_fact:
          pam_module_control: ''

      - name: Avoid using remember in pam_unix module - Check if {{ pam_file_path }} file
          is present
        ansible.builtin.stat:
          path: '{{ pam_file_path }}'
        register: result_pam_file_present

      - name: Avoid using remember in pam_unix module - Ensure the "remember" option from
          "pam_unix.so" is not present in {{ pam_file_path }}
        ansible.builtin.replace:
          dest: '{{ pam_file_path }}'
          regexp: (.*password.*pam_unix.so.*)\bremember\b=?[0-9a-zA-Z]*(.*)
          replace: \1\2
        register: result_pam_option_removal
        when:
        - result_pam_file_present.stat.exists

      - name: Avoid using remember in pam_unix module - 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:
      - '"pam" in ansible_facts.packages'
      - result_pam_password_auth_file_present.stat.exists
      tags:
      - accounts_password_pam_unix_no_remember
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
#### Rule All GIDs referenced in /etc/passwd must be defined in /etc/group [ref]
Add a group to the system for each GID referenced without a corresponding group.
Rationale: | If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group with the Group Identifier (GID) is subsequently created, the user may have unintended rights to any files associated with the group.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_gid_passwd_group_same
References: |
cis-csc | 1, 12, 15, 16, 5
cjis | 5.5.2
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
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, CM-6(a)
nist-csf | PR.AC-1, PR.AC-6, PR.AC-7
pcidss | Req-8.5.a
os-srg | SRG-OS-000104-GPOS-00051
pcidss4 | 8.2.2, 8.2
bsi | SYS.1.3.A2
cis | 7.2.3
#### 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
cis | 5.3.2.4.1
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-core; 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
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.2
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.2
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - 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-core" in ansible_facts.packages'
      - not result_authselect_present.stat.exists
      tags:
      - CJIS-5.5.2
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-CM-6(a)
      - NIST-800-53-IA-5(1)(a)
      - NIST-800-53-IA-5(c)
      - PCI-DSS-Req-8.2.3
      - PCI-DSSv4-8.3
      - PCI-DSSv4-8.3.1
      - configure_strategy
      - high_severity
      - low_complexity
      - medium_disruption
      - no_empty_passwords
      - no_reboot_needed
#### Rule Ensure There Are No Accounts With Blank or Null Passwords [ref]
Check the "/etc/shadow" file for blank passwords with the following command:
    $ sudo awk -F: '!$2 {print $1}' /etc/shadow
If the command returns any results, this is a finding. Configure all accounts on the system to have a password or lock the account with the following commands: Perform a password reset:
    $ sudo passwd [username]
Lock an account:
    $ sudo passwd -l [username]
Warning: Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
Rationale: | If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_no_empty_passwords_etc_shadow
References: |
nist | CM-6(b), CM-6.1(iv)
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.2, 2.2
cis | 7.2.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.2
      - high_severity
      - low_complexity
      - low_disruption
      - no_empty_passwords_etc_shadow
      - no_reboot_needed
      - restrict_strategy

    - name: Collect users with no password
      ansible.builtin.command: |
        awk -F: '!$2 {print $1}' /etc/shadow
      register: users_nopasswd
      changed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.2
      - high_severity
      - low_complexity
      - low_disruption
      - no_empty_passwords_etc_shadow
      - no_reboot_needed
      - restrict_strategy

    - name: Lock users with no password
      ansible.builtin.command: |
        passwd -l {{ item }}
      with_items: '{{ users_nopasswd.stdout_lines }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - users_nopasswd is not skipped and users_nopasswd.stdout_lines | length > 0
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.2
      - high_severity
      - low_complexity
      - low_disruption
      - no_empty_passwords_etc_shadow
      - no_reboot_needed
      - restrict_strategy
#### Rule Verify No .forward Files Exist [ref]
The `.forward` file specifies an email address to forward the user's mail to.
Rationale: | Use of the `.forward` file poses a security risk in that sensitive data may be inadvertently transferred outside the organization. The .forward file also poses a risk as it can be used to execute commands that may perform unintended actions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_forward_files
References: |
cis | 7.2.9
#### Rule Verify No netrc Files Exist [ref]
The `.netrc` files contain login information used to auto-login into FTP servers and reside in the user's home directory. These files may contain unencrypted passwords to remote FTP servers making them susceptible to access by unauthorized users and should not be used. Any `.netrc` files should be removed.
Rationale: | Unencrypted passwords for remote FTP servers may be stored in `.netrc` files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_netrc_files
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
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 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, 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-5(h), IA-5(1)(c), CM-6(a), IA-5(7)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
cis | 7.2.9
#### Rule Verify No .rhost Files Exist [ref]
Local system users should not have a `.rhost` file in their home directory. Warning: Automatic remediation of this rule is not available due to the unique requirements of each system. Any .rhost files should be investigated and removed manually.
Rationale: | User configuration files with excessive or incorrect access may enable malicious users to steal or modify other users' data or to gain another user's system privileges. The `.rhost` file provides the "remote authentication" database for the rcp, rlogin, and rsh commands and the rcmd() function. These files bypass the standard password-based user authentication mechanism. They specify remote hosts and users that are considered trusted (i.e. are allowed to access the local system without supplying a password).
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_rhost_files
References: |
cis | 7.2.9
Group Restrict Root Logins Group contains 9 rules
[ref] Direct root logins should be allowed only for emergency use. In normal situations, the administrator should access the system via a unique unprivileged account, and then use `su` or `sudo` to execute privileged commands. Discouraging administrators from accessing the root account directly ensures an audit trail in organizations with multiple administrators. Locking down the channels through which root can connect directly also reduces opportunities for password-guessing against the root account. The `login` program uses the file `/etc/securetty` to determine which interfaces should allow root logins. The virtual devices `/dev/console` and `/dev/tty*` represent the system consoles (accessible via the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default installation). The default securetty file also contains `/dev/vc/*`. These are likely to be deprecated in most environments, but may be retained for compatibility. Root should also be prohibited from connecting via network protocols. Other sections of this document include guidance describing how to prevent root from logging in via SSH.
#### Rule Verify Only Root Has UID 0 [ref]
If any account other than root has a UID of 0, this misconfiguration should be investigated and the accounts other than root should be removed or have their UID changed. If the account is associated with system commands or applications the UID should be changed to one greater than "0" but less than "1000." Otherwise assign a UID greater than "1000" that has not already been assigned.
Rationale: | An account has root authority if it has a UID of 0. Multiple accounts with a UID of 0 afford more opportunity for potential intruders to guess a password for a privileged account. Proper configuration of sudo is recommended to afford multiple system administrators access to root privileges in an accountable manner.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_no_uid_except_zero
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui | 3.1.1, 3.1.5
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
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-6(5), IA-4(b)
nist-csf | PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
pcidss | Req-8.5
os-srg | SRG-OS-000480-GPOS-00227
ism | 1546
pcidss4 | 8.2.1, 8.2
cis | 5.4.2.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    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.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-6(5)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-4(b)
      - PCI-DSS-Req-8.5
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.1
      - accounts_no_uid_except_zero
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Get all /etc/passwd file entries
      ansible.builtin.getent:
        database: passwd
        split: ':'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-6(5)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-4(b)
      - PCI-DSS-Req-8.5
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.1
      - accounts_no_uid_except_zero
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy

    - name: Lock the password of the user accounts other than root with uid 0
      ansible.builtin.command: passwd -l {{ item.key }}
      loop: '{{ getent_passwd | dict2items | rejectattr(''key'', ''search'', ''root'')
        | list }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.value.1  == '0'
      tags:
      - NIST-800-171-3.1.1
      - NIST-800-171-3.1.5
      - NIST-800-53-AC-6(5)
      - NIST-800-53-IA-2
      - NIST-800-53-IA-4(b)
      - PCI-DSS-Req-8.5
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.1
      - accounts_no_uid_except_zero
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - restrict_strategy
#### Rule Verify Root Has A Primary GID 0 [ref]
The `root` user should have a primary group of 0.
Rationale: | To help ensure that root-owned files are not inadvertently exposed to other users.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_root_gid_zero
References: |
pcidss | Req-8.1.1
pcidss4 | 8.2.1, 8.2
cis | 5.4.2.2
#### Rule Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty [ref]
Ensure that the group `sugroup` referenced by `var_pam_wheel_group_for_su` variable and used as value for the `pam_wheel.so` `group` option exists and has no members. This empty group used by `pam_wheel.so` in `/etc/pam.d/su` ensures that no user can run commands with altered privileges through the `su` command. Warning: Note that this rule just ensures the group exists and has no members. This rule does not configure `pam_wheel.so` module. The `pam_wheel.so` module configuration is accomplished by `use_pam_wheel_group_for_su` rule.
Rationale: | The `su` program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_pam_wheel_group_empty
References: |
pcidss4 | 2.2.6, 2.2
cis | 5.2.7
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_pam_wheel_group_for_su='sugroup'

    # Workaround for https://github.com/OpenSCAP/openscap/issues/2242: Use full
    # path to groupadd command to avoid the issue with the command not being found.
    if ! grep -q "^${var_pam_wheel_group_for_su}:[^:]*:[^:]*:[^:]*" /etc/group; then
        /usr/sbin/groupadd ${var_pam_wheel_group_for_su}
    fi

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - ensure_pam_wheel_group_empty
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
      set_fact:
        var_pam_wheel_group_for_su: !!str sugroup
      tags:
        - always

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

    - name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
        - Ensure {{ var_pam_wheel_group_for_su }} Group is Empty
      ansible.builtin.lineinfile:
        path: /etc/group
        regexp: ^({{ var_pam_wheel_group_for_su }}:[^:]+:[0-9]+:).*$
        line: \1
        backrefs: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - ensure_pam_wheel_group_empty
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure Authentication Required for Single User Mode [ref]
Single user mode is used for recovery when the system detects an issue during boot or by manual selection from the bootloader.
Rationale: | Requiring authentication in single user mode prevents an unauthorized user from rebooting the system into single user to gain root privileges without credentials.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_root_password_configured
References: |
pcidss4 | 2.2.2, 2.2
cis | 5.4.2.4
#### Rule Verify Only Group Root Has GID 0 [ref]
If any group other than root has a GID of 0, this misconfiguration should be investigated and the groups other than root should be removed or have their GID changed. Warning: This rule doesn't come with a remediation. The removal of groups from a system or reassigning the GID is considered too disruptive.
Rationale: | Ensuring that only the `root` group has a GID of 0 helps prevent root group owned files from becoming accidentally accessible to non-privileged users.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_groups_no_zero_gid_except_root
References: |
cis | 5.4.2.3
#### Rule Verify Non-Interactive Accounts Are Locked [ref]
Accounts meant for non-interactive purposes should be locked to prevent unauthorized access. Accounts with non-standard shells (those not defined in `/etc/shells`) should be locked using `usermod -L`. Warning: Automatic remediation of this control is not recommended. Locking system accounts could be highly disruptive.
Rationale: | Locking non-interactive accounts improves security by preventing potential misuse. While many systems configure these accounts with invalid strings, setting the shell field to `nologin` is also suggested
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_invalid_shell_accounts_unlocked
References: |
cis | 5.4.2.8
#### Rule Ensure that System Accounts Are Locked [ref]
Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. An attacker should not be able to log into these accounts. System accounts are those user accounts with a user ID less than `1000`. If any system account other than `root`, `halt`, `sync`, `shutdown` and `nfsnobody` has an unlocked password, disable it with the command:
    $ sudo usermod -L account
Rationale: | Disabling authentication for default system accounts makes it more difficult for attackers to make use of them to compromise a system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_password_auth_for_systemaccounts
References: |
nerc-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 | AC-6, CM-6(a)
pcidss4 | 8.2.2, 8.2
cis | 5.4.2.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_password_auth_for_systemaccounts
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure that System Accounts Are Locked - Get All Local Users From /etc/passwd
      ansible.builtin.getent:
        database: passwd
        split: ':'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_password_auth_for_systemaccounts
      - no_reboot_needed
      - restrict_strategy

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

    - name: Ensure that System Accounts Are Locked - Lock System Accounts
      ansible.builtin.user:
        name: '{{ item.key }}'
        password_lock: true
      loop: '{{ local_users }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.value[1]|int < 1000
      - item.key not in ['root', 'halt', 'sync', 'shutdown', 'nfsnobody']
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_password_auth_for_systemaccounts
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure that System Accounts Do Not Run a Shell Upon Login [ref]
Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. Should an attacker be able to log into these accounts, they should not be granted access to a shell. The login shell for each local account is stored in the last field of each line in `/etc/passwd`. System accounts are those user accounts with a user ID less than `1000`. The user ID is stored in the third field. If any system account other than `root` has a login shell, disable it with the command:
    $ sudo usermod -s /sbin/nologin account
Warning: Do not perform the steps in this section on the root account. Doing so might cause the system to become inaccessible.
Rationale: | Ensuring shells are not given to system accounts upon login makes it more difficult for attackers to make use of system accounts.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_shelllogin_for_systemaccounts
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cobit5 | DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03
isa-62443-2009 | 4.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013 | SR 1.1, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
iso27001-2013 | A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nist | AC-6, CM-6(a), CM-6(b), CM-6.1(iv)
nist-csf | DE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6
os-srg | SRG-OS-000480-GPOS-00227
ism | 1491
pcidss4 | 8.2.2, 8.2
cis | 5.4.2.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - no_shelllogin_for_systemaccounts
      - restrict_strategy

    - name: Ensure that System Accounts Do Not Run a Shell Upon Login - Get All Local
        Users From /etc/passwd
      ansible.builtin.getent:
        database: passwd
        split: ':'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - no_shelllogin_for_systemaccounts
      - restrict_strategy

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

    - name: Ensure that System Accounts Do Not Run a Shell Upon Login -  Disable Login
        Shell for System Accounts
      ansible.builtin.user:
        name: '{{ item.key }}'
        shell: /sbin/nologin
      loop: '{{ local_users }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.key not in ['root']
      - item.value[1]|int < 1000
      - item.value[5] not in ['/sbin/shutdown', '/sbin/halt', '/bin/sync']
      tags:
      - NIST-800-53-AC-6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.2
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - no_shelllogin_for_systemaccounts
      - restrict_strategy
#### Rule Enforce Usage of pam_wheel with Group Parameter for su Authentication [ref]
To ensure that only users who are members of the group set in the `group` option of `pam_wheel.so` module can run commands with altered privileges through the `su` command, make sure that the following line exists in the file `/etc/pam.d/su`:
    auth required pam_wheel.so use_uid group=sugroup
Warning: Note that `ensure_pam_wheel_group_empty` rule complements this requirement by ensuring the referenced group exists and has no members.
Rationale: | The `su` program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_use_pam_wheel_group_for_su
References: |
pcidss4 | 2.2.6, 2.2
cis | 5.2.7
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q pam; then

    var_pam_wheel_group_for_su='sugroup'

    PAM_CONF=/etc/pam.d/su

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - use_pam_wheel_group_for_su
    - name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
      set_fact:
        var_pam_wheel_group_for_su: !!str sugroup
      tags:
        - always

    - name: Enforce Usage of pam_wheel with Group Parameter for su Authentication - Add
        the group to the /etc/pam.d/su file
      ansible.builtin.lineinfile:
        path: /etc/pam.d/su
        state: present
        regexp: ^[\s]*#[\s]*auth[\s]+required[\s]+pam_wheel\.so[\s]+use_uid group=$
        line: auth             required        pam_wheel.so use_uid group={{ var_pam_wheel_group_for_su
          }}
      when: '"pam" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - use_pam_wheel_group_for_su
#### Rule Ensure All Accounts on the System Have Unique User IDs [ref]
Change user IDs (UIDs), or delete accounts, so each has a unique name. Warning: Automatic remediation of this control is not available due to unique requirements of each system.
Rationale: | To assure accountability and prevent unauthenticated access, interactive users must be identified and authenticated to prevent potential misuse and compromise of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_account_unique_id
References: |
pcidss | Req-8.1.1
os-srg | SRG-OS-000104-GPOS-00051, SRG-OS-000121-GPOS-00062
pcidss4 | 8.2.1, 8.2
bsi | SYS.1.3.A2
cis | 7.2.4
#### Rule Ensure All Groups on the System Have Unique Group ID [ref]
Change the group name or delete groups, so each has a unique id. Warning: Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale: | To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_group_unique_id
References: |
os-srg | SRG-OS-000104-GPOS-00051
pcidss4 | 8.2.1, 8.2
bsi | SYS.1.3.A2
cis | 7.2.5
#### Rule Ensure All Groups on the System Have Unique Group Names [ref]
Change the group name or delete groups, so each has a unique name. Warning: Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale: | To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_group_unique_name
References: |
pcidss4 | 8.2.1, 8.2
bsi | SYS.1.3.A2
cis | 7.2.7
Group Secure Session Configuration Files for Login Accounts Group contains 2 groups and 14 rules
[ref] When a user logs into a Unix account, the system configures the user's session by reading a number of files. Many of these files are located in the user's home directory, and may have weak permissions as a result of user error or misconfiguration. If an attacker can modify or even read certain types of account configuration information, they can often gain full access to the affected user's account. Therefore, it is important to test and correct configuration file permissions for interactive accounts, particularly those of privileged users such as root or system administrators.
Group Ensure that No Dangerous Directories Exist in Root's Path Group contains 2 rules
[ref] The active path of the root account can be obtained by starting a new root shell and running:
    # echo $PATH
This will produce a colon-separated list of directories in the path. Certain path elements could be considered dangerous, as they could lead to root executing unknown or untrusted programs, which could contain malicious code. Since root may sometimes work inside untrusted directories, the `.` character, which represents the current directory, should never be in the root path, nor should any directory which can be written to by an unprivileged or semi-privileged (system) user. It is a good practice for administrators to always execute privileged commands by typing the full path to the command.
#### Rule Ensure that Root's Path Does Not Include World or Group-Writable Directories [ref]
For each element in root's path, run:
    # ls -ld DIR
and ensure that write permissions are disabled for group and other.
Rationale: | Such entries increase the risk that root could execute code provided by unprivileged users, and potentially malicious code.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_root_path_dirs_no_write
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
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-6(a), CM-6(a)
nist-csf | PR.IP-1
cis | 5.4.2.5
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | restrict
    - name: Get root paths which are not symbolic links
      ansible.builtin.stat:
        path: '{{ item }}'
      changed_when: false
      failed_when: false
      register: root_paths
      with_items: '{{ ansible_env.PATH.split('':'') }}'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - accounts_root_path_dirs_no_write
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable writability to root directories
      ansible.builtin.file:
        path: '{{ item.item }}'
        mode: g-w,o-w
      with_items: '{{ root_paths.results }}'
      when:
      - root_paths.results is defined
      - item.stat.exists
      - not item.stat.islnk
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(a)
      - accounts_root_path_dirs_no_write
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure that Root's Path Does Not Include Relative Paths or Null Directories [ref]
Ensure that none of the directories in root's path is equal to a single `.` character, or that it contains any instances that lead to relative path traversal, such as `..` or beginning a path without the slash (`/`) character. Also ensure that there are no "empty" elements in the path, such as in these examples:
    PATH=:/bin
    PATH=/bin:
    PATH=/bin::/sbin
These empty elements have the same effect as a single `.` character.
Rationale: | Including these entries increases the risk that root could execute code from an untrusted location.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_root_path_no_dot
References: |
cis-csc | 11, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05
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-6(a), CM-6(a)
nist-csf | PR.IP-1
cis | 5.4.2.5
Group Ensure that Users Have Sensible Umask Values Group contains 4 rules
[ref] The umask setting controls the default permissions for the creation of new files. With a default `umask` setting of 077, files and directories created by users will not be readable by any other user on the system. Users who wish to make specific files group- or world-readable can accomplish this by using the chmod command. Additionally, users can make all their files readable to their group by default by setting a `umask` of 027 in their shell configuration files. If default per-user groups exist (that is, if every user has a default group whose name is the same as that user's username and whose only member is the user), then it may even be safe for users to select a `umask` of 007, making it very easy to intentionally share files with groups of which the user is a member.
#### Rule Ensure the Default Bash Umask is Set Correctly [ref]
To ensure the default umask for users of the Bash shell is set properly, add or correct the `umask` setting in `/etc/bashrc` to read as follows:
    umask 027
Rationale: | The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc
References: |
cis-csc | 18
cobit5 | APO13.01, BAI03.01, BAI03.02, BAI03.03
isa-62443-2009 | 4.3.4.3.3
iso27001-2013 | A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.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 | AC-6(1), CM-6(a)
nist-csf | PR.IP-2
os-srg | SRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
anssi | R36
cis | 5.4.3.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q bash; }; then

    var_accounts_user_umask='027'

    grep -q "^[^#]*\bumask" /etc/bashrc && \
      sed -i -E -e "s/^([^#]*\bumask)[[:space:]]+[[:digit:]]+/\1 $var_accounts_user_umask/g" /etc/bashrc
    if ! [ $? -eq 0 ]; then
        echo "umask $var_accounts_user_umask" >> /etc/bashrc
    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-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_bashrc
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_user_umask # promote to variable
      set_fact:
        var_accounts_user_umask: !!str 027
      tags:
        - always

    - name: Check if umask in /etc/bashrc is already set
      ansible.builtin.lineinfile:
        path: /etc/bashrc
        regexp: ^[^#]*\bumask\s+\d+$
        state: absent
      check_mode: true
      changed_when: false
      register: umask_replace
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"bash" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_bashrc
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Replace user umask in /etc/bashrc
      ansible.builtin.replace:
        path: /etc/bashrc
        regexp: ^([^#]*\b)umask\s+\d+$
        replace: \g<1>umask {{ var_accounts_user_umask }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"bash" in ansible_facts.packages'
      - umask_replace.found > 0
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_bashrc
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure the Default umask is Appended Correctly
      ansible.builtin.lineinfile:
        create: true
        path: /etc/bashrc
        line: umask {{ var_accounts_user_umask }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"bash" in ansible_facts.packages'
      - umask_replace.found == 0
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_bashrc
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure the Default Umask is Set Correctly in login.defs [ref]
To ensure the default umask controlled by `/etc/login.defs` is set properly, add or correct the `UMASK` setting in `/etc/login.defs` to read as follows:
    UMASK 027
Rationale: | The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read and written to by unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs
References: |
cis-csc | 11, 18, 3, 9
cobit5 | APO13.01, BAI03.01, BAI03.02, BAI03.03, BAI10.01, BAI10.02, BAI10.03, BAI10.05
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.1.1, A.14.2.1, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.5, A.6.1.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 | AC-6(1), CM-6(a)
nist-csf | PR.IP-1, PR.IP-2
os-srg | SRG-OS-000480-GPOS-00228
anssi | R36
cis | 5.4.3.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q shadow-utils; }; then

    var_accounts_user_umask='027'

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

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

    # 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 "^UMASK\\>" "/etc/login.defs"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^UMASK\\>.*/$escaped_formatted_output/gi" "/etc/login.defs"
    else
        if [[ -s "/etc/login.defs" ]] && [[ -n "$(tail -c 1 -- "/etc/login.defs" || true)" ]]; then
            LC_ALL=C sed -i --follow-symlinks '$a'\\ "/etc/login.defs"
        fi
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "/etc/login.defs" >> "/etc/login.defs"
        printf '%s\n' "$formatted_output" >> "/etc/login.defs"
    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-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_user_umask # promote to variable
      set_fact:
        var_accounts_user_umask: !!str 027
      tags:
        - always

    - name: Check if UMASK is already set
      ansible.builtin.lineinfile:
        path: /etc/login.defs
        regexp: ^(\s*)UMASK\s+.*
        state: absent
      check_mode: true
      changed_when: false
      register: result_umask_is_set
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Replace user UMASK in /etc/login.defs
      ansible.builtin.replace:
        path: /etc/login.defs
        regexp: ^(\s*)UMASK(\s+).*
        replace: \g<1>UMASK\g<2>{{ var_accounts_user_umask }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      - result_umask_is_set.found > 0
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure the Default UMASK is Appended Correctly
      ansible.builtin.lineinfile:
        create: true
        path: /etc/login.defs
        line: UMASK {{ var_accounts_user_umask }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"shadow-utils" in ansible_facts.packages'
      - result_umask_is_set.found == 0
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_login_defs
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure the Default Umask is Set Correctly in /etc/profile [ref]
To ensure the default umask controlled by `/etc/profile` is set properly, add or correct the `umask` setting in `/etc/profile` to read as follows:
    umask 027
Note that `/etc/profile` also reads scripts within `/etc/profile.d` directory. These scripts are also valid files to set umask value. Therefore, they should also be considered during the check and properly remediated, if necessary.
Rationale: | The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile
References: |
cis-csc | 18
cobit5 | APO13.01, BAI03.01, BAI03.02, BAI03.03
isa-62443-2009 | 4.3.4.3.3
iso27001-2013 | A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.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 | AC-6(1), CM-6(a)
nist-csf | PR.IP-2
os-srg | SRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
anssi | R36
cis | 5.4.3.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_accounts_user_umask='027'

    readarray -t profile_files < <(find /etc/profile.d/ -type f -name '*.sh' -or -name 'sh.local')

    for file in "${profile_files[@]}" /etc/profile; do
      grep -qE '^[^#]*umask' "$file" && sed -i -E "s/^(\s*umask\s*)[0-7]+/\1$var_accounts_user_umask/g" "$file"
    done

    if ! grep -qrE '^[^#]*umask' /etc/profile*; then
      echo "umask $var_accounts_user_umask" >> /etc/profile
    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-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_user_umask # promote to variable
      set_fact:
        var_accounts_user_umask: !!str 027
      tags:
        - always

    - name: Ensure the Default Umask is Set Correctly in /etc/profile - Locate Profile
        Configuration Files Where umask Is Defined
      ansible.builtin.find:
        paths:
        - /etc/profile.d
        patterns:
        - sh.local
        - '*.sh'
        contains: ^[\s]*umask\s+\d+
      register: result_profile_d_files
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure the Default Umask is Set Correctly in /etc/profile - Replace Existing
        umask Value in Files From /etc/profile.d
      ansible.builtin.replace:
        path: '{{ item.path }}'
        regexp: ^(\s*)umask\s+\d+
        replace: \1umask {{ var_accounts_user_umask }}
      loop: '{{ result_profile_d_files.files }}'
      register: result_umask_replaced_profile_d
      when:
      - '"kernel-core" in ansible_facts.packages'
      - result_profile_d_files.matched
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure the Default Umask is Set Correctly in /etc/profile - Ensure umask Is
        Set in /etc/profile if Not Already Set Elsewhere
      ansible.builtin.lineinfile:
        create: true
        mode: 420
        path: /etc/profile
        line: umask {{ var_accounts_user_umask }}
      when:
      - '"kernel-core" in ansible_facts.packages'
      - not result_profile_d_files.matched
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure the Default Umask is Set Correctly in /etc/profile - Ensure umask Value
        For All Existing umask Definition in /etc/profile
      ansible.builtin.replace:
        path: /etc/profile
        regexp: ^(\s*)umask\s+\d+
        replace: \1umask {{ var_accounts_user_umask }}
      register: result_umask_replaced_profile
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - accounts_umask_etc_profile
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure the Root Bash Umask is Set Correctly [ref]
To ensure the root user's umask of the Bash shell is set properly, add or correct the `umask` setting in `/root/.bashrc` or `/root/.profile` to read as follows:
    umask 0027
Rationale: | The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_umask_root
References: |
cis | 5.4.2.6
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q bash; }; then

    for file in /root/.bashrc /root/.profile; do
        if [ -f "$file" ]; then
            sed -i -E -e "s/^([^#]*\bumask)[[:space:]]+[[:digit:]]+/\1 0027/g" "$file"
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
#### Rule Set Interactive Session Timeout [ref]
Setting the `TMOUT` option in `/etc/profile` ensures that all user sessions will terminate based on inactivity. A value of `0` (zero) disables the automatic logout feature and is therefore not a compliant setting. The value of TMOUT should be a positive integer, exported, and read only. The `TMOUT` setting in a file loaded by `/etc/profile`, e.g. `/etc/profile.d/tmout.sh` should read as follows:
    typeset -xr TMOUT=900
or
    declare -xr TMOUT=900
Using the `typeset` keyword is preferred for wider compatibility with ksh and other shells.
Rationale: | Terminating an idle session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been left unattended.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_tmout
References: |
cis-csc | 1, 12, 15, 16
cobit5 | DSS05.04, DSS05.10, DSS06.10
cui | 3.1.11
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
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 | AC-12, SC-10, AC-2(5), CM-6(a)
nist-csf | PR.AC-7
os-srg | SRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010
anssi | R32
pcidss4 | 8.6.1, 8.6
cis | 5.4.3.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_accounts_tmout='900'

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

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

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

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
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.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSSv4-8.6
      - PCI-DSSv4-8.6.1
      - accounts_tmout
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
    - name: XCCDF Value var_accounts_tmout # promote to variable
      set_fact:
        var_accounts_tmout: !!str 900
      tags:
        - always

    - name: Correct any occurrence of TMOUT in /etc/profile
      ansible.builtin.replace:
        path: /etc/profile
        regexp: ^[^#].*TMOUT=.*
        replace: typeset -xr TMOUT={{ var_accounts_tmout }}
      register: profile_replaced
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSSv4-8.6
      - PCI-DSSv4-8.6.1
      - accounts_tmout
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Set Interactive Session Timeout
      ansible.builtin.lineinfile:
        path: /etc/profile.d/tmout.sh
        create: true
        regexp: TMOUT=
        line: typeset -xr TMOUT={{ var_accounts_tmout }}
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSSv4-8.6
      - PCI-DSSv4-8.6.1
      - accounts_tmout
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule User Initialization Files Must Be Group-Owned By The Primary Group [ref]
Change the group owner of interactive users files to the group found in
    /etc/passwd
for the user. To change the group owner of a local interactive user home directory, use the following command:
    $ sudo chgrp USER_GROUP /home/USER/.INIT_FILE
This rule ensures every initialization file related to an interactive user is group-owned by an interactive user. Warning: Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the group-ownership of their respective initialization files.
Rationale: | Local initialization files for interactive users are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_user_dot_group_ownership
References: |
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
cis | 7.2.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    awk -F: '{if ($4 >= 1000 && $4 != 65534) print $4":"$6}' /etc/passwd | while IFS=: read -r gid home; do find -P "$home" -maxdepth 1 -type f -name "\.[^.]*" -exec chgrp -f --no-dereference -- $gid "{}" \;; done

    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:
      - accounts_user_dot_group_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Group-Owned By The Primary Group - Get interactive
        users from passwd file
      ansible.builtin.getent:
        database: passwd
      register: passwd_entries
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - accounts_user_dot_group_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Group-Owned By The Primary Group - Create
        list of interactive users with GID and home directory
      ansible.builtin.set_fact:
        interactive_users: '{{ interactive_users | default([]) + [{''home'': item.value[4],
          ''gid'': item.value[2]}] }}'
      loop: '{{ passwd_entries.ansible_facts.getent_passwd | dict2items }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.value[2] | int >= 1000 | int
      - item.value[2] | int != 65534 | int
      - item.value[4] != ""
      tags:
      - accounts_user_dot_group_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Group-Owned By The Primary Group - Find
        dot files in interactive user home directories
      ansible.builtin.find:
        paths: '{{ item.home }}'
        patterns: .*
        file_type: file
        hidden: true
        depth: 1
        follow: false
      register: user_dotfiles
      loop: '{{ interactive_users | default([]) }}'
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.home != ""
      tags:
      - accounts_user_dot_group_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Group-Owned By The Primary Group - Set correct
        group ownership for user initialization files
      ansible.builtin.file:
        path: '{{ item.1.path }}'
        group: '{{ item.0.item.gid }}'
        follow: false
      loop: '{{ user_dotfiles.results | subelements(''files'', skip_missing=True) }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.0 is not skipped
      - item.1.path is defined
      tags:
      - accounts_user_dot_group_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule User Initialization Files Must Be Owned By the Primary User [ref]
Set the owner of the user initialization files for interactive users to the primary owner with the following command:
    $ sudo chown USER /home/USER/.*
This rule ensures every initialization file related to an interactive user is owned by an interactive user. Warning: Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the ownership of their respective initialization files.
Rationale: | Local initialization files are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_user_dot_user_ownership
References: |
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
cis | 7.2.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    awk -F: '{if ($3 >= 1000 && $3 != 65534) print $3":"$6}' /etc/passwd | while IFS=: read -r uid home; do find -P "$home" -maxdepth 1 -type f -name "\.[^.]*" -exec chown -f --no-dereference -- $uid "{}" \;; done

    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:
      - accounts_user_dot_user_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Owned By the Primary User - Get interactive
        users from passwd file
      ansible.builtin.getent:
        database: passwd
      register: passwd_entries
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - accounts_user_dot_user_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Owned By the Primary User - Create list
        of interactive users with UID and home directory
      ansible.builtin.set_fact:
        interactive_users: '{{ interactive_users | default([]) + [{''uid'': item.value[1],
          ''home'': item.value[4], ''username'': item.key}] }}'
      loop: '{{ passwd_entries.ansible_facts.getent_passwd | dict2items }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.value[1] | int >= 1000 | int
      - item.value[1] | int != 65534 | int
      - item.value[4] != ""
      tags:
      - accounts_user_dot_user_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Owned By the Primary User - Find dot files
        in interactive user home directories
      ansible.builtin.find:
        paths: '{{ item.home }}'
        patterns: .*
        file_type: file
        hidden: true
        depth: 1
        follow: false
      register: user_dotfiles
      loop: '{{ interactive_users | default([]) }}'
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.home != ""
      tags:
      - accounts_user_dot_user_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: User Initialization Files Must Be Owned By the Primary User - Set correct
        ownership for user initialization files
      ansible.builtin.file:
        path: '{{ item.1.path }}'
        owner: '{{ item.0.item.username }}'
        follow: false
      loop: '{{ user_dotfiles.results | subelements(''files'', skip_missing=True) }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.0 is not skipped
      - item.0 is not failed
      - item.0.item is defined
      - item.0.item.username is defined
      - item.1.path is defined
      tags:
      - accounts_user_dot_user_ownership
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule All Interactive Users Home Directories Must Exist [ref]
Create home directories to all local interactive users that currently do not have a home directory assigned. Use the following commands to create the user home directory assigned in `/etc/passwd`:
    $ sudo mkdir /home/USER
Rationale: | If a local interactive user has a home directory defined that does not exist, the user may be given access to the / directory as the current working directory upon logon. This could create a Denial of Service because the user would not be able to access their logon configuration files, and it may give them visibility to system files they normally would not be able to access.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_accounts_user_interactive_home_directory_exists
References: |
os-srg | SRG-OS-000480-GPOS-00227
cis | 7.2.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    for user in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534) print $1}' /etc/passwd); do
        mkhomedir_helper $user 0077;
    done

    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:
      - accounts_user_interactive_home_directory_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Get all local users from /etc/passwd
      ansible.builtin.getent:
        database: passwd
        split: ':'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - accounts_user_interactive_home_directory_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Create local_users variable from the getent output
      ansible.builtin.set_fact:
        local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - accounts_user_interactive_home_directory_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure interactive users have a home directory exists
      ansible.builtin.user:
        name: '{{ item.key }}'
        create_home: true
      loop: '{{ local_users }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - item.value[1]|int >= 1000
      - item.value[1]|int != 65534
      tags:
      - accounts_user_interactive_home_directory_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule All Interactive User Home Directories Must Be Owned By The Primary User [ref]
Change the owner of interactive users home directories to that correct owner. To change the owner of a interactive users home directory, use the following command:
    $ sudo chown USER /home/USER
This rule ensures every home directory related to an interactive user is owned by an interactive user. It also ensures that interactive users are owners of one and only one home directory. Warning: Due to OVAL limitation, this rule can report a false negative in a specific situation where two interactive users swap the ownership of their respective home directories.
Rationale: | If a local interactive user does not own their home directory, unauthorized users could access or modify the user's files, and the users may not be able to access their own files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_ownership_home_directories
References: |
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 7.2.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    awk -F':' '{ if ($3 >= 1000 && $3 != 65534) system("chown -f " $3" "$6) }' /etc/passwd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Get all local users from /etc/passwd
      ansible.builtin.getent:
        database: passwd
        split: ':'
      tags:
      - file_ownership_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Create local_users variable from the getent output
      ansible.builtin.set_fact:
        local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
      tags:
      - file_ownership_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Test for existence of home directories to avoid creating them, but only fixing
        ownership
      ansible.builtin.stat:
        path: '{{ item.value[4] }}'
      register: path_exists
      loop: '{{ local_users }}'
      when:
      - item.value[1]|int >= 1000
      - item.value[1]|int != 65534
      tags:
      - file_ownership_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure interactive local users are the owners of their respective home directories
      ansible.builtin.file:
        path: '{{ item.0.value[4] }}'
        owner: '{{ item.0.value[1] }}'
      loop: '{{ local_users|zip(path_exists.results)|list }}'
      when: item.1.stat is defined and item.1.stat.exists
      tags:
      - file_ownership_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure User Bash History File Has Correct Permissions [ref]
Set the mode of the bash history file to `0600` with the following command:
    $ sudo chmod 0600 /home/USER/.bash_history
Rationale: | Incorrect permissions may enable malicious users to recover other users' command history.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permission_user_bash_history
References: |
bsi | SYS.1.3.A14
cis | 7.2.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    readarray -t interactive_users < <(awk -F: '$3>=1000   {print $1}' /etc/passwd)
    readarray -t interactive_users_home < <(awk -F: '$3>=1000   {print $6}' /etc/passwd)
    readarray -t interactive_users_shell < <(awk -F: '$3>=1000   {print $7}' /etc/passwd)

    USERS_IGNORED_REGEX='nobody|nfsnobody'

    for (( i=0; i<"${#interactive_users[@]}"; i++ )); do
        if ! grep -qP "$USERS_IGNORED_REGEX" <<< "${interactive_users[$i]}" && \
            [ "${interactive_users_shell[$i]}" != "/sbin/nologin" ]; then

            chmod u-sx,go= "${interactive_users_home[$i]}/.bash_history"
        fi
    done
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Ensure User Bash History File Has Correct Permissions - Gather User Info
      ansible.builtin.getent:
        database: passwd
      tags:
      - file_permission_user_bash_history
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure User Bash History File Has Correct Permissions - Check Bash History
        Files Existence
      ansible.builtin.stat:
        path: '{{ item.value[4] }}/.bash_history'
      register: bash_history_files
      with_dict: '{{ ansible_facts.getent_passwd }}'
      when:
      - item.value[4] != "/sbin/nologin"
      - item.key not in ["nobody", "nfsnobody"]
      - item.value[1] | int >= 1000
      tags:
      - file_permission_user_bash_history
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure User Bash History File Has Correct Permissions - Fix Bash History Files
        Permissions
      ansible.builtin.file:
        path: '{{ item.stat.path }}'
        mode: u-sx,go=
      with_items: '{{ bash_history_files.results }}'
      when:
      - item.stat is defined
      - item.stat.exists
      tags:
      - file_permission_user_bash_history
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure All User Initialization Files Have Mode 0740 Or Less Permissive [ref]
Set the mode of the user initialization files to `0740` with the following command:
    $ sudo chmod 0740 /home/USER/.INIT_FILE
Rationale: | Local initialization files are used to configure the user's shell environment upon logon. Malicious modification of these files could compromise accounts upon logon.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permission_user_init_files
References: |
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
cis | 7.2.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    var_user_initialization_files_regex='^\.[\w\- ]+$'

    readarray -t interactive_users < <(awk -F: '$3>=1000   {print $1}' /etc/passwd)
    readarray -t interactive_users_home < <(awk -F: '$3>=1000   {print $6}' /etc/passwd)
    readarray -t interactive_users_shell < <(awk -F: '$3>=1000   {print $7}' /etc/passwd)

    USERS_IGNORED_REGEX='nobody|nfsnobody'

    for (( i=0; i<"${#interactive_users[@]}"; i++ )); do
        if ! grep -qP "$USERS_IGNORED_REGEX" <<< "${interactive_users[$i]}" && \
            [ "${interactive_users_shell[$i]}" != "/sbin/nologin" ]; then

            readarray -t init_files < <(find "${interactive_users_home[$i]}" -maxdepth 1 \
                -exec basename {} \; | grep -P "$var_user_initialization_files_regex")
            for file in "${init_files[@]}"; do
                chmod u-s,g-wxs,o= "${interactive_users_home[$i]}/$file"
            done
        fi
    done
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: XCCDF Value var_user_initialization_files_regex # promote to variable
      set_fact:
        var_user_initialization_files_regex: !!str ^\.[\w\- ]+$
      tags:
        - always

    - name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Gather
        User Info
      ansible.builtin.getent:
        database: passwd
      tags:
      - file_permission_user_init_files
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Find
        Init Files
      ansible.builtin.find:
        paths: '{{ item.value[4] }}'
        pattern: '{{ var_user_initialization_files_regex }}'
        hidden: true
        use_regex: true
      with_dict: '{{ ansible_facts.getent_passwd }}'
      when:
      - item.value[4] != "/sbin/nologin"
      - item.key not in ["nobody", "nfsnobody"]
      - item.value[1] | int >= 1000
      register: found_init_files
      tags:
      - file_permission_user_init_files
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure All User Initialization Files Have Mode 0740 Or Less Permissive - Fix
        Init Files Permissions
      ansible.builtin.file:
        path: '{{ item.1.path }}'
        mode: u-s,g-wxs,o=
      loop: '{{ q(''ansible.builtin.subelements'', found_init_files.results, ''files'',
        {''skip_missing'': True}) }}'
      tags:
      - file_permission_user_init_files
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule All Interactive User Home Directories Must Have mode 0750 Or Less Permissive [ref]
Change the mode of interactive users home directories to `0750`. To change the mode of interactive users home directory, use the following command:
    $ sudo chmod 0750 /home/USER
Rationale: | Excessive permissions on local interactive user home directories may allow unauthorized access to user files by other users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_home_directories
References: |
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 7.2.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    for home_dir in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534 && $6 != "/") print $6 }' /etc/passwd); do
        # Only update the permissions when necessary. This will avoid changing the inode timestamp when
        # the permission is already defined as expected, therefore not impacting in possible integrity
        # check systems that also check inodes timestamps.
        find "$home_dir" -maxdepth 0 -perm /7027 \! -type l -exec chmod u-s,g-w-s,o=- {} \;
    done
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Get all local users from /etc/passwd
      ansible.builtin.getent:
        database: passwd
        split: ':'
      tags:
      - file_permissions_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Create local_users variable from the getent output
      ansible.builtin.set_fact:
        local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
      tags:
      - file_permissions_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Test for existence home directories to avoid creating them.
      ansible.builtin.stat:
        path: '{{ item.value[4] }}'
      register: path_exists
      loop: '{{ local_users }}'
      when:
      - item.value[1]|int >= 1000
      - item.value[1]|int != 65534
      - item.value[4] != "/"
      tags:
      - file_permissions_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure interactive local users have proper permissions on their respective
        home directories
      ansible.builtin.file:
        path: '{{ item.0.value[4] }}'
        mode: u-s,g-w-s,o=-
        follow: false
        recurse: false
      loop: '{{ local_users|zip(path_exists.results)|list }}'
      when: item.1.stat is defined and item.1.stat.exists
      tags:
      - file_permissions_home_directories
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group GRUB2 bootloader configuration Group contains 1 group and 4 rules
[ref] During the boot process, the boot loader is responsible for starting the execution of the kernel and passing options to it. The boot loader allows for the selection of different kernels - possibly on different partitions or media. The default Red Hat Enterprise Linux 10 boot loader for x86 systems is called GRUB2. Options it can pass to the kernel include single-user mode, which provides root access without any authentication, and the ability to disable SELinux. To prevent local users from modifying the boot parameters and endangering security, protect the boot loader configuration with a password and ensure its configuration file's permissions are set properly.
Group Non-UEFI GRUB2 bootloader configuration Group contains 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
cis | 1.4.1
#### Rule All GRUB configuration files must be group-owned by root [ref]
The files in `/boot/grub2` should be group-owned by the `root` group to prevent destruction or modification of the file. To properly set the group owner of `/boot/grub2`, run the command:
    $ sudo chgrp root /boot/grub2
Rationale: | The `root` group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_boot_grub2
References: |
cis | 1.4.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel-core ); then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /boot/grub2/ -maxdepth 1 -type f  ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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:
      - configure_strategy
      - file_groupowner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_boot_grub2_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_boot_grub2_newgroup: '0'
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_groupowner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /boot/grub2/ file(s) matching ^.*$
      ansible.builtin.command: find -P /boot/grub2/ -maxdepth 1 -type f  ! -group 0 -regextype
        posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_groupowner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /boot/grub2/ file(s) matching ^.*$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        group: '{{ file_groupowner_boot_grub2_newgroup }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_groupowner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule All GRUB configuration files must be owned by root [ref]
The files in `/boot/grub2` should be owned by the `root` user to prevent destruction or modification of the file. To properly set the owner of `/boot/grub2`, run the command:
    $ sudo chown root /boot/grub2
Rationale: | To prevent unauthorized access and modifications to boot configuration.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_boot_grub2
References: |
cis | 1.4.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel-core ); then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else

    find -P /boot/grub2/ -maxdepth 1 -type f  ! -user 0 -regextype posix-extended -regex '^.*$' -exec chown --no-dereference "$newown" {} \;

    fi

    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:
      - configure_strategy
      - file_owner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_boot_grub2_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_boot_grub2_newown: '0'
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_owner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /boot/grub2/ file(s) matching ^.*$
      ansible.builtin.command: find -P /boot/grub2/ -maxdepth 1 -type f  ! -user 0 -regextype
        posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_owner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /boot/grub2/ file(s) matching ^.*$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        owner: '{{ file_owner_boot_grub2_newown }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_owner_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule All GRUB configuration files must have mode 0600 or more restrictive [ref]
The files in `/boot/grub2` should have mode `0600` to prevent destruction or modification of the file. To properly set the permissions of `/boot/grub2`, run the command:
    $ sudo chmod 0600 /boot/grub2
Rationale: | The file mode 0600 prevents unauthorized access and modifications to boot settings.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_boot_grub2
References: |
cis | 1.4.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if ( rpm --quiet -q grub2-common && rpm --quiet -q kernel-core ); then

    find -P /boot/grub2/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt  -type f -regextype posix-extended -regex '^.*$' -exec chmod u-xs,g-xwrs,o-xwrt {} \;

    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:
      - configure_strategy
      - file_permissions_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /boot/grub2/ file(s)
      ansible.builtin.command: find -P /boot/grub2/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt  -type
        f -regextype posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_permissions_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /boot/grub2/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-xs,g-xwrs,o-xwrt
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: ( "grub2-common" in ansible_facts.packages and "kernel-core" in ansible_facts.packages
        )
      tags:
      - configure_strategy
      - file_permissions_boot_grub2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Group Configure Syslog Group contains 2 groups and 11 rules
[ref] The syslog service has been the default Unix logging mechanism for many years. It has a number of downsides, including inconsistent log format, lack of authentication for received messages, and lack of authentication, encryption, or reliable transport for messages sent over a network. However, due to its long history, syslog is a de facto standard which is supported by almost all Unix applications. In Red Hat Enterprise Linux 10, rsyslog has replaced ksyslogd as the syslog daemon of choice, and it includes some additional security features such as reliable, connection-oriented (i.e. TCP) transmission of logs, the option to log to database formats, and the encryption of log data en route to a central logging server. This section discusses how to configure rsyslog for best effect, and how to use tools provided with the system to maintain and monitor logs.
Group Ensure Proper Configuration of Log Files Group contains 3 rules
[ref] The file `/etc/rsyslog.conf` controls where log message are written. These are controlled by lines called rules, which consist of a selector and an action. These rules are often customized depending on the role of the system, the requirements of the environment, and whatever may enable the administrator to most effectively make use of log data. The default rules in Red Hat Enterprise Linux 10 are:
    *.info;mail.none;authpriv.none;cron.none                /var/log/messages
    authpriv.*                                              /var/log/secure
    mail.*                                                  -/var/log/maillog
    cron.*                                                  /var/log/cron
    *.emerg                                                 *
    uucp,news.crit                                          /var/log/spooler
    local7.*                                                /var/log/boot.log
See the man page `rsyslog.conf(5)` for more information. Note that the `rsyslog` daemon can be configured to use a timestamp format that some log processing programs may not understand. If this occurs, edit the file `/etc/rsyslog.conf` and add or edit the following line:
    $ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
#### Rule Ensure Log Files Are Owned By Appropriate Group [ref]
The group-owner of all log files written by `rsyslog` should be `root`. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's group owner:
    $ ls -l LOGFILE
If the owner is not `root`, run the following command to correct this:
    $ sudo chgrp root LOGFILE
Rationale: | The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_rsyslog_files_groupownership
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-10.5.1, Req-10.5.2
anssi | R71
ism | 0988, 1405
pcidss4 | 10.3.2, 10.3
cis | 6.2.4.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && rpm --quiet -q rsyslog; then

    # List of log file paths to be inspected for correct permissions
    # * Primarily inspect log file paths listed in /etc/rsyslog.conf
    RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
    # * And also the log file paths listed after rsyslog's $IncludeConfig directive
    #   (store the result into array for the case there's shell glob used as value of IncludeConfig)
    readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
    readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
    readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
    readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

    # Declare an array to hold the final list of different log file paths
    declare -a LOG_FILE_PATHS

    # Array to hold all rsyslog config entries
    RSYSLOG_CONFIGS=()
    RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

    # Get full list of files to be checked
    # RSYSLOG_CONFIGS may contain globs such as
    # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
    # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
    RSYSLOG_CONFIG_FILES=()
    for ENTRY in "${RSYSLOG_CONFIGS[@]}"
    do
    	# If directory, rsyslog will search for config files in recursively.
    	# However, files in hidden sub-directories or hidden files will be ignored.
    	if [ -d "${ENTRY}" ]
    	then
    		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
    		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
    	elif [ -f "${ENTRY}" ]
    	then
    		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
    	else
    		echo "Invalid include object: ${ENTRY}"
    	fi
    done

    # Browse each file selected above as containing paths of log files
    # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	# From each of these files extract just particular log file path(s), thus:
    	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
    	# * Ignore empty lines,
    	# * Strip quotes and closing brackets from paths.
    	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
    	# * From the remaining valid rows select only fields constituting a log file path
    	# Text file column is understood to represent a log file path if and only if all of the
    	# following are met:
    	# * it contains at least one slash '/' character,
    	# * it is preceded by space
    	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
    	# Search log file for path(s) only in case it exists!
    	if [[ -f "${LOG_FILE}" ]]
    	then
    		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
    		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
    		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
    		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
    		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
    		# Since above sed command might return more than one item (delimited by newline), split
    		# the particular matches entries into new array specific for this log file
    		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
    		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
    		# items from newly created array for this log file
    		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
    		# Delete the temporary array
    		unset ARRAY_FOR_LOG_FILE
    	fi
    done

    # Check for RainerScript action log format which might be also multiline so grep regex is a bit
    # curly:
    # extract possibly multiline action omfile expressions
    # extract File="logfile" expression
    # match only "logfile" expression
    # exclude /dev/* paths (e.g., /dev/console)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
    	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
    	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"" | grep -v "^/dev/")")
    done

    # Ensure the correct attribute if file exists
    FILE_CMD="chgrp"
    for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
    do
    	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
    	if [ -z "$LOG_FILE_PATH" ]
    	then
    		continue
    	fi
    	$FILE_CMD "root" "$LOG_FILE_PATH"
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership

    - name: Ensure Log Files Are Owned By Appropriate Group - Set rsyslog logfile configuration
        facts
      ansible.builtin.set_fact:
        rsyslog_etc_config: /etc/rsyslog.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership

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

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

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

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

    - name: Ensure Log Files Are Owned By Appropriate Group - Extract log files old format
      ansible.builtin.shell: |
        set -o pipefail
        grep -oP '^[^(\s|#|\$)]+[\s]*.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
        awk '{print $NF}' | \
        sed -e 's/^-//' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_old
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership

    - name: Ensure Log Files Are Owned By Appropriate Group - Extract log files new format
      ansible.builtin.shell: |
        set -o pipefail
        grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
        grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
        grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
        tr -d "\"" | \
        grep -v '^/dev/' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_new
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership

    - name: Ensure Log Files Are Owned By Appropriate Group - Sum all log files found
      ansible.builtin.set_fact:
        log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
          | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
          | list | flatten | unique }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership

    - name: Ensure Log Files Are Owned By Appropriate Group -Setup log files attribute
      ansible.builtin.file:
        path: '{{ item }}'
        group: root
        state: file
      loop: '{{ log_files | list | flatten | unique }}'
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_groupownership
#### Rule Ensure Log Files Are Owned By Appropriate User [ref]
The owner of all log files written by `rsyslog` should be `root`. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's owner:
    $ ls -l LOGFILE
If the owner is not `root`, run the following command to correct this:
    $ sudo chown root LOGFILE
Rationale: | The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_rsyslog_files_ownership
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-10.5.1, Req-10.5.2
anssi | R71
ism | 0988, 1405
pcidss4 | 10.3.2, 10.3
cis | 6.2.4.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && rpm --quiet -q rsyslog; then

    # List of log file paths to be inspected for correct permissions
    # * Primarily inspect log file paths listed in /etc/rsyslog.conf
    RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
    # * And also the log file paths listed after rsyslog's $IncludeConfig directive
    #   (store the result into array for the case there's shell glob used as value of IncludeConfig)
    readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
    readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
    readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
    readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

    # Declare an array to hold the final list of different log file paths
    declare -a LOG_FILE_PATHS

    # Array to hold all rsyslog config entries
    RSYSLOG_CONFIGS=()
    RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

    # Get full list of files to be checked
    # RSYSLOG_CONFIGS may contain globs such as
    # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
    # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
    RSYSLOG_CONFIG_FILES=()
    for ENTRY in "${RSYSLOG_CONFIGS[@]}"
    do
    	# If directory, rsyslog will search for config files in recursively.
    	# However, files in hidden sub-directories or hidden files will be ignored.
    	if [ -d "${ENTRY}" ]
    	then
    		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
    		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
    	elif [ -f "${ENTRY}" ]
    	then
    		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
    	else
    		echo "Invalid include object: ${ENTRY}"
    	fi
    done

    # Browse each file selected above as containing paths of log files
    # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	# From each of these files extract just particular log file path(s), thus:
    	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
    	# * Ignore empty lines,
    	# * Strip quotes and closing brackets from paths.
    	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
    	# * From the remaining valid rows select only fields constituting a log file path
    	# Text file column is understood to represent a log file path if and only if all of the
    	# following are met:
    	# * it contains at least one slash '/' character,
    	# * it is preceded by space
    	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
    	# Search log file for path(s) only in case it exists!
    	if [[ -f "${LOG_FILE}" ]]
    	then
    		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
    		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
    		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
    		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
    		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
    		# Since above sed command might return more than one item (delimited by newline), split
    		# the particular matches entries into new array specific for this log file
    		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
    		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
    		# items from newly created array for this log file
    		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
    		# Delete the temporary array
    		unset ARRAY_FOR_LOG_FILE
    	fi
    done

    # Check for RainerScript action log format which might be also multiline so grep regex is a bit
    # curly:
    # extract possibly multiline action omfile expressions
    # extract File="logfile" expression
    # match only "logfile" expression
    # exclude /dev/* paths (e.g., /dev/console)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
    	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
    	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"" | grep -v "^/dev/")")
    done

    # Ensure the correct attribute if file exists
    FILE_CMD="chown"
    for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
    do
    	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
    	if [ -z "$LOG_FILE_PATH" ]
    	then
    		continue
    	fi
    	$FILE_CMD "root" "$LOG_FILE_PATH"
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - Set rsyslog logfile configuration
        facts
      ansible.builtin.set_fact:
        rsyslog_etc_config: /etc/rsyslog.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - Get IncludeConfig directive
      ansible.builtin.shell: |
        set -o pipefail
        grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
      register: rsyslog_old_inc
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

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

    - name: Ensure Log Files Are Owned By Appropriate User - Aggregate rsyslog includes
      ansible.builtin.set_fact:
        include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
          }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - List all config files
      ansible.builtin.find:
        paths: '{{ item | dirname }}'
        patterns: '{{ item | basename }}'
        hidden: false
        follow: true
      loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - include_config_output is defined
      register: rsyslog_config_files
      failed_when: false
      changed_when: false
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - Extract log files old format
      ansible.builtin.shell: |
        set -o pipefail
        grep -oP '^[^(\s|#|\$)]+[\s]*.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
        awk '{print $NF}' | \
        sed -e 's/^-//' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_old
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - Extract log files new format
      ansible.builtin.shell: |
        set -o pipefail
        grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
        grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
        grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
        tr -d "\"" | \
        grep -v '^/dev/' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_new
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User - Sum all log files found
      ansible.builtin.set_fact:
        log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
          | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
          | list | flatten | unique }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership

    - name: Ensure Log Files Are Owned By Appropriate User -Setup log files attribute
      ansible.builtin.file:
        path: '{{ item }}'
        owner: root
        state: file
      loop: '{{ log_files | list | flatten | unique }}'
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.2
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_ownership
#### Rule Ensure System Log Files Have Correct Permissions [ref]
The file permissions for all log files written by `rsyslog` should be set to 640, or more restrictive. These log files are determined by the second part of each Rule line in `/etc/rsyslog.conf` and typically all appear in `/var/log`. For each log file LOGFILE referenced in `/etc/rsyslog.conf`, run the following command to inspect the file's permissions:
    $ ls -l LOGFILE
If the permissions are not 640 or more restrictive, run the following command to correct this:
    $ sudo chmod 640 LOGFILE
"
Rationale: | Log files can contain valuable information regarding system configuration. If the system log files are not protected unauthorized users could change the logged data, eliminating their forensic value.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_rsyslog_files_permissions
References: |
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)
pcidss | Req-10.5.1, Req-10.5.2
anssi | R71
ism | 0988, 1405
pcidss4 | 10.3.1, 10.3
cis | 6.2.4.1
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && rpm --quiet -q rsyslog; then

    # List of log file paths to be inspected for correct permissions
    # * Primarily inspect log file paths listed in /etc/rsyslog.conf
    RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
    # * And also the log file paths listed after rsyslog's $IncludeConfig directive
    #   (store the result into array for the case there's shell glob used as value of IncludeConfig)
    readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
    readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
    readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
    readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

    # Declare an array to hold the final list of different log file paths
    declare -a LOG_FILE_PATHS

    # Array to hold all rsyslog config entries
    RSYSLOG_CONFIGS=()
    RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

    # Get full list of files to be checked
    # RSYSLOG_CONFIGS may contain globs such as
    # /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
    # So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
    RSYSLOG_CONFIG_FILES=()
    for ENTRY in "${RSYSLOG_CONFIGS[@]}"
    do
    	# If directory, rsyslog will search for config files in recursively.
    	# However, files in hidden sub-directories or hidden files will be ignored.
    	if [ -d "${ENTRY}" ]
    	then
    		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
    		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
    	elif [ -f "${ENTRY}" ]
    	then
    		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
    	else
    		echo "Invalid include object: ${ENTRY}"
    	fi
    done

    # Browse each file selected above as containing paths of log files
    # ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	# From each of these files extract just particular log file path(s), thus:
    	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
    	# * Ignore empty lines,
    	# * Strip quotes and closing brackets from paths.
    	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
    	# * From the remaining valid rows select only fields constituting a log file path
    	# Text file column is understood to represent a log file path if and only if all of the
    	# following are met:
    	# * it contains at least one slash '/' character,
    	# * it is preceded by space
    	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
    	# Search log file for path(s) only in case it exists!
    	if [[ -f "${LOG_FILE}" ]]
    	then
    		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
    		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
    		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
    		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
    		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
    		# Since above sed command might return more than one item (delimited by newline), split
    		# the particular matches entries into new array specific for this log file
    		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
    		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
    		# items from newly created array for this log file
    		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
    		# Delete the temporary array
    		unset ARRAY_FOR_LOG_FILE
    	fi
    done

    # Check for RainerScript action log format which might be also multiline so grep regex is a bit
    # curly:
    # extract possibly multiline action omfile expressions
    # extract File="logfile" expression
    # match only "logfile" expression
    # exclude /dev/* paths (e.g., /dev/console)
    for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
    do
    	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
    	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
    	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"" | grep -v "^/dev/")")
    done

    # Ensure the correct attribute if file exists
    FILE_CMD="chmod"
    for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
    do
    	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
    	if [ -z "$LOG_FILE_PATH" ]
    	then
    		continue
    	fi
    	$FILE_CMD "0640" "$LOG_FILE_PATH"
    done

    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:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Set rsyslog logfile configuration
        facts
      ansible.builtin.set_fact:
        rsyslog_etc_config: /etc/rsyslog.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Get IncludeConfig directive
      ansible.builtin.shell: |
        set -o pipefail
        grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
      register: rsyslog_old_inc
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Get include files directives
      ansible.builtin.shell: |
        set -o pipefail
        awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
      register: rsyslog_new_inc
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Aggregate rsyslog includes
      ansible.builtin.set_fact:
        include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
          }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - List all config files
      ansible.builtin.find:
        paths: '{{ item | dirname }}'
        patterns: '{{ item | basename }}'
        hidden: false
        follow: true
      loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - include_config_output is defined
      register: rsyslog_config_files
      failed_when: false
      changed_when: false
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Extract log files old format
      ansible.builtin.shell: |
        set -o pipefail
        grep -oP '^[^(\s|#|\$)]+[\s]*.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
        awk '{print $NF}' | \
        sed -e 's/^-//' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_old
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Extract log files new format
      ansible.builtin.shell: |
        set -o pipefail
        grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
        grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
        grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
        tr -d "\"" | \
        grep -v '^/dev/' || true
      loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
      register: log_files_new
      changed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      - rsyslog_config_files is not skipped
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions - Sum all log files found
      ansible.builtin.set_fact:
        log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
          | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
          | list | flatten | unique }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions

    - name: Ensure System Log Files Have Correct Permissions -Setup log files attribute
      ansible.builtin.file:
        path: '{{ item }}'
        mode: '0640'
        state: file
      loop: '{{ log_files | list | flatten | unique }}'
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"rsyslog" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.5.1
      - PCI-DSS-Req-10.5.2
      - PCI-DSSv4-10.3
      - PCI-DSSv4-10.3.1
      - configure_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - no_reboot_needed
      - rsyslog_files_permissions
Group systemd-journald Group contains 7 rules
[ref] systemd-journald is a system service that collects and stores logging data. It creates and maintains structured, indexed journals based on logging information that is received from a variety of sources. For more information on `systemd-journald` and additional `systemd-journald` configuration options, see https://systemd.io/.
#### Rule Install systemd-journal-remote Package [ref]
Journald (via systemd-journal-remote ) supports the ability to send log events it gathers to a remote log host or to receive messages from remote hosts, thus enabling centralised log management.
Rationale: | Storing log data on a remote host protects log integrity from local attacks. If an attacker gains root access on the local system, they could tamper with or remove log data that is stored on the local system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_systemd-journal-remote_installed
References: |
os-srg | SRG-OS-000479-GPOS-00224
cis | 6.2.2.1.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install systemd-journal-remote
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "systemd-journal-remote"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    if ! rpm -q --quiet "systemd-journal-remote" ; then
        dnf install -y "systemd-journal-remote"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install systemd-journal-remote
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=systemd-journal-remote
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_systemd-journal-remote_installed

    - name: Ensure systemd-journal-remote is installed
      ansible.builtin.package:
        name: systemd-journal-remote
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_systemd-journal-remote_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_systemd-journal-remote

    class install_systemd-journal-remote {
      package { 'systemd-journal-remote':
        ensure => 'installed',
      }
    }
#### Rule Enable systemd-journal-upload Service [ref]
The `systemd-journal-upload` service is part of the `systemd-journal-remote` package and enables centralized logging by uploading local systemd journal entries to a remote log server via HTTPS. This service acts as a client that pushes journal data to a remote host running the `systemd-journal-remote` receiver service. The `systemd-journal-upload` service can be enabled with the following command:
    $ sudo systemctl enable systemd-journal-upload.service
Warning: The `systemd-journal-upload` service will fail to start if the remote server URL is not configured. Edit `/etc/systemd/journal-upload.conf` to configure the remote server URL.
Rationale: | Centralized logging through `systemd-journal-upload` is essential for security monitoring, incident response, and compliance requirements. Storing log data on a remote host protects log integrity from local attacks. If an attacker gains root access on the local system, they could tamper with or remove log data stored locally to hide their activities. Remote logging ensures that audit trails remain intact even if the local system is compromised. Additionally, centralized logs facilitate correlation of events across multiple systems, enabling better detection of distributed attacks and security incidents.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_systemd-journal-upload_enabled
References: |
os-srg | SRG-OS-000479-GPOS-00224
cis | 6.2.2.1.3
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["systemd-journal-upload"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { ( ( [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q systemd-journal-remote ) ); }; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service enable systemd-journal-upload
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
      - service_systemd-journal-upload_enabled

    - name: Enable systemd-journal-upload Service - Enable service systemd-journal-upload
      block:

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

      - name: Enable systemd-journal-upload Service - Enable Service systemd-journal-upload
        ansible.builtin.systemd:
          name: systemd-journal-upload
          enabled: true
          state: started
          masked: false
        when:
        - '"systemd-journal-remote" in ansible_facts.packages'
      tags:
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-journal-upload_enabled
      - special_service_block
      when:
      - '"kernel-core" in ansible_facts.packages'
      - ( ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        and "systemd-journal-remote" in ansible_facts.packages )
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_systemd-journal-upload

    class enable_systemd-journal-upload {
      service {'systemd-journal-upload':
        enable => true,
        ensure => 'running',
      }
    }
#### Rule Enable systemd-journald Service [ref]
The `systemd-journald` service is an essential component of systemd. The `systemd-journald` service can be enabled with the following command:
    $ sudo systemctl enable systemd-journald.service
Rationale: | In the event of a system failure, Red Hat Enterprise Linux 10 must preserve any information necessary to determine cause of failure and any information necessary to return to operations with least disruption to system processes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_systemd-journald_enabled
References: |
nist | SC-24
os-srg | SRG-OS-000269-GPOS-00103
cis | 6.2.1.1
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["systemd-journald"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service enable systemd-journald
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-SC-24
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-journald_enabled

    - name: Enable systemd-journald Service - Enable service systemd-journald
      block:

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

      - name: Enable systemd-journald Service - Enable Service systemd-journald
        ansible.builtin.systemd:
          name: systemd-journald
          enabled: true
          state: started
          masked: false
        when:
        - '"systemd" in ansible_facts.packages'
      tags:
      - NIST-800-53-SC-24
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_systemd-journald_enabled
      - special_service_block
      when: '"kernel-core" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_systemd-journald

    class enable_systemd-journald {
      service {'systemd-journald':
        enable => true,
        ensure => 'running',
      }
    }
#### Rule Ensure journald is configured to compress large log files [ref]
The journald system can compress large log files to avoid fill the system disk.
Rationale: | Log files that are not properly compressed run the risk of growing so large that they fill up the log partition. Valuable logging information could be lost if the log partition becomes full.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_journald_compress
References: |
cis | 6.2.2.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    found=false

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

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

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

            fi

            found=true

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

                sed -i "/^[[:space:]]*\[Journal\]/a Compress=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/systemd/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Journal]\nCompress=yes" >> "$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:
      - journald_compress
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to compress large log files - Search for a section
        in files
      ansible.builtin.find:
        paths: '{{item.path}}'
        patterns: '{{item.pattern}}'
        contains: ^\s*\[Journal\]
        read_whole_file: true
        use_regex: true
      register: systemd_dropin_files_with_section
      loop:
      - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
        pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
      - path: /etc/systemd/journald.conf.d
        pattern: .*\.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - journald_compress
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to compress large log files - Count number of
        files which contain the correct section
      ansible.builtin.set_fact:
        count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
          | map(attribute=''matched'') | list | map(''int'') | sum}}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - journald_compress
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to compress large log files - Add missing configuration
        to correct section
      community.general.ini_file:
        path: '{{item}}'
        section: Journal
        option: Compress
        value: 'yes'
        state: present
        no_extra_spaces: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int > 0
      loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
        | map(attribute=''path'') | list }}'
      tags:
      - journald_compress
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to compress large log files - Add configuration
        to new remediation file
      community.general.ini_file:
        path: /etc/systemd/journald.conf.d/complianceascode_hardening.conf
        section: Journal
        option: Compress
        value: 'yes'
        state: present
        no_extra_spaces: true
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int == 0
      tags:
      - journald_compress
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure journald ForwardToSyslog is disabled [ref]
Data from journald should be kept in the confines of the service and not forwarded to other services.
Rationale: | If journald is the method for capturing logs, all logs of the system should be handled by journald and not forwarded to other logging mechanisms.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_journald_disable_forward_to_syslog
References: |
cis | 6.2.2.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q systemd; }; then

    found=false

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

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

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

            fi

            found=true

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

                sed -i "/^[[:space:]]*\[Journal\]/a ForwardToSyslog=no" "$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/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Journal]\nForwardToSyslog=no" >> "$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:
      - journald_disable_forward_to_syslog
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald ForwardToSyslog is disabled - Search for a section in files
      ansible.builtin.find:
        paths: '{{item.path}}'
        patterns: '{{item.pattern}}'
        contains: ^\s*\[Journal\]
        read_whole_file: true
        use_regex: true
      register: systemd_dropin_files_with_section
      loop:
      - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
        pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
      - path: /etc/systemd/journald.conf.d
        pattern: .*\.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - journald_disable_forward_to_syslog
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald ForwardToSyslog is disabled - Count number of files which
        contain the correct section
      ansible.builtin.set_fact:
        count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
          | map(attribute=''matched'') | list | map(''int'') | sum}}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - journald_disable_forward_to_syslog
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald ForwardToSyslog is disabled - Add missing configuration to
        correct section
      community.general.ini_file:
        path: '{{item}}'
        section: Journal
        option: ForwardToSyslog
        value: 'no'
        state: present
        no_extra_spaces: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int > 0
      loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
        | map(attribute=''path'') | list }}'
      tags:
      - journald_disable_forward_to_syslog
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald ForwardToSyslog is disabled - Add configuration to new remediation
        file
      community.general.ini_file:
        path: /etc/systemd/journald.conf.d/complianceascode_hardening.conf
        section: Journal
        option: ForwardToSyslog
        value: 'no'
        state: present
        no_extra_spaces: true
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int == 0
      tags:
      - journald_disable_forward_to_syslog
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure journald is configured to write log files to persistent disk [ref]
The journald system may store log files in volatile memory or locally on disk. If the logs are only stored in volatile memory they will be lost upon reboot.
Rationale: | Log files contain valuable data and need to be persistent to aid in possible investigations.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_journald_storage
References: |
cis | 6.2.2.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    found=false

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

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

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

            fi

            found=true

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

                sed -i "/^[[:space:]]*\[Journal\]/a Storage=persistent" "$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/journald.conf.d/complianceascode_hardening.conf /etc/systemd/journald.conf.d/*.conf /etc/systemd/journald.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Journal]\nStorage=persistent" >> "$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:
      - journald_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to write log files to persistent disk - Search
        for a section in files
      ansible.builtin.find:
        paths: '{{item.path}}'
        patterns: '{{item.pattern}}'
        contains: ^\s*\[Journal\]
        read_whole_file: true
        use_regex: true
      register: systemd_dropin_files_with_section
      loop:
      - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
        pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
      - path: /etc/systemd/journald.conf.d
        pattern: .*\.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - journald_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to write log files to persistent disk - Count
        number of files which contain the correct section
      ansible.builtin.set_fact:
        count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
          | map(attribute=''matched'') | list | map(''int'') | sum}}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - journald_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to write log files to persistent disk - Add
        missing configuration to correct section
      community.general.ini_file:
        path: '{{item}}'
        section: Journal
        option: Storage
        value: persistent
        state: present
        no_extra_spaces: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int > 0
      loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
        | map(attribute=''path'') | list }}'
      tags:
      - journald_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure journald is configured to write log files to persistent disk - Add
        configuration to new remediation file
      community.general.ini_file:
        path: /etc/systemd/journald.conf.d/complianceascode_hardening.conf
        section: Journal
        option: Storage
        value: persistent
        state: present
        no_extra_spaces: true
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int == 0
      tags:
      - journald_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Disable systemd-journal-remote Socket [ref]
Journald supports the ability to receive messages from remote hosts, thus acting as a log server. Clients should not receive data from other hosts. NOTE: The same package, systemd-journal-remote , is used for both sending logs to remote hosts and receiving incoming logs. With regards to receiving logs, there are two Systemd unit files; systemd-journal-remote.socket and systemd-journal-remote.service.
Rationale: | If a client is configured to also receive data, thus turning it into a server, the client system is acting outside it's operational boundary.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_socket_systemd-journal-remote_disabled
References: |
cis | 6.2.2.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    SOCKET_NAME="systemd-journal-remote.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:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - socket_systemd-journal-remote_disabled

    - name: Disable systemd-journal-remote Socket - 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-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - socket_systemd-journal-remote_disabled

    - name: Disable systemd-journal-remote Socket - Ensure systemd-journal-remote.socket
        is Masked
      ansible.builtin.systemd:
        name: systemd-journal-remote.socket
        state: stopped
        enabled: false
        masked: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - result_systemd_unit_files.stdout_lines is search("systemd-journal-remote.socket")
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - socket_systemd-journal-remote_disabled
#### Rule Ensure journald and rsyslog Are Not Active Together [ref]
Ensure that exactly one logging system is active. Running both rsyslog and systemd-journald simultaneously can lead to duplicate logging, resource contention, and configuration conflicts. Running neither service means no logging is occurring, which is also a violation.
    systemctl is-active rsyslog systemd-journald
The command should return exactly one `active` service. Both services should not be active at the same time, and at least one must be active. Warning: This rule does not come with a remediation. The choice of logging system (rsyslog vs systemd-journald) is an architectural decision that should be made based on organizational requirements. Use service_rsyslog_enabled/disabled or service_systemd-journald_enabled rules to configure the desired logging system.
Rationale: | Running multiple logging systems concurrently can cause conflicts, resource contention, and inconsistent logging behavior. Systems should use either rsyslog or systemd-journald, but not both simultaneously. This ensures predictable logging behavior and prevents potential issues with log duplication or loss.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_journald_and_rsyslog_not_active_together
References: |
cis | 6.2.1.4
Group Network Configuration and Firewalls Group contains 9 groups and 36 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 2 groups and 4 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 Red Hat Enterprise Linux 10 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 dnf 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. Red Hat Enterprise Linux 10 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
ism | 1409
pcidss4 | 1.2.1, 1.2
bsi | SYS.1.1.A19
cis | 4.1.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install firewalld
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-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install firewalld
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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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, SYS.1.1.A19
ism | 1409
pcidss4 | 1.2.1, 1.2
cis | 4.1.3
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-core && { 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 script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service enable firewalld
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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:
      - 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-core" 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 Strengthen the Default Ruleset Group contains 1 rule
[ref] The default rules can be strengthened. The system scripts that activate the firewall rules expect them to be defined in configuration files under the `/etc/firewalld/services` and `/etc/firewalld/zones` directories. The following recommendations describe how to strengthen the default ruleset configuration file. An alternative to editing this configuration file is to create a shell script that makes calls to the `firewall-cmd` program to load in rules under the `/etc/firewalld/services` and `/etc/firewalld/zones` directories. Instructions apply to both unless otherwise noted. Language and address conventions for regular firewalld rules are used throughout this section. Warning: The program `firewall-config` allows additional services to penetrate the default firewall rules and automatically adjusts the `firewalld` ruleset(s).
#### Rule Configure Firewalld to Trust Loopback Traffic [ref]
Assign loopback interface to the `firewalld` `trusted` zone in order to explicitly allow the loopback traffic in the system. To configure `firewalld` to trust loopback traffic, run the following command:
    sudo firewall-cmd --permanent --zone=trusted --add-interface=lo
To ensure `firewalld` settings are applied in runtime, run the following command:
    firewall-cmd --reload
Rationale: | Loopback traffic is generated between processes on machine and is typically critical to operation of the system. The loopback interface is the only place that loopback network traffic should be seen, all other interfaces should ignore traffic on this network as an anti-spoofing measure.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_firewalld_loopback_traffic_trusted
References: |
pcidss4 | 1.4.1, 1.4
cis | 4.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    if test "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" || { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; }; then
        firewall-offline-cmd --zone=trusted --add-interface=lo
    elif systemctl is-active firewalld; then
        firewall-cmd --permanent --zone=trusted --add-interface=lo
        firewall-cmd --reload
    else
        echo "
        firewalld service is not active. Remediation aborted!
        This remediation could not be applied because it depends on firewalld service running.
        The service is not started by this remediation in order to prevent connection issues."
    fi

    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:
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.1
      - configure_strategy
      - firewalld_loopback_traffic_trusted
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld Package is
        Installed
      ansible.builtin.package:
        name: '{{ item }}'
        state: present
      with_items:
      - firewalld
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.1
      - configure_strategy
      - firewalld_loopback_traffic_trusted
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure Firewalld to Trust Loopback Traffic - Collect Facts About System
        Services
      ansible.builtin.service_facts: null
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.1
      - configure_strategy
      - firewalld_loopback_traffic_trusted
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure Firewalld to Trust Loopback Traffic - Remediation is Applicable
        if firewalld Service is Running
      block:

      - name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld trusted
          Zone Includes lo Interface
        ansible.builtin.command:
          cmd: firewall-cmd --permanent --zone=trusted --add-interface=lo
        register: result_lo_interface_assignment
        changed_when:
        - '''ALREADY_ENABLED'' not in result_lo_interface_assignment.stderr'

      - name: Configure Firewalld to Trust Loopback Traffic - Ensure firewalld Changes
          are Applied
        ansible.builtin.service:
          name: firewalld
          state: reloaded
        when:
        - result_lo_interface_assignment is changed
      when:
      - '"kernel-core" in ansible_facts.packages'
      - ansible_facts.services['firewalld.service'].state == 'running'
      tags:
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.1
      - configure_strategy
      - firewalld_loopback_traffic_trusted
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Configure Firewalld to Trust Loopback Traffic - Informative Message Based
        on Service State
      ansible.builtin.assert:
        that:
        - ansible_check_mode or ansible_facts.services['firewalld.service'].state == 'running'
        fail_msg:
        - firewalld service is not active. Remediation aborted!
        - This remediation could not be applied because it depends on firewalld service
          running.
        - The service is not started by this remediation in order to prevent connection
          issues.
        success_msg:
        - Configure Firewalld to Trust Loopback Traffic remediation successfully executed
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.1
      - configure_strategy
      - firewalld_loopback_traffic_trusted
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Configure Firewalld to Use the Nftables Backend [ref]
Firewalld can be configured with many backends, such as nftables.
Rationale: | Nftables is modern kernel module for controlling network connections coming into a system. Utilizing the limit statement in "nftables" can help to mitigate DoS attacks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_firewalld-backend
References: |
nist | SC-5
os-srg | SRG-OS-000420-GPOS-00186
cis | 4.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q firewalld; }; then

    if [ -e "/etc/firewalld/firewalld.conf" ] ; then

        LC_ALL=C sed -i "/^\s*FirewallBackend\s*=\s*/d" "/etc/firewalld/firewalld.conf"
    else
        touch "/etc/firewalld/firewalld.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/firewalld/firewalld.conf"

    cp "/etc/firewalld/firewalld.conf" "/etc/firewalld/firewalld.conf.bak"
    # Insert before the line matching the regex '^#\s*FirewallBackend'.
    line_number="$(LC_ALL=C grep -n "^#\s*FirewallBackend" "/etc/firewalld/firewalld.conf.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^#\s*FirewallBackend', insert at
        # the end of the file.
        printf '%s\n' "FirewallBackend=nftables" >> "/etc/firewalld/firewalld.conf"
    else
        head -n "$(( line_number - 1 ))" "/etc/firewalld/firewalld.conf.bak" > "/etc/firewalld/firewalld.conf"
        printf '%s\n' "FirewallBackend=nftables" >> "/etc/firewalld/firewalld.conf"
        tail -n "+$(( line_number ))" "/etc/firewalld/firewalld.conf.bak" >> "/etc/firewalld/firewalld.conf"
    fi
    # Clean up after ourselves.
    rm "/etc/firewalld/firewalld.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:
      - NIST-800-53-SC-5
      - firewalld-backend
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Setting unquoted shell-style assignment of 'FirewallBackend' to 'nftables'
        in '/etc/firewalld/firewalld.conf'
      block:

      - name: Check for duplicate values
        ansible.builtin.lineinfile:
          path: /etc/firewalld/firewalld.conf
          create: true
          regexp: (?i)^\s*FirewallBackend=
          state: absent
        check_mode: true
        changed_when: false
        register: dupes

      - name: Deduplicate values from /etc/firewalld/firewalld.conf
        ansible.builtin.lineinfile:
          path: /etc/firewalld/firewalld.conf
          create: true
          regexp: (?i)^\s*FirewallBackend=
          state: absent
        when: dupes.found is defined and dupes.found > 1

      - name: Insert correct line to /etc/firewalld/firewalld.conf
        ansible.builtin.lineinfile:
          path: /etc/firewalld/firewalld.conf
          create: true
          regexp: (?i)^\s*FirewallBackend=
          line: FirewallBackend=nftables
          state: present
          insertbefore: ^# FirewallBackend
          validate: /usr/bin/bash -n %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"firewalld" in ansible_facts.packages'
      tags:
      - NIST-800-53-SC-5
      - firewalld-backend
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
Group IPv6 Group contains 1 group and 8 rules
[ref] The system includes support for Internet Protocol version 6. A major and often-mentioned improvement over IPv4 is its enormous increase in the number of available addresses. Another important feature is its support for automatic configuration of many network settings.
Group Configure IPv6 Settings if Necessary Group contains 8 rules
[ref] A major feature of IPv6 is the extent to which systems implementing it can automatically configure their networking devices using information from the network. From a security perspective, manually configuring important configuration information is preferable to accepting it from the network in an unauthenticated fashion.
#### Rule Configure Accepting Router Advertisements on All IPv6 Interfaces [ref]
To set the runtime status of the `net.ipv6.conf.all.accept_ra` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.all.accept_ra=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.all.accept_ra = 0
Rationale: | An illicit router advertisement message could result in a man-in-the-middle attack.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_ra
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui | 3.1.20
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
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.2.7
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.all.accept_ra%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_ra.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.all.accept_ra 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]*net.ipv6.conf.all.accept_ra.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.all.accept_ra" 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/net_ipv6_conf_all_accept_ra.conf'

    sysctl_net_ipv6_conf_all_accept_ra_value='0'

    #
    # Set runtime for net.ipv6.conf.all.accept_ra
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_ra="$sysctl_net_ipv6_conf_all_accept_ra_value"
    fi

    #
    # If net.ipv6.conf.all.accept_ra present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.all.accept_ra = 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' <<< "^net.ipv6.conf.all.accept_ra")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_ra_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 "^net.ipv6.conf.all.accept_ra\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_ra\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra
    - name: XCCDF Value sysctl_net_ipv6_conf_all_accept_ra_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_all_accept_ra_value: !!str 0
      tags:
        - always

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Set fact
        for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Find all
        files that contain net.ipv6.conf.all.accept_ra
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_ra\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Find all
        files that set net.ipv6.conf.all.accept_ra to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_ra\s*=\s*{{ sysctl_net_ipv6_conf_all_accept_ra_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Comment
        out any occurrences of net.ipv6.conf.all.accept_ra from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_ra
        replace: '#net.ipv6.conf.all.accept_ra'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Comment
        out any occurrences of net.ipv6.conf.all.accept_ra from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_ra
        replace: '#net.ipv6.conf.all.accept_ra'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra

    - name: Configure Accepting Router Advertisements on All IPv6 Interfaces - Ensure
        sysctl net.ipv6.conf.all.accept_ra is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.all.accept_ra
        value: '{{ sysctl_net_ipv6_conf_all_accept_ra_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_all_accept_ra.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_ra
#### Rule Disable Accepting ICMP Redirects for All IPv6 Interfaces [ref]
To set the runtime status of the `net.ipv6.conf.all.accept_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.all.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.all.accept_redirects = 0
Rationale: | An illicit ICMP redirect message could result in a man-in-the-middle attack.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_redirects
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui | 3.1.20
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), CM-6(b), CM-6.1(iv)
nist-csf | PR.IP-1, PR.PT-3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R13
cis | 3.3.2.3
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.all.accept_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.all.accept_redirects 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]*net.ipv6.conf.all.accept_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.all.accept_redirects" 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/net_ipv6_conf_all_accept_redirects.conf'

    sysctl_net_ipv6_conf_all_accept_redirects_value='0'

    #
    # Set runtime for net.ipv6.conf.all.accept_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_redirects="$sysctl_net_ipv6_conf_all_accept_redirects_value"
    fi

    #
    # If net.ipv6.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.all.accept_redirects = 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' <<< "^net.ipv6.conf.all.accept_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_redirects_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 "^net.ipv6.conf.all.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects
    - name: XCCDF Value sysctl_net_ipv6_conf_all_accept_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_all_accept_redirects_value: !!str 0
      tags:
        - always

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Set fact for sysctl
        paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Find all files
        that contain net.ipv6.conf.all.accept_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Find all files
        that set net.ipv6.conf.all.accept_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_redirects\s*=\s*{{ sysctl_net_ipv6_conf_all_accept_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Comment out any
        occurrences of net.ipv6.conf.all.accept_redirects from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_redirects
        replace: '#net.ipv6.conf.all.accept_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Comment out any
        occurrences of net.ipv6.conf.all.accept_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_redirects
        replace: '#net.ipv6.conf.all.accept_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv6 Interfaces - Ensure sysctl net.ipv6.conf.all.accept_redirects
        is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.all.accept_redirects
        value: '{{ sysctl_net_ipv6_conf_all_accept_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_all_accept_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_redirects
#### Rule Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces [ref]
To set the runtime status of the `net.ipv6.conf.all.accept_source_route` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.all.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.all.accept_source_route = 0
Rationale: | Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and the system is functioning as a router. Accepting source-routed packets in the IPv6 protocol has few legitimate uses. It should be disabled unless it is absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_accept_source_route
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 4, 6, 8, 9
cobit5 | APO01.06, APO13.01, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013 | 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.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.3, A.9.4.1, A.9.4.4, A.9.4.5
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | DE.AE-1, ID.AM-3, PR.AC-5, PR.DS-5, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R13
cis | 3.3.2.5
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.all.accept_source_route%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_all_accept_source_route.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.all.accept_source_route 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]*net.ipv6.conf.all.accept_source_route.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.all.accept_source_route" 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/net_ipv6_conf_all_accept_source_route.conf'

    sysctl_net_ipv6_conf_all_accept_source_route_value='0'

    #
    # Set runtime for net.ipv6.conf.all.accept_source_route
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.all.accept_source_route="$sysctl_net_ipv6_conf_all_accept_source_route_value"
    fi

    #
    # If net.ipv6.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.all.accept_source_route = 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' <<< "^net.ipv6.conf.all.accept_source_route")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_accept_source_route_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 "^net.ipv6.conf.all.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.accept_source_route\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route
    - name: XCCDF Value sysctl_net_ipv6_conf_all_accept_source_route_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_all_accept_source_route_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Find all files that contain net.ipv6.conf.all.accept_source_route
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_source_route\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Find all files that set net.ipv6.conf.all.accept_source_route to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.accept_source_route\s*=\s*{{ sysctl_net_ipv6_conf_all_accept_source_route_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Comment out any occurrences of net.ipv6.conf.all.accept_source_route from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_source_route
        replace: '#net.ipv6.conf.all.accept_source_route'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Comment out any occurrences of net.ipv6.conf.all.accept_source_route from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.all.accept_source_route
        replace: '#net.ipv6.conf.all.accept_source_route'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv6 Interfaces
        - Ensure sysctl net.ipv6.conf.all.accept_source_route is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.all.accept_source_route
        value: '{{ sysctl_net_ipv6_conf_all_accept_source_route_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_all_accept_source_route.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_accept_source_route
#### Rule Disable Kernel Parameter for IPv6 Forwarding [ref]
To set the runtime status of the `net.ipv6.conf.all.forwarding` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.all.forwarding=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.all.forwarding = 0
Rationale: | IP forwarding permits the kernel to forward packets from one network interface to another. The ability to forward packets between two networks is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_all_forwarding
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, 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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nist | CM-7(a), CM-7(b), CM-6(a), CM-6(b), CM-6.1(iv)
nist-csf | DE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.all.forwarding 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]*net.ipv6.conf.all.forwarding.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.all.forwarding" 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/net_ipv6_conf_all_forwarding.conf'

    sysctl_net_ipv6_conf_all_forwarding_value='0'

    #
    # Set runtime for net.ipv6.conf.all.forwarding
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.all.forwarding="$sysctl_net_ipv6_conf_all_forwarding_value"
    fi

    #
    # If net.ipv6.conf.all.forwarding present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.all.forwarding = 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' <<< "^net.ipv6.conf.all.forwarding")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_all_forwarding_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 "^net.ipv6.conf.all.forwarding\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.all.forwarding\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding
    - name: XCCDF Value sysctl_net_ipv6_conf_all_forwarding_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_all_forwarding_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for IPv6 Forwarding - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding - Find all files that contain
        net.ipv6.conf.all.forwarding
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.forwarding\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding - Find all files that set net.ipv6.conf.all.forwarding
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.all.forwarding\s*=\s*{{ sysctl_net_ipv6_conf_all_forwarding_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding - Comment out any occurrences
        of net.ipv6.conf.all.forwarding from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.all.forwarding
        replace: '#net.ipv6.conf.all.forwarding'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding - Comment out any occurrences
        of net.ipv6.conf.all.forwarding from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.all.forwarding
        replace: '#net.ipv6.conf.all.forwarding'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding - Ensure sysctl net.ipv6.conf.all.forwarding
        is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.all.forwarding
        value: '{{ sysctl_net_ipv6_conf_all_forwarding_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_all_forwarding.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_all_forwarding
#### Rule Disable Accepting Router Advertisements on all IPv6 Interfaces by Default [ref]
To set the runtime status of the `net.ipv6.conf.default.accept_ra` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.default.accept_ra=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.default.accept_ra = 0
Rationale: | An illicit router advertisement message could result in a man-in-the-middle attack.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_ra
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui | 3.1.20
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
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.2.8
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.default.accept_ra%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_ra.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.default.accept_ra 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]*net.ipv6.conf.default.accept_ra.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.default.accept_ra" 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/net_ipv6_conf_default_accept_ra.conf'

    sysctl_net_ipv6_conf_default_accept_ra_value='0'

    #
    # Set runtime for net.ipv6.conf.default.accept_ra
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_ra="$sysctl_net_ipv6_conf_default_accept_ra_value"
    fi

    #
    # If net.ipv6.conf.default.accept_ra present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.default.accept_ra = 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' <<< "^net.ipv6.conf.default.accept_ra")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_ra_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 "^net.ipv6.conf.default.accept_ra\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_ra\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra
    - name: XCCDF Value sysctl_net_ipv6_conf_default_accept_ra_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_default_accept_ra_value: !!str 0
      tags:
        - always

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Find all files that contain net.ipv6.conf.default.accept_ra
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_ra\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Find all files that set net.ipv6.conf.default.accept_ra to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_ra\s*=\s*{{ sysctl_net_ipv6_conf_default_accept_ra_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Comment out any occurrences of net.ipv6.conf.default.accept_ra from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_ra
        replace: '#net.ipv6.conf.default.accept_ra'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Comment out any occurrences of net.ipv6.conf.default.accept_ra from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_ra
        replace: '#net.ipv6.conf.default.accept_ra'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra

    - name: Disable Accepting Router Advertisements on all IPv6 Interfaces by Default
        - Ensure sysctl net.ipv6.conf.default.accept_ra is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.default.accept_ra
        value: '{{ sysctl_net_ipv6_conf_default_accept_ra_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_default_accept_ra.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_ra
#### Rule Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces [ref]
To set the runtime status of the `net.ipv6.conf.default.accept_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.default.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.default.accept_redirects = 0
Rationale: | An illicit ICMP redirect message could result in a man-in-the-middle attack.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_redirects
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
cui | 3.1.20
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
os-srg | SRG-OS-000480-GPOS-00227
anssi | R13
cis | 3.3.2.4
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.default.accept_redirects%20%3D%200%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.default.accept_redirects 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]*net.ipv6.conf.default.accept_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.default.accept_redirects" 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/net_ipv6_conf_default_accept_redirects.conf'

    sysctl_net_ipv6_conf_default_accept_redirects_value='0'

    #
    # Set runtime for net.ipv6.conf.default.accept_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_redirects="$sysctl_net_ipv6_conf_default_accept_redirects_value"
    fi

    #
    # If net.ipv6.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.default.accept_redirects = 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' <<< "^net.ipv6.conf.default.accept_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_redirects_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 "^net.ipv6.conf.default.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects
    - name: XCCDF Value sysctl_net_ipv6_conf_default_accept_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_default_accept_redirects_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Find all files that contain net.ipv6.conf.default.accept_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Find all files that set net.ipv6.conf.default.accept_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_redirects\s*=\s*{{ sysctl_net_ipv6_conf_default_accept_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Comment out any occurrences of net.ipv6.conf.default.accept_redirects from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_redirects
        replace: '#net.ipv6.conf.default.accept_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Comment out any occurrences of net.ipv6.conf.default.accept_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_redirects
        replace: '#net.ipv6.conf.default.accept_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv6 Interfaces
        - Ensure sysctl net.ipv6.conf.default.accept_redirects is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.default.accept_redirects
        value: '{{ sysctl_net_ipv6_conf_default_accept_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_default_accept_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_redirects
#### Rule Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces by Default [ref]
To set the runtime status of the `net.ipv6.conf.default.accept_source_route` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.default.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.default.accept_source_route = 0
Rationale: | Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv6 forwarding is enabled and the system is functioning as a router. Accepting source-routed packets in the IPv6 protocol has few legitimate uses. It should be disabled unless it is absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_accept_source_route
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 4, 6, 8, 9
cobit5 | APO01.06, APO13.01, DSS01.05, DSS03.01, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013 | 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.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.3, A.9.4.1, A.9.4.4, A.9.4.5
nist | CM-7(a), CM-7(b), CM-6(a), CM-6(b), CM-6.1(iv)
nist-csf | DE.AE-1, ID.AM-3, PR.AC-5, PR.DS-5, PR.PT-4
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R13
pcidss4 | 1.4.2, 1.4
cis | 3.3.2.6
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv6.conf.default.accept_source_route%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv6_conf_default_accept_source_route.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.default.accept_source_route 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]*net.ipv6.conf.default.accept_source_route.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.default.accept_source_route" 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/net_ipv6_conf_default_accept_source_route.conf'

    sysctl_net_ipv6_conf_default_accept_source_route_value='0'

    #
    # Set runtime for net.ipv6.conf.default.accept_source_route
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.default.accept_source_route="$sysctl_net_ipv6_conf_default_accept_source_route_value"
    fi

    #
    # If net.ipv6.conf.default.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.default.accept_source_route = 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' <<< "^net.ipv6.conf.default.accept_source_route")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_accept_source_route_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 "^net.ipv6.conf.default.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.accept_source_route\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route
    - name: XCCDF Value sysctl_net_ipv6_conf_default_accept_source_route_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_default_accept_source_route_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Find all files that contain net.ipv6.conf.default.accept_source_route
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_source_route\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Find all files that set net.ipv6.conf.default.accept_source_route
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.accept_source_route\s*=\s*{{ sysctl_net_ipv6_conf_default_accept_source_route_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Comment out any occurrences of net.ipv6.conf.default.accept_source_route
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_source_route
        replace: '#net.ipv6.conf.default.accept_source_route'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Comment out any occurrences of net.ipv6.conf.default.accept_source_route
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.default.accept_source_route
        replace: '#net.ipv6.conf.default.accept_source_route'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv6 Interfaces
        by Default - Ensure sysctl net.ipv6.conf.default.accept_source_route is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.default.accept_source_route
        value: '{{ sysctl_net_ipv6_conf_default_accept_source_route_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_default_accept_source_route.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_accept_source_route
#### Rule Disable Kernel Parameter for IPv6 Forwarding by default [ref]
To set the runtime status of the `net.ipv6.conf.default.forwarding` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv6.conf.default.forwarding=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv6.conf.default.forwarding = 0
Rationale: | IP forwarding permits the kernel to forward packets from one network interface to another. The ability to forward packets between two networks is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv6_conf_default_forwarding
References: |
nist | CM-6(b), CM-6.1(iv)
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.2.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv6.conf.default.forwarding 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]*net.ipv6.conf.default.forwarding.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv6.conf.default.forwarding" 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/net_ipv6_conf_default_forwarding.conf'

    sysctl_net_ipv6_conf_default_forwarding_value='0'

    #
    # Set runtime for net.ipv6.conf.default.forwarding
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv6.conf.default.forwarding="$sysctl_net_ipv6_conf_default_forwarding_value"
    fi

    #
    # If net.ipv6.conf.default.forwarding present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv6.conf.default.forwarding = 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' <<< "^net.ipv6.conf.default.forwarding")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv6_conf_default_forwarding_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 "^net.ipv6.conf.default.forwarding\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv6.conf.default.forwarding\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding
    - name: XCCDF Value sysctl_net_ipv6_conf_default_forwarding_value # promote to variable
      set_fact:
        sysctl_net_ipv6_conf_default_forwarding_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Set fact for sysctl
        paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Find all files that
        contain net.ipv6.conf.default.forwarding
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.forwarding\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Find all files that
        set net.ipv6.conf.default.forwarding to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv6.conf.default.forwarding\s*=\s*{{ sysctl_net_ipv6_conf_default_forwarding_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Comment out any
        occurrences of net.ipv6.conf.default.forwarding from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv6.conf.default.forwarding
        replace: '#net.ipv6.conf.default.forwarding'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Comment out any
        occurrences of net.ipv6.conf.default.forwarding from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv6.conf.default.forwarding
        replace: '#net.ipv6.conf.default.forwarding'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv6 Forwarding by default - Ensure sysctl net.ipv6.conf.default.forwarding
        is set
      ansible.posix.sysctl:
        name: net.ipv6.conf.default.forwarding
        value: '{{ sysctl_net_ipv6_conf_default_forwarding_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv6_conf_default_forwarding.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - NIST-800-53-CM-6.1(iv)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv6_conf_default_forwarding
Group Kernel Parameters Which Affect Networking Group contains 2 groups and 18 rules
[ref] The `sysctl` utility is used to set parameters which affect the operation of the Linux kernel. Kernel parameters which affect networking and have security implications are described here.
Group Network Related Kernel Runtime Parameters for Hosts and Routers Group contains 15 rules
[ref] Certain kernel parameters should be set for systems which are acting as either hosts or routers to improve the system's ability defend against certain types of IPv4 protocol attacks.
#### Rule Disable Accepting ICMP Redirects for All IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.accept_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.accept_redirects = 0
Rationale: | ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages modify the host's route table and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle attack. This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless absolutely required."
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui | 3.1.20
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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nist | CM-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csf | DE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
cis | 3.3.1.8
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.accept_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_accept_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.accept_redirects 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]*net.ipv4.conf.all.accept_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.accept_redirects" 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/net_ipv4_conf_all_accept_redirects.conf'

    sysctl_net_ipv4_conf_all_accept_redirects_value='0'

    #
    # Set runtime for net.ipv4.conf.all.accept_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.accept_redirects="$sysctl_net_ipv4_conf_all_accept_redirects_value"
    fi

    #
    # If net.ipv4.conf.all.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.accept_redirects = 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' <<< "^net.ipv4.conf.all.accept_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_accept_redirects_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 "^net.ipv4.conf.all.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.accept_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects
    - name: XCCDF Value sysctl_net_ipv4_conf_all_accept_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_accept_redirects_value: !!str 0
      tags:
        - always

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Set fact for sysctl
        paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Find all files
        that contain net.ipv4.conf.all.accept_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.accept_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Find all files
        that set net.ipv4.conf.all.accept_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.accept_redirects\s*=\s*{{ sysctl_net_ipv4_conf_all_accept_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Comment out any
        occurrences of net.ipv4.conf.all.accept_redirects from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.accept_redirects
        replace: '#net.ipv4.conf.all.accept_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Comment out any
        occurrences of net.ipv4.conf.all.accept_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.accept_redirects
        replace: '#net.ipv4.conf.all.accept_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects

    - name: Disable Accepting ICMP Redirects for All IPv4 Interfaces - Ensure sysctl net.ipv4.conf.all.accept_redirects
        is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.accept_redirects
        value: '{{ sysctl_net_ipv4_conf_all_accept_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_accept_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_redirects
#### Rule Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.accept_source_route` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.accept_source_route = 0
Rationale: | Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. This requirement applies only to the forwarding of source-routerd traffic, such as when IPv4 forwarding is enabled and the system is functioning as a router. Accepting source-routed packets in the IPv4 protocol has few legitimate uses. It should be disabled unless it is absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_accept_source_route
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
cis | 3.3.1.14
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.accept_source_route%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_accept_source_route.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.accept_source_route 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]*net.ipv4.conf.all.accept_source_route.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.accept_source_route" 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/net_ipv4_conf_all_accept_source_route.conf'

    sysctl_net_ipv4_conf_all_accept_source_route_value='0'

    #
    # Set runtime for net.ipv4.conf.all.accept_source_route
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.accept_source_route="$sysctl_net_ipv4_conf_all_accept_source_route_value"
    fi

    #
    # If net.ipv4.conf.all.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.accept_source_route = 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' <<< "^net.ipv4.conf.all.accept_source_route")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_accept_source_route_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 "^net.ipv4.conf.all.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.accept_source_route\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route
    - name: XCCDF Value sysctl_net_ipv4_conf_all_accept_source_route_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_accept_source_route_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Find all files that contain net.ipv4.conf.all.accept_source_route
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.accept_source_route\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Find all files that set net.ipv4.conf.all.accept_source_route to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.accept_source_route\s*=\s*{{ sysctl_net_ipv4_conf_all_accept_source_route_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.accept_source_route from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.accept_source_route
        replace: '#net.ipv4.conf.all.accept_source_route'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.accept_source_route from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.accept_source_route
        replace: '#net.ipv4.conf.all.accept_source_route'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on all IPv4 Interfaces
        - Ensure sysctl net.ipv4.conf.all.accept_source_route is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.accept_source_route
        value: '{{ sysctl_net_ipv4_conf_all_accept_source_route_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_accept_source_route.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_accept_source_route
#### Rule Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.forwarding` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.forwarding=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.forwarding = 0
Warning: There might be cases when certain applications can systematically override this option. One such case is Libvirt; a toolkit for managing of virtualization platforms. By default, Libvirt requires IP forwarding to be enabled to facilitate network communication between the virtualization host and guest machines. It enables IP forwarding after every reboot.
Rationale: | IP forwarding permits the kernel to forward packets from one network interface to another. The ability to forward packets between two networks is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_forwarding
References: |
nist | CM-6(b)
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.forwarding 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]*net.ipv4.conf.all.forwarding.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.forwarding" 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/net_ipv4_conf_all_forwarding.conf'

    sysctl_net_ipv4_conf_all_forwarding_value='0'

    #
    # Set runtime for net.ipv4.conf.all.forwarding
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.forwarding="$sysctl_net_ipv4_conf_all_forwarding_value"
    fi

    #
    # If net.ipv4.conf.all.forwarding present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.forwarding = 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' <<< "^net.ipv4.conf.all.forwarding")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_forwarding_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 "^net.ipv4.conf.all.forwarding\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.forwarding\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding
    - name: XCCDF Value sysctl_net_ipv4_conf_all_forwarding_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_forwarding_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Set
        fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Find
        all files that contain net.ipv4.conf.all.forwarding
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.forwarding\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Find
        all files that set net.ipv4.conf.all.forwarding to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.forwarding\s*=\s*{{ sysctl_net_ipv4_conf_all_forwarding_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Comment
        out any occurrences of net.ipv4.conf.all.forwarding from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.forwarding
        replace: '#net.ipv4.conf.all.forwarding'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Comment
        out any occurrences of net.ipv4.conf.all.forwarding from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.forwarding
        replace: '#net.ipv4.conf.all.forwarding'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding on all IPv4 Interfaces - Ensure
        sysctl net.ipv4.conf.all.forwarding is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.forwarding
        value: '{{ sysctl_net_ipv4_conf_all_forwarding_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_forwarding.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(b)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_forwarding
#### Rule Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.log_martians` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.log_martians=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.log_martians = 1
Rationale: | The presence of "martian" packets (which have impossible addresses) as well as spoofed packets, source-routed packets, and redirects could be a sign of nefarious network activity. Logging these packets enables this activity to be detected.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_log_martians
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.04, DSS03.05, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.06
cui | 3.1.20
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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.11.2.6, A.12.1.2, A.12.1.3, 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.17.2.1, A.6.2.1, A.6.2.2, A.9.1.2
nist | CM-7(a), CM-7(b), SC-5(3)(a)
nist-csf | DE.CM-1, PR.AC-3, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.1.16
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.log_martians%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_log_martians.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.log_martians 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]*net.ipv4.conf.all.log_martians.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.log_martians" 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/net_ipv4_conf_all_log_martians.conf'

    sysctl_net_ipv4_conf_all_log_martians_value='1'

    #
    # Set runtime for net.ipv4.conf.all.log_martians
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.log_martians="$sysctl_net_ipv4_conf_all_log_martians_value"
    fi

    #
    # If net.ipv4.conf.all.log_martians present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.log_martians = 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' <<< "^net.ipv4.conf.all.log_martians")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_log_martians_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 "^net.ipv4.conf.all.log_martians\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.log_martians\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity
    - name: XCCDF Value sysctl_net_ipv4_conf_all_log_martians_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_log_martians_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Set
        fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Find
        all files that contain net.ipv4.conf.all.log_martians
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.log_martians\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Find
        all files that set net.ipv4.conf.all.log_martians to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.log_martians\s*=\s*{{ sysctl_net_ipv4_conf_all_log_martians_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Comment
        out any occurrences of net.ipv4.conf.all.log_martians from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.log_martians
        replace: '#net.ipv4.conf.all.log_martians'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Comment
        out any occurrences of net.ipv4.conf.all.log_martians from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.log_martians
        replace: '#net.ipv4.conf.all.log_martians'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces - Ensure
        sysctl net.ipv4.conf.all.log_martians is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.log_martians
        value: '{{ sysctl_net_ipv4_conf_all_log_martians_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_log_martians.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_all_log_martians
      - unknown_severity
#### Rule Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.rp_filter` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.rp_filter=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.rp_filter = 1
Rationale: | Enabling reverse path filtering drops packets with source addresses that should not have been able to be received on the interface they were received on. It should not be used on systems which are routers for complicated networks, but is helpful for end hosts and routers serving small networks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_rp_filter
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cobit5 | APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013 | 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.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, 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.17.2.1, 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-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.3, 1.4
cis | 3.3.1.12
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.rp_filter%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_rp_filter.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.rp_filter 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]*net.ipv4.conf.all.rp_filter.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.rp_filter" 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/net_ipv4_conf_all_rp_filter.conf'

    sysctl_net_ipv4_conf_all_rp_filter_value='1'

    #
    # Set runtime for net.ipv4.conf.all.rp_filter
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.rp_filter="$sysctl_net_ipv4_conf_all_rp_filter_value"
    fi

    #
    # If net.ipv4.conf.all.rp_filter present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.rp_filter = 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' <<< "^net.ipv4.conf.all.rp_filter")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_rp_filter_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 "^net.ipv4.conf.all.rp_filter\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.rp_filter\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter
    - name: XCCDF Value sysctl_net_ipv4_conf_all_rp_filter_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_rp_filter_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Find all files that contain net.ipv4.conf.all.rp_filter
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.rp_filter\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Find all files that set net.ipv4.conf.all.rp_filter to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.rp_filter\s*=\s*{{ sysctl_net_ipv4_conf_all_rp_filter_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.rp_filter from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.rp_filter
        replace: '#net.ipv4.conf.all.rp_filter'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.rp_filter from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.rp_filter
        replace: '#net.ipv4.conf.all.rp_filter'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        - Ensure sysctl net.ipv4.conf.all.rp_filter is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.rp_filter
        value: '{{ sysctl_net_ipv4_conf_all_rp_filter_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_rp_filter.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_rp_filter
#### Rule Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.secure_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.secure_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.secure_redirects = 0
Rationale: | Accepting "secure" ICMP redirects (from those gateways listed as default gateways) has few legitimate uses. It should be disabled unless it is absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_secure_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.3, 1.4
cis | 3.3.1.10
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.secure_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_secure_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.secure_redirects 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]*net.ipv4.conf.all.secure_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.secure_redirects" 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/net_ipv4_conf_all_secure_redirects.conf'

    sysctl_net_ipv4_conf_all_secure_redirects_value='0'

    #
    # Set runtime for net.ipv4.conf.all.secure_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.secure_redirects="$sysctl_net_ipv4_conf_all_secure_redirects_value"
    fi

    #
    # If net.ipv4.conf.all.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.all.secure_redirects = 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' <<< "^net.ipv4.conf.all.secure_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_all_secure_redirects_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 "^net.ipv4.conf.all.secure_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.secure_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects
    - name: XCCDF Value sysctl_net_ipv4_conf_all_secure_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_all_secure_redirects_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Find all files that contain net.ipv4.conf.all.secure_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.secure_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Find all files that set net.ipv4.conf.all.secure_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.secure_redirects\s*=\s*{{ sysctl_net_ipv4_conf_all_secure_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.secure_redirects from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.secure_redirects
        replace: '#net.ipv4.conf.all.secure_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.secure_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.secure_redirects
        replace: '#net.ipv4.conf.all.secure_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects

    - name: Disable Kernel Parameter for Accepting Secure ICMP Redirects on all IPv4 Interfaces
        - Ensure sysctl net.ipv4.conf.all.secure_redirects is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.secure_redirects
        value: '{{ sysctl_net_ipv4_conf_all_secure_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_secure_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_secure_redirects
#### Rule Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.default.accept_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.accept_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.accept_redirects = 0
Rationale: | ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages modify the host's route table and are unauthenticated. An illicit ICMP redirect message could result in a man-in-the-middle attack. This feature of the IPv4 protocol has few legitimate uses. It should be disabled unless absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.3, 1.4
cis | 3.3.1.9
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.accept_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_accept_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.accept_redirects 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]*net.ipv4.conf.default.accept_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.accept_redirects" 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/net_ipv4_conf_default_accept_redirects.conf'

    sysctl_net_ipv4_conf_default_accept_redirects_value='0'

    #
    # Set runtime for net.ipv4.conf.default.accept_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.accept_redirects="$sysctl_net_ipv4_conf_default_accept_redirects_value"
    fi

    #
    # If net.ipv4.conf.default.accept_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.accept_redirects = 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' <<< "^net.ipv4.conf.default.accept_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_accept_redirects_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 "^net.ipv4.conf.default.accept_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.accept_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects
    - name: XCCDF Value sysctl_net_ipv4_conf_default_accept_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_accept_redirects_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Find all files that contain net.ipv4.conf.default.accept_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.accept_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Find all files that set net.ipv4.conf.default.accept_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.accept_redirects\s*=\s*{{ sysctl_net_ipv4_conf_default_accept_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.default.accept_redirects from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.accept_redirects
        replace: '#net.ipv4.conf.default.accept_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.default.accept_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.accept_redirects
        replace: '#net.ipv4.conf.default.accept_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects

    - name: Disable Kernel Parameter for Accepting ICMP Redirects by Default on IPv4 Interfaces
        - Ensure sysctl net.ipv4.conf.default.accept_redirects is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.accept_redirects
        value: '{{ sysctl_net_ipv4_conf_default_accept_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_accept_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_redirects
#### Rule Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces by Default [ref]
To set the runtime status of the `net.ipv4.conf.default.accept_source_route` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.accept_source_route=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.accept_source_route = 0
Rationale: | Source-routed packets allow the source of the packet to suggest routers forward the packet along a different path than configured on the router, which can be used to bypass network security measures. Accepting source-routed packets in the IPv4 protocol has few legitimate uses. It should be disabled unless it is absolutely required, such as when IPv4 forwarding is enabled and the system is legitimately functioning as a router.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_accept_source_route
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
cis | 3.3.1.15
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.accept_source_route%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_accept_source_route.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.accept_source_route 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]*net.ipv4.conf.default.accept_source_route.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.accept_source_route" 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/net_ipv4_conf_default_accept_source_route.conf'

    sysctl_net_ipv4_conf_default_accept_source_route_value='0'

    #
    # Set runtime for net.ipv4.conf.default.accept_source_route
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.accept_source_route="$sysctl_net_ipv4_conf_default_accept_source_route_value"
    fi

    #
    # If net.ipv4.conf.default.accept_source_route present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.accept_source_route = 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' <<< "^net.ipv4.conf.default.accept_source_route")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_accept_source_route_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 "^net.ipv4.conf.default.accept_source_route\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.accept_source_route\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route
    - name: XCCDF Value sysctl_net_ipv4_conf_default_accept_source_route_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_accept_source_route_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Find all files that contain net.ipv4.conf.default.accept_source_route
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.accept_source_route\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Find all files that set net.ipv4.conf.default.accept_source_route
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.accept_source_route\s*=\s*{{ sysctl_net_ipv4_conf_default_accept_source_route_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.accept_source_route
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.accept_source_route
        replace: '#net.ipv4.conf.default.accept_source_route'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.accept_source_route
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.accept_source_route
        replace: '#net.ipv4.conf.default.accept_source_route'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route

    - name: Disable Kernel Parameter for Accepting Source-Routed Packets on IPv4 Interfaces
        by Default - Ensure sysctl net.ipv4.conf.default.accept_source_route is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.accept_source_route
        value: '{{ sysctl_net_ipv4_conf_default_accept_source_route_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_accept_source_route.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_accept_source_route
#### Rule Disable Kernel Parameter for IPv4 Forwarding By Default [ref]
To set the runtime status of the `net.ipv4.conf.default.forwarding` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.forwarding=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.forwarding = 0
Warning: There might be cases when certain applications can systematically override this option. One such case is Libvirt; a toolkit for managing of virtualization platforms. By default, Libvirt requires IP forwarding to be enabled to facilitate network communication between the virtualization host and guest machines. It enables IP forwarding after every reboot.
Rationale: | IP forwarding permits the kernel to forward packets from one network interface to another. The ability to forward packets between two networks is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_forwarding
References: |
cis | 3.3.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.forwarding 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]*net.ipv4.conf.default.forwarding.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.forwarding" 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/net_ipv4_conf_default_forwarding.conf'

    sysctl_net_ipv4_conf_default_forwarding_value='0'

    #
    # Set runtime for net.ipv4.conf.default.forwarding
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.forwarding="$sysctl_net_ipv4_conf_default_forwarding_value"
    fi

    #
    # If net.ipv4.conf.default.forwarding present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.forwarding = 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' <<< "^net.ipv4.conf.default.forwarding")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_forwarding_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 "^net.ipv4.conf.default.forwarding\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.forwarding\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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_net_ipv4_conf_default_forwarding
    - name: XCCDF Value sysctl_net_ipv4_conf_default_forwarding_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_forwarding_value: !!str 0
      tags:
        - always

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Set fact for sysctl
        paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Find all files that
        contain net.ipv4.conf.default.forwarding
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.forwarding\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Find all files that
        set net.ipv4.conf.default.forwarding to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.forwarding\s*=\s*{{ sysctl_net_ipv4_conf_default_forwarding_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Comment out any
        occurrences of net.ipv4.conf.default.forwarding from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.forwarding
        replace: '#net.ipv4.conf.default.forwarding'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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_net_ipv4_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Comment out any
        occurrences of net.ipv4.conf.default.forwarding from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.forwarding
        replace: '#net.ipv4.conf.default.forwarding'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_forwarding

    - name: Disable Kernel Parameter for IPv4 Forwarding By Default - Ensure sysctl net.ipv4.conf.default.forwarding
        is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.forwarding
        value: '{{ sysctl_net_ipv4_conf_default_forwarding_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_forwarding.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_forwarding
#### Rule Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default [ref]
To set the runtime status of the `net.ipv4.conf.default.log_martians` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.log_martians=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.log_martians = 1
Rationale: | The presence of "martian" packets (which have impossible addresses) as well as spoofed packets, source-routed packets, and redirects could be a sign of nefarious network activity. Logging these packets enables this activity to be detected.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_log_martians
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.04, DSS03.05, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.06
cui | 3.1.20
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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.11.2.6, A.12.1.2, A.12.1.3, 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.17.2.1, A.6.2.1, A.6.2.2, A.9.1.2
nist | CM-7(a), CM-7(b), SC-5(3)(a)
nist-csf | DE.CM-1, PR.AC-3, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
cis | 3.3.1.17
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.log_martians%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_log_martians.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.log_martians 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]*net.ipv4.conf.default.log_martians.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.log_martians" 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/net_ipv4_conf_default_log_martians.conf'

    sysctl_net_ipv4_conf_default_log_martians_value='1'

    #
    # Set runtime for net.ipv4.conf.default.log_martians
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.log_martians="$sysctl_net_ipv4_conf_default_log_martians_value"
    fi

    #
    # If net.ipv4.conf.default.log_martians present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.log_martians = 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' <<< "^net.ipv4.conf.default.log_martians")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_log_martians_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 "^net.ipv4.conf.default.log_martians\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.log_martians\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity
    - name: XCCDF Value sysctl_net_ipv4_conf_default_log_martians_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_log_martians_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Find all files that contain net.ipv4.conf.default.log_martians
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.log_martians\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Find all files that set net.ipv4.conf.default.log_martians to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.log_martians\s*=\s*{{ sysctl_net_ipv4_conf_default_log_martians_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Comment out any occurrences of net.ipv4.conf.default.log_martians from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.log_martians
        replace: '#net.ipv4.conf.default.log_martians'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Comment out any occurrences of net.ipv4.conf.default.log_martians from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.log_martians
        replace: '#net.ipv4.conf.default.log_martians'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity

    - name: Enable Kernel Parameter to Log Martian Packets on all IPv4 Interfaces by Default
        - Ensure sysctl net.ipv4.conf.default.log_martians is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.log_martians
        value: '{{ sysctl_net_ipv4_conf_default_log_martians_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_log_martians.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(3)(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_conf_default_log_martians
      - unknown_severity
#### Rule Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces by Default [ref]
To set the runtime status of the `net.ipv4.conf.default.rp_filter` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.rp_filter=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.rp_filter = 1
Rationale: | Enabling reverse path filtering drops packets with source addresses that should not have been able to be received on the interface they were received on. It should not be used on systems which are routers for complicated networks, but is helpful for end hosts and routers serving small networks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_rp_filter
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cobit5 | APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013 | 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.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, 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.17.2.1, 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-7(a), CM-7(b), CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
cis | 3.3.1.13
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.rp_filter%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_rp_filter.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.rp_filter 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]*net.ipv4.conf.default.rp_filter.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.rp_filter" 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/net_ipv4_conf_default_rp_filter.conf'

    sysctl_net_ipv4_conf_default_rp_filter_value='1'

    #
    # Set runtime for net.ipv4.conf.default.rp_filter
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.rp_filter="$sysctl_net_ipv4_conf_default_rp_filter_value"
    fi

    #
    # If net.ipv4.conf.default.rp_filter present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.rp_filter = 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' <<< "^net.ipv4.conf.default.rp_filter")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_rp_filter_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 "^net.ipv4.conf.default.rp_filter\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.rp_filter\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter
    - name: XCCDF Value sysctl_net_ipv4_conf_default_rp_filter_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_rp_filter_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Find all files that contain net.ipv4.conf.default.rp_filter
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.rp_filter\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Find all files that set net.ipv4.conf.default.rp_filter to correct
        value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.rp_filter\s*=\s*{{ sysctl_net_ipv4_conf_default_rp_filter_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.rp_filter from
        config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.rp_filter
        replace: '#net.ipv4.conf.default.rp_filter'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.rp_filter from
        /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.rp_filter
        replace: '#net.ipv4.conf.default.rp_filter'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter

    - name: Enable Kernel Parameter to Use Reverse Path Filtering on all IPv4 Interfaces
        by Default - Ensure sysctl net.ipv4.conf.default.rp_filter is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.rp_filter
        value: '{{ sysctl_net_ipv4_conf_default_rp_filter_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_rp_filter.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_rp_filter
#### Rule Configure Kernel Parameter for Accepting Secure Redirects By Default [ref]
To set the runtime status of the `net.ipv4.conf.default.secure_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.secure_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.secure_redirects = 0
Rationale: | Accepting "secure" ICMP redirects (from those gateways listed as default gateways) has few legitimate uses. It should be disabled unless it is absolutely required.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_secure_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
cis | 3.3.1.11
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.secure_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_secure_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.secure_redirects 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]*net.ipv4.conf.default.secure_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.secure_redirects" 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/net_ipv4_conf_default_secure_redirects.conf'

    sysctl_net_ipv4_conf_default_secure_redirects_value='0'

    #
    # Set runtime for net.ipv4.conf.default.secure_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.secure_redirects="$sysctl_net_ipv4_conf_default_secure_redirects_value"
    fi

    #
    # If net.ipv4.conf.default.secure_redirects present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.conf.default.secure_redirects = 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' <<< "^net.ipv4.conf.default.secure_redirects")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_conf_default_secure_redirects_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 "^net.ipv4.conf.default.secure_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.secure_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects
    - name: XCCDF Value sysctl_net_ipv4_conf_default_secure_redirects_value # promote to variable
      set_fact:
        sysctl_net_ipv4_conf_default_secure_redirects_value: !!str 0
      tags:
        - always

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Set
        fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Find
        all files that contain net.ipv4.conf.default.secure_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.secure_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Find
        all files that set net.ipv4.conf.default.secure_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.secure_redirects\s*=\s*{{ sysctl_net_ipv4_conf_default_secure_redirects_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Comment
        out any occurrences of net.ipv4.conf.default.secure_redirects from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.secure_redirects
        replace: '#net.ipv4.conf.default.secure_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Comment
        out any occurrences of net.ipv4.conf.default.secure_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.secure_redirects
        replace: '#net.ipv4.conf.default.secure_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects

    - name: Configure Kernel Parameter for Accepting Secure Redirects By Default - Ensure
        sysctl net.ipv4.conf.default.secure_redirects is set
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.secure_redirects
        value: '{{ sysctl_net_ipv4_conf_default_secure_redirects_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_secure_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_secure_redirects
#### Rule Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.icmp_echo_ignore_broadcasts` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.icmp_echo_ignore_broadcasts = 1
Rationale: | Responding to broadcast (ICMP) echoes facilitates network mapping and provides a vector for amplification attacks. Ignoring ICMP echo requests (pings) sent to broadcast or multicast addresses makes the system slightly more difficult to enumerate on the network.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_echo_ignore_broadcasts
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 1.4.2, 1.4
cis | 3.3.1.7
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.icmp_echo_ignore_broadcasts%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_icmp_echo_ignore_broadcasts.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.icmp_echo_ignore_broadcasts 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]*net.ipv4.icmp_echo_ignore_broadcasts.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.icmp_echo_ignore_broadcasts" 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/net_ipv4_icmp_echo_ignore_broadcasts.conf'

    sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value='1'

    #
    # Set runtime for net.ipv4.icmp_echo_ignore_broadcasts
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.icmp_echo_ignore_broadcasts="$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value"
    fi

    #
    # If net.ipv4.icmp_echo_ignore_broadcasts present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.icmp_echo_ignore_broadcasts = 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' <<< "^net.ipv4.icmp_echo_ignore_broadcasts")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_icmp_echo_ignore_broadcasts_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 "^net.ipv4.icmp_echo_ignore_broadcasts\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.icmp_echo_ignore_broadcasts\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts
    - name: XCCDF Value sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value # promote to variable
      set_fact:
        sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Find all files that contain net.ipv4.icmp_echo_ignore_broadcasts
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.icmp_echo_ignore_broadcasts\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Find all files that set net.ipv4.icmp_echo_ignore_broadcasts to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.icmp_echo_ignore_broadcasts\s*=\s*{{ sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.icmp_echo_ignore_broadcasts from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.icmp_echo_ignore_broadcasts
        replace: '#net.ipv4.icmp_echo_ignore_broadcasts'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.icmp_echo_ignore_broadcasts from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.icmp_echo_ignore_broadcasts
        replace: '#net.ipv4.icmp_echo_ignore_broadcasts'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts

    - name: Enable Kernel Parameter to Ignore ICMP Broadcast Echo Requests on IPv4 Interfaces
        - Ensure sysctl net.ipv4.icmp_echo_ignore_broadcasts is set
      ansible.posix.sysctl:
        name: net.ipv4.icmp_echo_ignore_broadcasts
        value: '{{ sysctl_net_ipv4_icmp_echo_ignore_broadcasts_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_icmp_echo_ignore_broadcasts.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_icmp_echo_ignore_broadcasts
#### Rule Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.icmp_ignore_bogus_error_responses` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.icmp_ignore_bogus_error_responses=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.icmp_ignore_bogus_error_responses = 1
Rationale: | Ignoring bogus ICMP error responses reduces log size, although some activity would not be logged.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_icmp_ignore_bogus_error_responses
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui | 3.1.20
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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, A.9.1.2
nerc-cip | CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5
nist-csf | DE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3
pcidss | Req-1.4.3
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.2, 1.4
cis | 3.3.1.6
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.icmp_ignore_bogus_error_responses%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_icmp_ignore_bogus_error_responses.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.icmp_ignore_bogus_error_responses 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]*net.ipv4.icmp_ignore_bogus_error_responses.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.icmp_ignore_bogus_error_responses" 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/net_ipv4_icmp_ignore_bogus_error_responses.conf'

    sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value='1'

    #
    # Set runtime for net.ipv4.icmp_ignore_bogus_error_responses
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.icmp_ignore_bogus_error_responses="$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value"
    fi

    #
    # If net.ipv4.icmp_ignore_bogus_error_responses present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.icmp_ignore_bogus_error_responses = 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' <<< "^net.ipv4.icmp_ignore_bogus_error_responses")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_icmp_ignore_bogus_error_responses_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 "^net.ipv4.icmp_ignore_bogus_error_responses\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.icmp_ignore_bogus_error_responses\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity
    - name: XCCDF Value sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value # promote to variable
      set_fact:
        sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Find all files that contain net.ipv4.icmp_ignore_bogus_error_responses
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.icmp_ignore_bogus_error_responses\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Find all files that set net.ipv4.icmp_ignore_bogus_error_responses to correct
        value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.icmp_ignore_bogus_error_responses\s*=\s*{{ sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.icmp_ignore_bogus_error_responses from
        config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.icmp_ignore_bogus_error_responses
        replace: '#net.ipv4.icmp_ignore_bogus_error_responses'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.icmp_ignore_bogus_error_responses from
        /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.icmp_ignore_bogus_error_responses
        replace: '#net.ipv4.icmp_ignore_bogus_error_responses'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity

    - name: Enable Kernel Parameter to Ignore Bogus ICMP Error Responses on IPv4 Interfaces
        - Ensure sysctl net.ipv4.icmp_ignore_bogus_error_responses is set
      ansible.posix.sysctl:
        name: net.ipv4.icmp_ignore_bogus_error_responses
        value: '{{ sysctl_net_ipv4_icmp_ignore_bogus_error_responses_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_icmp_ignore_bogus_error_responses.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - PCI-DSS-Req-1.4.3
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - disable_strategy
      - low_complexity
      - medium_disruption
      - reboot_required
      - sysctl_net_ipv4_icmp_ignore_bogus_error_responses
      - unknown_severity
#### Rule Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces [ref]
To set the runtime status of the `net.ipv4.tcp_syncookies` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.tcp_syncookies=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.tcp_syncookies = 1
Rationale: | A TCP SYN flood attack can cause a denial of service by filling a system's TCP connection table with connections in the SYN_RCVD state. Syncookies can be used to track a connection when a subsequent ACK is received, verifying the initiator is attempting a valid connection and is not a flood source. This feature is activated when a flood condition is detected, and enables the system to continue servicing valid connection requests.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_tcp_syncookies
References: |
cis-csc | 1, 12, 13, 14, 15, 16, 18, 2, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 4.3.3.4, 4.4.3.3
isa-62443-2013 | 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.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, 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.17.2.1, 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-7(a), CM-7(b), SC-5(1), SC-5(2), SC-5(3)(a), CM-6(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.PT-4
pcidss | Req-1.4.1
os-srg | SRG-OS-000480-GPOS-00227, SRG-OS-000420-GPOS-00186, SRG-OS-000142-GPOS-00071
anssi | R12
pcidss4 | 1.4.3, 1.4
cis | 3.3.1.18
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.tcp_syncookies%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_tcp_syncookies.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.tcp_syncookies 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]*net.ipv4.tcp_syncookies.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.tcp_syncookies" 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/net_ipv4_tcp_syncookies.conf'

    sysctl_net_ipv4_tcp_syncookies_value='1'

    #
    # Set runtime for net.ipv4.tcp_syncookies
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.tcp_syncookies="$sysctl_net_ipv4_tcp_syncookies_value"
    fi

    #
    # If net.ipv4.tcp_syncookies present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "net.ipv4.tcp_syncookies = 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' <<< "^net.ipv4.tcp_syncookies")

    # shellcheck disable=SC2059
    printf -v formatted_output "%s = %s" "$stripped_key" "$sysctl_net_ipv4_tcp_syncookies_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 "^net.ipv4.tcp_syncookies\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.tcp_syncookies\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies
    - name: XCCDF Value sysctl_net_ipv4_tcp_syncookies_value # promote to variable
      set_fact:
        sysctl_net_ipv4_tcp_syncookies_value: !!str 1
      tags:
        - always

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Set
        fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Find
        all files that contain net.ipv4.tcp_syncookies
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.tcp_syncookies\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Find
        all files that set net.ipv4.tcp_syncookies to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.tcp_syncookies\s*=\s*{{ sysctl_net_ipv4_tcp_syncookies_value
          }}$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Comment
        out any occurrences of net.ipv4.tcp_syncookies from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.tcp_syncookies
        replace: '#net.ipv4.tcp_syncookies'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Comment
        out any occurrences of net.ipv4.tcp_syncookies from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.tcp_syncookies
        replace: '#net.ipv4.tcp_syncookies'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies

    - name: Enable Kernel Parameter to Use TCP Syncookies on Network Interfaces - Ensure
        sysctl net.ipv4.tcp_syncookies is set
      ansible.posix.sysctl:
        name: net.ipv4.tcp_syncookies
        value: '{{ sysctl_net_ipv4_tcp_syncookies_value }}'
        sysctl_file: /etc/sysctl.d/net_ipv4_tcp_syncookies.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5(1)
      - NIST-800-53-SC-5(2)
      - NIST-800-53-SC-5(3)(a)
      - PCI-DSS-Req-1.4.1
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_tcp_syncookies
Group Network Parameters for Hosts Only Group contains 3 rules
[ref] If the system is not going to be used as a router, then setting certain kernel parameters ensure that the host will not perform routing of network traffic.
#### Rule Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.conf.all.send_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.all.send_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.all.send_redirects = 0
Rationale: | ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages contain information from the system's route table possibly revealing portions of the network topology. The ability to send ICMP redirects is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_all_send_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.5, 1.4
cis | 3.3.1.4
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.all.send_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_all_send_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.all.send_redirects 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]*net.ipv4.conf.all.send_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.all.send_redirects" 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/net_ipv4_conf_all_send_redirects.conf'

    #
    # Set runtime for net.ipv4.conf.all.send_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.all.send_redirects="0"
    fi

    #
    # If net.ipv4.conf.all.send_redirects present in /etc/sysctl.conf, change value to "0"
    #	else, add "net.ipv4.conf.all.send_redirects = 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' <<< "^net.ipv4.conf.all.send_redirects")

    # 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 "^net.ipv4.conf.all.send_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.all.send_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Find all files that contain net.ipv4.conf.all.send_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.send_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Find all files that set net.ipv4.conf.all.send_redirects to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.all.send_redirects\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.send_redirects from config
        files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.all.send_redirects
        replace: '#net.ipv4.conf.all.send_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Comment out any occurrences of net.ipv4.conf.all.send_redirects from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.all.send_redirects
        replace: '#net.ipv4.conf.all.send_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        - Ensure sysctl net.ipv4.conf.all.send_redirects is set to 0
      ansible.posix.sysctl:
        name: net.ipv4.conf.all.send_redirects
        value: '0'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_all_send_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_all_send_redirects
#### Rule Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces by Default [ref]
To set the runtime status of the `net.ipv4.conf.default.send_redirects` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.conf.default.send_redirects=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.conf.default.send_redirects = 0
Rationale: | ICMP redirect messages are used by routers to inform hosts that a more direct route exists for a particular destination. These messages contain information from the system's route table possibly revealing portions of the network topology. The ability to send ICMP redirects is only appropriate for systems acting as routers.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_conf_default_send_redirects
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 18, 2, 3, 4, 6, 7, 8, 9
cjis | 5.10.1.1
cobit5 | APO01.06, APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS01.05, DSS03.01, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
cui | 3.1.20
isa-62443-2009 | 4.2.3.4, 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.3.2, 4.3.4.3.3, 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.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 6.2, SR 7.1, SR 7.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.1, A.12.1.2, A.12.1.3, A.12.5.1, A.12.6.2, 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.14.2.2, A.14.2.3, A.14.2.4, A.17.2.1, 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-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csf | DE.AE-1, DE.CM-1, ID.AM-3, PR.AC-5, PR.DS-4, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.5, 1.4
cis | 3.3.1.5
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,net.ipv4.conf.default.send_redirects%3D0%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_net_ipv4_conf_default_send_redirects.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.conf.default.send_redirects 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]*net.ipv4.conf.default.send_redirects.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.conf.default.send_redirects" 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/net_ipv4_conf_default_send_redirects.conf'

    #
    # Set runtime for net.ipv4.conf.default.send_redirects
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.conf.default.send_redirects="0"
    fi

    #
    # If net.ipv4.conf.default.send_redirects present in /etc/sysctl.conf, change value to "0"
    #	else, add "net.ipv4.conf.default.send_redirects = 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' <<< "^net.ipv4.conf.default.send_redirects")

    # 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 "^net.ipv4.conf.default.send_redirects\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.conf.default.send_redirects\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Find all files that contain net.ipv4.conf.default.send_redirects
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.send_redirects\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Find all files that set net.ipv4.conf.default.send_redirects to correct
        value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.conf.default.send_redirects\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.send_redirects
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.conf.default.send_redirects
        replace: '#net.ipv4.conf.default.send_redirects'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Comment out any occurrences of net.ipv4.conf.default.send_redirects
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.conf.default.send_redirects
        replace: '#net.ipv4.conf.default.send_redirects'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects

    - name: Disable Kernel Parameter for Sending ICMP Redirects on all IPv4 Interfaces
        by Default - Ensure sysctl net.ipv4.conf.default.send_redirects is set to 0
      ansible.posix.sysctl:
        name: net.ipv4.conf.default.send_redirects
        value: '0'
        sysctl_file: /etc/sysctl.d/net_ipv4_conf_default_send_redirects.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1.1
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.5
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_conf_default_send_redirects
#### Rule Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces [ref]
To set the runtime status of the `net.ipv4.ip_forward` kernel parameter, run the following command:
    $ sudo sysctl -w net.ipv4.ip_forward=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.ipv4.ip_forward = 0
Warning: Certain technologies such as virtual machines, containers, etc. rely on IPv4 forwarding to enable and use networking. Disabling IPv4 forwarding would cause those technologies to stop working. Therefore, this rule should not be used in profiles or benchmarks that target usage of IPv4 forwarding.
Rationale: | Routing protocol daemons are typically used on routers to exchange network topology information with other routers. If this capability is used when not required, system network information may be unnecessarily transmitted across the network.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_ipv4_ip_forward
References: |
cis-csc | 1, 11, 12, 13, 14, 15, 16, 2, 3, 7, 8, 9
cobit5 | APO13.01, BAI04.04, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS05.02, DSS05.05, DSS05.07, DSS06.06
cui | 3.1.20
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 6.2, SR 7.1, SR 7.2, SR 7.6
iso27001-2013 | A.12.1.2, A.12.1.3, 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.17.2.1, A.9.1.2
nerc-cip | CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nist | CM-7(a), CM-7(b), SC-5, CM-6(a), SC-7(a)
nist-csf | DE.CM-1, PR.DS-4, PR.IP-1, PR.PT-3, PR.PT-4
pcidss | Req-1.3.1, Req-1.3.2
os-srg | SRG-OS-000480-GPOS-00227
anssi | R12
pcidss4 | 1.4.3, 1.4
cis | 3.3.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of net.ipv4.ip_forward 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]*net.ipv4.ip_forward.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "net.ipv4.ip_forward" 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/net_ipv4_ip_forward.conf'

    #
    # Set runtime for net.ipv4.ip_forward
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w net.ipv4.ip_forward="0"
    fi

    #
    # If net.ipv4.ip_forward present in /etc/sysctl.conf, change value to "0"
    #	else, add "net.ipv4.ip_forward = 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' <<< "^net.ipv4.ip_forward")

    # 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 "^net.ipv4.ip_forward\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^net.ipv4.ip_forward\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Set fact for
        sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Find all files
        that contain net.ipv4.ip_forward
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.ip_forward\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Find all files
        that set net.ipv4.ip_forward to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*net.ipv4.ip_forward\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Comment out
        any occurrences of net.ipv4.ip_forward from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*net.ipv4.ip_forward
        replace: '#net.ipv4.ip_forward'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Comment out
        any occurrences of net.ipv4.ip_forward from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.ipv4.ip_forward
        replace: '#net.ipv4.ip_forward'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward

    - name: Disable Kernel Parameter for IP Forwarding on IPv4 Interfaces - Ensure sysctl
        net.ipv4.ip_forward is set to 0
      ansible.posix.sysctl:
        name: net.ipv4.ip_forward
        value: '0'
        sysctl_file: /etc/sysctl.d/net_ipv4_ip_forward.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.20
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-SC-5
      - NIST-800-53-SC-7(a)
      - PCI-DSS-Req-1.3.1
      - PCI-DSS-Req-1.3.2
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.3
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_net_ipv4_ip_forward
Group Uncommon Network Protocols Group contains 6 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 ATM Support [ref]
The Asynchronous Transfer Mode (ATM) is a protocol operating on network, data link, and physical layers, based on virtual circuits and virtual paths. To configure the system to prevent the `atm` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/atm.conf`:
    install atm /bin/false
This entry will cause a non-zero return value during a `atm` 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 atm /bin/true
Rationale: | Disabling ATM protects the system against exploitation of any flaws in its implementation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled
References: |
nist | AC-18
os-srg | SRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
cis | 3.2.1
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20atm%20/bin/false%0Ablacklist%20atm%0A
            mode: 0644
            path: /etc/modprobe.d/atm.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist atm$" /etc/modprobe.d/atm.conf ; then
    	echo "blacklist atm" >> /etc/modprobe.d/atm.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:
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_atm_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'atm' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/atm.conf
        regexp: install\s+atm
        line: install atm /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_atm_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'atm' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/atm.conf
        regexp: ^blacklist atm$
        line: blacklist atm
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_atm_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
#### 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
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
cis | 3.2.2
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20can%20/bin/false%0Ablacklist%20can%0A
            mode: 0644
            path: /etc/modprobe.d/can.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-18
      - disable_strategy
      - kernel_module_can_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
#### Rule Disable DCCP Support [ref]
The Datagram Congestion Control Protocol (DCCP) is a relatively new transport layer protocol, designed to support streaming media and telephony. To configure the system to prevent the `dccp` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/dccp.conf`:
    install dccp /bin/false
This entry will cause a non-zero return value during a `dccp` 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 dccp /bin/true
Rationale: | Disabling DCCP protects the system against exploitation of any flaws in its implementation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_dccp_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
pcidss | Req-1.4.2
os-srg | SRG-OS-000096-GPOS-00050, SRG-OS-000378-GPOS-00163
pcidss4 | 1.4.2, 1.4
cis | 3.2.3
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20dccp%20/bin/false%0Ablacklist%20dccp%0A
            mode: 0644
            path: /etc/modprobe.d/dccp.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist dccp$" /etc/modprobe.d/dccp.conf ; then
    	echo "blacklist dccp" >> /etc/modprobe.d/dccp.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
      - 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_dccp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'dccp' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/dccp.conf
        regexp: install\s+dccp
        line: install dccp /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1
      - 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_dccp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required

    - name: Ensure kernel module 'dccp' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/dccp.conf
        regexp: ^blacklist dccp$
        line: blacklist dccp
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1
      - 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_dccp_disabled
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
#### Rule Disable RDS Support [ref]
The Reliable Datagram Sockets (RDS) protocol is a transport layer protocol designed to provide reliable high-bandwidth, low-latency communications between nodes in a cluster. To configure the system to prevent the `rds` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/rds.conf`:
    install rds /bin/false
This entry will cause a non-zero return value during a `rds` 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 rds /bin/true
Rationale: | Disabling RDS protects the system against exploitation of any flaws in its implementation.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_rds_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
cis | 3.2.5
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20rds%20/bin/false%0Ablacklist%20rds%0A
            mode: 0644
            path: /etc/modprobe.d/rds.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist rds$" /etc/modprobe.d/rds.conf ; then
    	echo "blacklist rds" >> /etc/modprobe.d/rds.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:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_rds_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

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

    - name: Ensure kernel module 'rds' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/rds.conf
        regexp: ^blacklist rds$
        line: blacklist rds
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_rds_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - 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
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
cis | 3.2.6
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20sctp%20/bin/false%0Ablacklist%20sctp%0A
            mode: 0644
            path: /etc/modprobe.d/sctp.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.10.1
      - 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
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
cis | 3.2.4
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20tipc%20/bin/false%0Ablacklist%20tipc%0A
            mode: 0644
            path: /etc/modprobe.d/tipc.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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 File Permissions and Masks Group contains 7 groups and 70 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 Red Hat Enterprise Linux 10 installations:
    $ mount -t xfs | awk '{print $3}'
For any systems that use a different local filesystem type, modify this command as appropriate.
Group Verify Permissions on Important Files and Directories Group contains 1 group and 38 rules
[ref] Permissions for many files on a system must be set restrictively to ensure sensitive information is properly protected. This section discusses important permission restrictions which can be verified to ensure that no harmful discrepancies have arisen.
Group Verify Permissions on Files with Local Account Information and Credentials Group contains 33 rules
[ref] The default restrictive permissions for files which act as important security databases such as `passwd`, `shadow`, `group`, and `gshadow` files must be maintained. Many utilities need read access to the `passwd` file in order to function properly, but read access to the `shadow` file allows malicious attacks against system passwords, and should never be enabled.
#### Rule Verify Group Who Owns Backup group File [ref]
To properly set the group owner of `/etc/group-`, run the command:
    $ sudo chgrp root /etc/group-
Rationale: | The `/etc/group-` file is a backup file of `/etc/group`, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_group
References: |
nist | AC-6 (1)
pcidss | Req-8.7
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/group-" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/group-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_backup_etc_group_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_backup_etc_group_newgroup: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/group-
      ansible.builtin.stat:
        path: /etc/group-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/group-
      ansible.builtin.file:
        path: /etc/group-
        follow: false
        group: '{{ file_groupowner_backup_etc_group_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns Backup gshadow File [ref]
To properly set the group owner of `/etc/gshadow-`, run the command:
    $ sudo chgrp root /etc/gshadow-
Rationale: | The `/etc/gshadow-` file is a backup of `/etc/gshadow`, and as such, it contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_gshadow
References: |
nist | AC-6 (1)
pcidss | Req-8.7
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 7.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/gshadow-" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/gshadow-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_backup_etc_gshadow_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_backup_etc_gshadow_newgroup: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_groupowner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/gshadow-
      ansible.builtin.stat:
        path: /etc/gshadow-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_groupowner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/gshadow-
      ansible.builtin.file:
        path: /etc/gshadow-
        follow: false
        group: '{{ file_groupowner_backup_etc_gshadow_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_groupowner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns Backup passwd File [ref]
To properly set the group owner of `/etc/passwd-`, run the command:
    $ sudo chgrp root /etc/passwd-
Rationale: | The `/etc/passwd-` file is a backup file of `/etc/passwd`, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_passwd
References: |
nist | AC-6 (1)
pcidss | Req-8.7
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/passwd-" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/passwd-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_backup_etc_passwd_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_backup_etc_passwd_newgroup: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/passwd-
      ansible.builtin.stat:
        path: /etc/passwd-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/passwd-
      ansible.builtin.file:
        path: /etc/passwd-
        follow: false
        group: '{{ file_groupowner_backup_etc_passwd_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns Backup shadow File [ref]
To properly set the group owner of `/etc/shadow-`, run the command:
    $ sudo chgrp root /etc/shadow-
Rationale: | The `/etc/shadow-` file is a backup file of `/etc/shadow`, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_backup_etc_shadow
References: |
pcidss | Req-8.7
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/shadow-" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/shadow-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_backup_etc_shadow_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_backup_etc_shadow_newgroup: '0'
      tags:
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shadow-
      ansible.builtin.stat:
        path: /etc/shadow-
      register: file_exists
      tags:
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/shadow-
      ansible.builtin.file:
        path: /etc/shadow-
        follow: false
        group: '{{ file_groupowner_backup_etc_shadow_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSS-Req-8.7
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns group File [ref]
To properly set the group owner of `/etc/group`, run the command:
    $ sudo chgrp root /etc/group
Rationale: | The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_group
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/group" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/group
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_group_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_group_newgroup: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/group
      ansible.builtin.stat:
        path: /etc/group
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/group
      ansible.builtin.file:
        path: /etc/group
        follow: false
        group: '{{ file_groupowner_etc_group_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns gshadow File [ref]
To properly set the group owner of `/etc/gshadow`, run the command:
    $ sudo chgrp root /etc/gshadow
Rationale: | The `/etc/gshadow` file contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_gshadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/gshadow" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/gshadow
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_gshadow_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_gshadow_newgroup: '0'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/gshadow
      ansible.builtin.stat:
        path: /etc/gshadow
      register: file_exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/gshadow
      ansible.builtin.file:
        path: /etc/gshadow
        follow: false
        group: '{{ file_groupowner_etc_gshadow_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns passwd File [ref]
To properly set the group owner of `/etc/passwd`, run the command:
    $ sudo chgrp root /etc/passwd
Rationale: | The `/etc/passwd` file contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_passwd
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/passwd" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/passwd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_passwd_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_passwd_newgroup: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/passwd
      ansible.builtin.stat:
        path: /etc/passwd
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/passwd
      ansible.builtin.file:
        path: /etc/passwd
        follow: false
        group: '{{ file_groupowner_etc_passwd_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns /etc/security/opasswd File [ref]
To properly set the group owner of `/etc/security/opasswd`, run the command:
    $ sudo chgrp root /etc/security/opasswd
Rationale: | The `/etc/security/opasswd` file stores old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_security_opasswd
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/security/opasswd" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/security/opasswd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_security_opasswd_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_security_opasswd_newgroup: '0'
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/security/opasswd
      ansible.builtin.stat:
        path: /etc/security/opasswd
      register: file_exists
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/security/opasswd
      ansible.builtin.file:
        path: /etc/security/opasswd
        follow: false
        group: '{{ file_groupowner_etc_security_opasswd_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns /etc/security/opasswd.old File [ref]
To properly set the group owner of `/etc/security/opasswd.old`, run the command:
    $ sudo chgrp root /etc/security/opasswd.old
Rationale: | The `/etc/security/opasswd.old` file stores backups of old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_security_opasswd_old
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/security/opasswd.old" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/security/opasswd.old
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_security_opasswd_old_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_security_opasswd_old_newgroup: '0'
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/security/opasswd.old
      ansible.builtin.stat:
        path: /etc/security/opasswd.old
      register: file_exists
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/security/opasswd.old
      ansible.builtin.file:
        path: /etc/security/opasswd.old
        follow: false
        group: '{{ file_groupowner_etc_security_opasswd_old_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_groupowner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns shadow File [ref]
To properly set the group owner of `/etc/shadow`, run the command:
    $ sudo chgrp root /etc/shadow
Rationale: | The `/etc/shadow` file stores password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_shadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/shadow" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/shadow
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_shadow_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_shadow_newgroup: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shadow
      ansible.builtin.stat:
        path: /etc/shadow
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/shadow
      ansible.builtin.file:
        path: /etc/shadow
        follow: false
        group: '{{ file_groupowner_etc_shadow_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns /etc/shells File [ref]
To properly set the group owner of `/etc/shells`, run the command:
    $ sudo chgrp root /etc/shells
Rationale: | The `/etc/shells` file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_etc_shells
References: |
nist | AC-3, MP-2
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/shells" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/shells
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_groupowner_etc_shells_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_etc_shells_newgroup: '0'
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_groupowner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shells
      ansible.builtin.stat:
        path: /etc/shells
      register: file_exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_groupowner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/shells
      ansible.builtin.file:
        path: /etc/shells
        follow: false
        group: '{{ file_groupowner_etc_shells_newgroup }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_groupowner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns Backup group File [ref]
To properly set the owner of `/etc/group-`, run the command:
    $ sudo chown root /etc/group-
Rationale: | The `/etc/group-` file is a backup file of `/etc/group`, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_backup_etc_group
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/group-" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/group-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_backup_etc_group_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_backup_etc_group_newown: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/group-
      ansible.builtin.stat:
        path: /etc/group-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/group-
      ansible.builtin.file:
        path: /etc/group-
        follow: false
        owner: '{{ file_owner_backup_etc_group_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns Backup gshadow File [ref]
To properly set the owner of `/etc/gshadow-`, run the command:
    $ sudo chown root /etc/gshadow-
Rationale: | The `/etc/gshadow-` file is a backup of `/etc/gshadow`, and as such, it contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_backup_etc_gshadow
References: |
nist | AC-6 (1)
pcidss | Req-8.7
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 7.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/gshadow-" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/gshadow-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_backup_etc_gshadow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_backup_etc_gshadow_newown: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_owner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/gshadow-
      ansible.builtin.stat:
        path: /etc/gshadow-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_owner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/gshadow-
      ansible.builtin.file:
        path: /etc/gshadow-
        follow: false
        owner: '{{ file_owner_backup_etc_gshadow_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7
      - configure_strategy
      - file_owner_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns Backup passwd File [ref]
To properly set the owner of `/etc/passwd-`, run the command:
    $ sudo chown root /etc/passwd-
Rationale: | The `/etc/passwd-` file is a backup file of `/etc/passwd`, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_backup_etc_passwd
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/passwd-" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/passwd-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_backup_etc_passwd_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_backup_etc_passwd_newown: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/passwd-
      ansible.builtin.stat:
        path: /etc/passwd-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/passwd-
      ansible.builtin.file:
        path: /etc/passwd-
        follow: false
        owner: '{{ file_owner_backup_etc_passwd_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns Backup shadow File [ref]
To properly set the owner of `/etc/shadow-`, run the command:
    $ sudo chown root /etc/shadow-
Rationale: | The `/etc/shadow-` file is a backup file of `/etc/shadow`, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_backup_etc_shadow
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/shadow-" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/shadow-
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_backup_etc_shadow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_backup_etc_shadow_newown: '0'
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shadow-
      ansible.builtin.stat:
        path: /etc/shadow-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/shadow-
      ansible.builtin.file:
        path: /etc/shadow-
        follow: false
        owner: '{{ file_owner_backup_etc_shadow_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns group File [ref]
To properly set the owner of `/etc/group`, run the command:
    $ sudo chown root /etc/group
Rationale: | The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_group
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/group" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/group
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_group_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_group_newown: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/group
      ansible.builtin.stat:
        path: /etc/group
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/group
      ansible.builtin.file:
        path: /etc/group
        follow: false
        owner: '{{ file_owner_etc_group_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns gshadow File [ref]
To properly set the owner of `/etc/gshadow`, run the command:
    $ sudo chown root /etc/gshadow
Rationale: | The `/etc/gshadow` file contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_gshadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/gshadow" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/gshadow
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_gshadow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_gshadow_newown: '0'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/gshadow
      ansible.builtin.stat:
        path: /etc/gshadow
      register: file_exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/gshadow
      ansible.builtin.file:
        path: /etc/gshadow
        follow: false
        owner: '{{ file_owner_etc_gshadow_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns passwd File [ref]
To properly set the owner of `/etc/passwd`, run the command:
    $ sudo chown root /etc/passwd
Rationale: | The `/etc/passwd` file contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_passwd
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/passwd" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/passwd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_passwd_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_passwd_newown: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/passwd
      ansible.builtin.stat:
        path: /etc/passwd
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/passwd
      ansible.builtin.file:
        path: /etc/passwd
        follow: false
        owner: '{{ file_owner_etc_passwd_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns /etc/security/opasswd File [ref]
To properly set the owner of `/etc/security/opasswd`, run the command:
    $ sudo chown root /etc/security/opasswd
Rationale: | The `/etc/security/opasswd` file stores old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_security_opasswd
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/security/opasswd" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/security/opasswd
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_security_opasswd_newown variable if represented by
        uid
      ansible.builtin.set_fact:
        file_owner_etc_security_opasswd_newown: '0'
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/security/opasswd
      ansible.builtin.stat:
        path: /etc/security/opasswd
      register: file_exists
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/security/opasswd
      ansible.builtin.file:
        path: /etc/security/opasswd
        follow: false
        owner: '{{ file_owner_etc_security_opasswd_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns /etc/security/opasswd.old File [ref]
To properly set the owner of `/etc/security/opasswd.old`, run the command:
    $ sudo chown root /etc/security/opasswd.old
Rationale: | The `/etc/security/opasswd.old` file stores backups of old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_security_opasswd_old
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/security/opasswd.old" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/security/opasswd.old
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_security_opasswd_old_newown variable if represented
        by uid
      ansible.builtin.set_fact:
        file_owner_etc_security_opasswd_old_newown: '0'
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/security/opasswd.old
      ansible.builtin.stat:
        path: /etc/security/opasswd.old
      register: file_exists
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/security/opasswd.old
      ansible.builtin.file:
        path: /etc/security/opasswd.old
        follow: false
        owner: '{{ file_owner_etc_security_opasswd_old_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_owner_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns shadow File [ref]
To properly set the owner of `/etc/shadow`, run the command:
    $ sudo chown root /etc/shadow
Rationale: | The `/etc/shadow` file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_shadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/shadow" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/shadow
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_shadow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_shadow_newown: '0'
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shadow
      ansible.builtin.stat:
        path: /etc/shadow
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/shadow
      ansible.builtin.file:
        path: /etc/shadow
        follow: false
        owner: '{{ file_owner_etc_shadow_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Who Owns /etc/shells File [ref]
To properly set the owner of `/etc/shells`, run the command:
    $ sudo chown root /etc/shells
Rationale: | The `/etc/shells` file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_etc_shells
References: |
nist | AC-3, MP-2
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/shells" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/shells
    fi

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Set the file_owner_etc_shells_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_etc_shells_newown: '0'
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_owner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/shells
      ansible.builtin.stat:
        path: /etc/shells
      register: file_exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_owner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/shells
      ansible.builtin.file:
        path: /etc/shells
        follow: false
        owner: '{{ file_owner_etc_shells_newown }}'
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_owner_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on Backup group File [ref]
To properly set the permissions of `/etc/group-`, run the command:
    $ sudo chmod 0644 /etc/group-
Rationale: | The `/etc/group-` file is a backup file of `/etc/group`, and as such, it contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_backup_etc_group
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/group-
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/group-
      ansible.builtin.stat:
        path: /etc/group-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/group-
      ansible.builtin.file:
        path: /etc/group-
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on Backup gshadow File [ref]
To properly set the permissions of `/etc/gshadow-`, run the command:
    $ sudo chmod 0000 /etc/gshadow-
Rationale: | The `/etc/gshadow-` file is a backup of `/etc/gshadow`, and as such, it contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_backup_etc_gshadow
References: |
nist | AC-6 (1)
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 7.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xwrs,g-xwrs,o-xwrt /etc/gshadow-
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/gshadow-
      ansible.builtin.stat:
        path: /etc/gshadow-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - configure_strategy
      - file_permissions_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xwrs,g-xwrs,o-xwrt on /etc/gshadow-
      ansible.builtin.file:
        path: /etc/gshadow-
        mode: u-xwrs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - configure_strategy
      - file_permissions_backup_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on Backup passwd File [ref]
To properly set the permissions of `/etc/passwd-`, run the command:
    $ sudo chmod 0644 /etc/passwd-
Rationale: | The `/etc/passwd-` file is a backup file of `/etc/passwd`, and as such, it contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_backup_etc_passwd
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/passwd-
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/passwd-
      ansible.builtin.stat:
        path: /etc/passwd-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/passwd-
      ansible.builtin.file:
        path: /etc/passwd-
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on Backup shadow File [ref]
To properly set the permissions of `/etc/shadow-`, run the command:
    $ sudo chmod 0000 /etc/shadow-
Rationale: | The `/etc/shadow-` file is a backup file of `/etc/shadow`, and as such, it contains the list of local system accounts and password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_backup_etc_shadow
References: |
nist | AC-6 (1)
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xwrs,g-xwrs,o-xwrt /etc/shadow-
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/shadow-
      ansible.builtin.stat:
        path: /etc/shadow-
      register: file_exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xwrs,g-xwrs,o-xwrt on /etc/shadow-
      ansible.builtin.file:
        path: /etc/shadow-
        mode: u-xwrs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6 (1)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_backup_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on group File [ref]
To properly set the permissions of `/etc/group`, run the command:
    $ sudo chmod 0644 /etc/group
Rationale: | The `/etc/group` file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_group
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/group
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/group
      ansible.builtin.stat:
        path: /etc/group
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/group
      ansible.builtin.file:
        path: /etc/group
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_group
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on gshadow File [ref]
To properly set the permissions of `/etc/gshadow`, run the command:
    $ sudo chmod 0000 /etc/gshadow
Rationale: | The `/etc/gshadow` file contains group password hashes. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_gshadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xwrs,g-xwrs,o-xwrt /etc/gshadow
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/gshadow
      ansible.builtin.stat:
        path: /etc/gshadow
      register: file_exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xwrs,g-xwrs,o-xwrt on /etc/gshadow
      ansible.builtin.file:
        path: /etc/gshadow
        mode: u-xwrs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_etc_gshadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on passwd File [ref]
To properly set the permissions of `/etc/passwd`, run the command:
    $ sudo chmod 0644 /etc/passwd
Rationale: | If the `/etc/passwd` file is writable by a group-owner or the world the risk of its compromise is increased. The file contains the list of accounts on the system and associated information, and protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_passwd
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/passwd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/passwd
      ansible.builtin.stat:
        path: /etc/passwd
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/passwd
      ansible.builtin.file:
        path: /etc/passwd
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_passwd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on /etc/security/opasswd File [ref]
To properly set the permissions of `/etc/security/opasswd`, run the command:
    $ sudo chmod 0600 /etc/security/opasswd
Rationale: | The `/etc/security/opasswd` file stores old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_security_opasswd
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xwrs,o-xwrt /etc/security/opasswd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/security/opasswd
      ansible.builtin.stat:
        path: /etc/security/opasswd
      register: file_exists
      tags:
      - configure_strategy
      - file_permissions_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xwrs,o-xwrt on /etc/security/opasswd
      ansible.builtin.file:
        path: /etc/security/opasswd
        mode: u-xs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_permissions_etc_security_opasswd
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on /etc/security/opasswd.old File [ref]
To properly set the permissions of `/etc/security/opasswd.old`, run the command:
    $ sudo chmod 0600 /etc/security/opasswd.old
Rationale: | The `/etc/security/opasswd.old` file stores backups of old passwords to prevent password reuse. Protection of this file is critical for system security.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_security_opasswd_old
References: |
bsi | SYS.1.3.A14
cis | 7.1.10
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xwrs,o-xwrt /etc/security/opasswd.old
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/security/opasswd.old
      ansible.builtin.stat:
        path: /etc/security/opasswd.old
      register: file_exists
      tags:
      - configure_strategy
      - file_permissions_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xwrs,o-xwrt on /etc/security/opasswd.old
      ansible.builtin.file:
        path: /etc/security/opasswd.old
        mode: u-xs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - configure_strategy
      - file_permissions_etc_security_opasswd_old
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on shadow File [ref]
To properly set the permissions of `/etc/shadow`, run the command:
    $ sudo chmod 0000 /etc/shadow
Rationale: | The `/etc/shadow` file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_shadow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cjis | 5.5.2.2
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
pcidss | Req-8.7.c
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xwrs,g-xwrs,o-xwrt /etc/shadow
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/shadow
      ansible.builtin.stat:
        path: /etc/shadow
      register: file_exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xwrs,g-xwrs,o-xwrt on /etc/shadow
      ansible.builtin.file:
        path: /etc/shadow
        mode: u-xwrs,g-xwrs,o-xwrt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - CJIS-5.5.2.2
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-8.7.c
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_etc_shadow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on /etc/shells File [ref]
To properly set the permissions of `/etc/shells`, run the command:
    $ sudo chmod 0644 /etc/shells
Rationale: | The `/etc/shells` file contains the list of full pathnames to shells on the system. Since this file is used by many system programs this file should be protected.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_etc_shells
References: |
nist | AC-3, MP-2
anssi | R50
bsi | SYS.1.3.A14
cis | 7.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    chmod u-xs,g-xws,o-xwt /etc/shells
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    - name: Test for existence /etc/shells
      ansible.builtin.stat:
        path: /etc/shells
      register: file_exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_permissions_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwt on /etc/shells
      ansible.builtin.file:
        path: /etc/shells
        mode: u-xs,g-xws,o-xwt
      when: file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-3
      - NIST-800-53-MP-2
      - configure_strategy
      - file_permissions_etc_shells
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify that All World-Writable Directories Have Sticky Bits Set [ref]
When the so-called 'sticky bit' is set on a directory, only the owner of a given file may remove that file from the directory. Without the sticky bit, any user with write access to a directory may remove any file in the directory. Setting the sticky bit prevents users from removing each other's files. In cases where there is no reason for a directory to be world-writable, a better solution is to remove that permission rather than to set the sticky bit. However, if a directory is used by a particular application, consult that application's documentation instead of blindly changing modes. To set the sticky bit on a world-writable directory DIR, run the following command:
    $ sudo chmod +t DIR
Warning: This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of directories present on the system. It is not a problem in most cases, but especially systems with a large number of directories can be affected. See `https://access.redhat.com/articles/6999111`. Warning: Please note that there might be cases where the rule remediation cannot fix directory permissions. This can happen for example when running on a system with some immutable parts. These immutable parts cannot be remediated because they are read-only. Example of such directories can be OStree deployments located at `/sysroot/ostree/deploy`. In such case, it is needed to make modifications to the underlying ostree snapshot and this is out of scope of regular rule remediation.
Rationale: | Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure. The only authorized public directories are those temporary directories supplied with the system, or those designed to be temporary file repositories. The setting is normally reserved for directories used by the system, by users for temporary file storage (such as `/tmp`), and for directories requiring global read/write access.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dir_perms_world_writable_sticky_bits
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
os-srg | SRG-OS-000138-GPOS-00069
anssi | R54
ism | 1409
pcidss4 | 2.2.6, 2.2
cis | 7.1.11
Remediation Shell script ⇲
    df --local -P | awk '{if (NR!=1) print $6}' \
    | xargs -I '$6' find '$6' -xdev -type d \
    \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null \
    -exec chmod a+t {} +
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    - name: Verify that All World-Writable Directories Have Sticky Bits Set - Define Excluded
        (Non-Local) File Systems and Paths
      ansible.builtin.set_fact:
        excluded_fstypes:
        - afs
        - autofs
        - ceph
        - cifs
        - smb3
        - smbfs
        - sshfs
        - ncpfs
        - ncp
        - nfs
        - nfs4
        - gfs
        - gfs2
        - glusterfs
        - gpfs
        - pvfs2
        - ocfs2
        - lustre
        - davfs
        - fuse.sshfs
        excluded_paths:
        - dev
        - proc
        - run
        - sys
        search_paths: []
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - dir_perms_world_writable_sticky_bits
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

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

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

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

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

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

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

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

    - name: Verify that All World-Writable Directories Have Sticky Bits Set - Ensure Sticky
        Bit is Set on Local World Writable Directories
      ansible.builtin.file:
        path: '{{ item }}'
        mode: a+t
      loop: '{{ world_writable_dirs }}'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - dir_perms_world_writable_sticky_bits
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure No World-Writable Files Exist [ref]
It is generally a good idea to remove global (other) write access to a file when it is discovered. However, check with documentation for specific applications before making changes. Also, monitor for recurring world-writable files, as these may be symptoms of a misconfigured application or user account. Finally, this applies to real files and not virtual files that are a part of pseudo file systems such as `sysfs` or `procfs`. Warning: This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See `https://access.redhat.com/articles/6999111`.
Rationale: | Data in world-writable files can be modified by any user on the system. In almost all circumstances, files can be configured using a combination of user and group permissions to support whatever legitimate access is needed without the risk caused by world-writable files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_world_writable
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
anssi | R54
ism | 1409
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 7.1.11
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    FILTER_NODEV=$(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)

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

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

    # Ensure /tmp is also fixed when tmpfs is used.
    if grep "^tmpfs /tmp" /proc/mounts; then
      find /tmp -xdev -type f -perm -002 -exec chmod o-w {} \; 2>/dev/null
    fi
#### Rule Ensure All Files And Directories Are Owned by a Group [ref]
If any files or directories are not group-owned by a valid defined group, the cause of the lack of group-ownership must be investigated. Following this, those files should be deleted or assigned to an appropriate group. The groups need to be defined in `/etc/group` or in `/usr/lib/group` if `nss-altfiles` are configured to be used in `/etc/nsswitch.conf`. Locate the mount points related to local devices by the following command:
    $ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files and directories which do not belong to a valid group using the following command:
    $ sudo find MOUNTPOINT -xdev -nogroup 2>/dev/null
Warning: This rule only considers local groups as valid groups. If you have your groups defined outside `/etc/group` or `/usr/lib/group`, the rule won't consider those. Warning: This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See `https://access.redhat.com/articles/6999111`.
Rationale: | Unowned files and directories do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files and directories should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_files_or_dirs_ungroupowned
References: |
bsi | SYS.1.3.A14
cis | 7.1.12
#### Rule Ensure All Files And Directories Are Owned by a User [ref]
If any files or directories are not owned by a user, then the cause of their lack of ownership should be investigated. Following this, the files should be deleted or assigned to an appropriate user. Locate the mount points related to local devices by the following command:
    $ findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
For all mount points listed by the previous command, it is necessary to search for files and directories which do not belong to a valid user using the following command:
    $ sudo find MOUNTPOINT -xdev -nouser 2>/dev/null
Warning: For this rule to evaluate centralized user accounts, `getent` must be working properly so that running the command
    getent passwd
returns a list of all users in your organization. If using the System Security Services Daemon (SSSD),
    enumerate = true
must be configured in your organization's domain to return a complete list of users Warning: This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of files present on the system. It is not a problem in most cases, but especially systems with a large number of files can be affected. See `https://access.redhat.com/articles/6999111`.
Rationale: | Unowned files and directories do not directly imply a security problem, but they are generally a sign that something is amiss. They may be caused by an intruder, by incorrect software installation or draft software removal, or by failure to remove all files belonging to a deleted account, or other similar cases. The files and directories should be repaired so they will not cause problems when accounts are created in the future, and the cause should be discovered and addressed.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_no_files_or_dirs_unowned_by_user
References: |
bsi | SYS.1.3.A14
cis | 7.1.12
#### Rule Enable Kernel Parameter to Enforce DAC on Hardlinks [ref]
To set the runtime status of the `fs.protected_hardlinks` kernel parameter, run the following command:
    $ sudo sysctl -w fs.protected_hardlinks=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    fs.protected_hardlinks = 1
Rationale: | By enabling this kernel parameter, users can no longer create soft or hard links to files which they do not own. Disallowing such hardlinks mitigate vulnerabilities based on insecure file system accessed by privileged programs, avoiding an exploitation vector exploiting unsafe use of `open()` or `creat()`.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_fs_protected_hardlinks
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-6(a), AC-6(1)
os-srg | SRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000324-GPOS-00125
anssi | R14
cis | 1.5.2
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,fs.protected_hardlinks%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_fs_protected_hardlinks.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of fs.protected_hardlinks 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]*fs.protected_hardlinks.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "fs.protected_hardlinks" 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/fs_protected_hardlinks.conf'

    #
    # Set runtime for fs.protected_hardlinks
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w fs.protected_hardlinks="1"
    fi

    #
    # If fs.protected_hardlinks present in /etc/sysctl.conf, change value to "1"
    #	else, add "fs.protected_hardlinks = 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' <<< "^fs.protected_hardlinks")

    # 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 "^fs.protected_hardlinks\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^fs.protected_hardlinks\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Set fact for sysctl
        paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Find all files that
        contain fs.protected_hardlinks
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*fs.protected_hardlinks\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Find all files that
        set fs.protected_hardlinks to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*fs.protected_hardlinks\s*=\s*1$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Comment out any occurrences
        of fs.protected_hardlinks from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*fs.protected_hardlinks
        replace: '#fs.protected_hardlinks'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Comment out any occurrences
        of fs.protected_hardlinks from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*fs.protected_hardlinks
        replace: '#fs.protected_hardlinks'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks

    - name: Enable Kernel Parameter to Enforce DAC on Hardlinks - Ensure sysctl fs.protected_hardlinks
        is set to 1
      ansible.posix.sysctl:
        name: fs.protected_hardlinks
        value: '1'
        sysctl_file: /etc/sysctl.d/fs_protected_hardlinks.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_protected_hardlinks
Group Restrict Dynamic Mounting and Unmounting of Filesystems Group contains 5 rules
[ref] Linux includes a number of facilities for the automated addition and removal of filesystems on a running system. These facilities may be necessary in many environments, but this capability also carries some risk -- whether direct risk from allowing users to introduce arbitrary filesystems, or risk that software flaws in the automated mount facility itself could allow an attacker to compromise the system. This command can be used to list the types of filesystems that are available to the currently executing kernel:
    $ find /lib/modules/`uname -r`/kernel/fs -type f -name '*.ko'
If these filesystems are not required then they can be explicitly disabled in a configuratio file in `/etc/modprobe.d`.
#### Rule Disable Mounting of cramfs [ref]
To configure the system to prevent the `cramfs` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/cramfs.conf`:
    install cramfs /bin/false
This entry will cause a non-zero return value during a `cramfs` 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 cramfs /bin/true
This effectively prevents usage of this uncommon filesystem. The `cramfs` filesystem type is a compressed read-only Linux filesystem embedded in small footprint systems. A `cramfs` image can be used without having to first decompress the image.
Rationale: | Removing support for unneeded filesystem types reduces the local attack surface of the server.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled
References: |
cis-csc | 11, 14, 3, 9
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
os-srg | SRG-OS-000095-GPOS-00049
cis | 1.1.1.1
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20cramfs%20/bin/false%0Ablacklist%20cramfs%0A
            mode: 0644
            path: /etc/modprobe.d/cramfs.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist cramfs$" /etc/modprobe.d/cramfs.conf ; then
    	echo "blacklist cramfs" >> /etc/modprobe.d/cramfs.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:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_cramfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'cramfs' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/cramfs.conf
        regexp: install\s+cramfs
        line: install cramfs /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_cramfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'cramfs' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/cramfs.conf
        regexp: ^blacklist cramfs$
        line: blacklist cramfs
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_cramfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
#### Rule Disable Mounting of freevxfs [ref]
To configure the system to prevent the `freevxfs` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/freevxfs.conf`:
    install freevxfs /bin/false
This entry will cause a non-zero return value during a `freevxfs` 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 freevxfs /bin/true
This effectively prevents usage of this uncommon filesystem.
Rationale: | Linux kernel modules which implement filesystems that are not needed by the local system should be disabled.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_freevxfs_disabled
References: |
cis-csc | 11, 14, 3, 9
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
cis | 1.1.1.2
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20freevxfs%20/bin/false%0Ablacklist%20freevxfs%0A
            mode: 0644
            path: /etc/modprobe.d/freevxfs.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist freevxfs$" /etc/modprobe.d/freevxfs.conf ; then
    	echo "blacklist freevxfs" >> /etc/modprobe.d/freevxfs.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:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_freevxfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'freevxfs' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/freevxfs.conf
        regexp: install\s+freevxfs
        line: install freevxfs /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_freevxfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'freevxfs' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/freevxfs.conf
        regexp: ^blacklist freevxfs$
        line: blacklist freevxfs
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_freevxfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
#### Rule Disable Mounting of hfs [ref]
To configure the system to prevent the `hfs` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/hfs.conf`:
    install hfs /bin/false
This entry will cause a non-zero return value during a `hfs` 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 hfs /bin/true
This effectively prevents usage of this uncommon filesystem.
Rationale: | Linux kernel modules which implement filesystems that are not needed by the local system should be disabled.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_hfs_disabled
References: |
cis-csc | 11, 14, 3, 9
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
cis | 1.1.1.3
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20hfs%20/bin/false%0Ablacklist%20hfs%0A
            mode: 0644
            path: /etc/modprobe.d/hfs.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist hfs$" /etc/modprobe.d/hfs.conf ; then
    	echo "blacklist hfs" >> /etc/modprobe.d/hfs.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:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'hfs' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/hfs.conf
        regexp: install\s+hfs
        line: install hfs /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'hfs' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/hfs.conf
        regexp: ^blacklist hfs$
        line: blacklist hfs
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfs_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
#### Rule Disable Mounting of hfsplus [ref]
To configure the system to prevent the `hfsplus` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/hfsplus.conf`:
    install hfsplus /bin/false
This entry will cause a non-zero return value during a `hfsplus` 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 hfsplus /bin/true
This effectively prevents usage of this uncommon filesystem.
Rationale: | Linux kernel modules which implement filesystems that are not needed by the local system should be disabled.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_hfsplus_disabled
References: |
cis-csc | 11, 14, 3, 9
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
cis | 1.1.1.4
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20hfsplus%20/bin/false%0Ablacklist%20hfsplus%0A
            mode: 0644
            path: /etc/modprobe.d/hfsplus.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist hfsplus$" /etc/modprobe.d/hfsplus.conf ; then
    	echo "blacklist hfsplus" >> /etc/modprobe.d/hfsplus.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:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfsplus_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'hfsplus' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/hfsplus.conf
        regexp: install\s+hfsplus
        line: install hfsplus /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfsplus_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'hfsplus' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/hfsplus.conf
        regexp: ^blacklist hfsplus$
        line: blacklist hfsplus
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_hfsplus_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
#### Rule Disable Mounting of jffs2 [ref]
To configure the system to prevent the `jffs2` kernel module from being loaded, add the following line to the file `/etc/modprobe.d/jffs2.conf`:
    install jffs2 /bin/false
This entry will cause a non-zero return value during a `jffs2` 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 jffs2 /bin/true
This effectively prevents usage of this uncommon filesystem.
Rationale: | Linux kernel modules which implement filesystems that are not needed by the local system should be disabled.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_kernel_module_jffs2_disabled
References: |
cis-csc | 11, 14, 3, 9
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
cis | 1.1.1.5
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,install%20jffs2%20/bin/false%0Ablacklist%20jffs2%0A
            mode: 0644
            path: /etc/modprobe.d/jffs2.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

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

    if ! LC_ALL=C grep -q -m 1 "^blacklist jffs2$" /etc/modprobe.d/jffs2.conf ; then
    	echo "blacklist jffs2" >> /etc/modprobe.d/jffs2.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:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_jffs2_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'jffs2' is disabled
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/jffs2.conf
        regexp: install\s+jffs2
        line: install jffs2 /bin/false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_jffs2_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required

    - name: Ensure kernel module 'jffs2' is blacklisted
      ansible.builtin.lineinfile:
        create: true
        dest: /etc/modprobe.d/jffs2.conf
        regexp: ^blacklist jffs2$
        line: blacklist jffs2
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.4.6
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - kernel_module_jffs2_disabled
      - low_complexity
      - low_severity
      - medium_disruption
      - reboot_required
Group Restrict Partition Mount Options Group contains 19 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 /dev/shm [ref]
The `nodev` mount option can be used to prevent creation of device files in `/dev/shm`. Legitimate character and block devices should not exist within temporary directories like `/dev/shm`. Add the `nodev` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/dev/shm`.
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_dev_shm_nodev
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
ism | 1409
cis | 1.1.2.2.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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

    function perform_remediation {

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

        # 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="tmpfs"
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo "tmpfs /dev/shm tmpfs 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 "/dev/shm"; then
            if mountpoint -q "/dev/shm"; then
                mount -o remount --target "/dev/shm"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_dev_shm_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /dev/shm: Check information associated to mountpoint'
      ansible.builtin.command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when: ( not ( "kernel-core" 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-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_dev_shm_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /dev/shm: 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-core" 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"] ) )
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - 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_dev_shm_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /dev/shm: If /dev/shm not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /dev/shm
        - tmpfs
        - tmpfs
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - ("" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - 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_dev_shm_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /dev/shm: Make sure nodev option is part of the to /dev/shm
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - 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_dev_shm_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /dev/shm: Ensure /dev/shm is mounted with nodev option'
      ansible.posix.mount:
        path: /dev/shm
        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-core" 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"] ) )
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
        length == 0)
      tags:
      - 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_dev_shm_nodev
      - no_reboot_needed
#### Rule Add noexec Option to /dev/shm [ref]
The `noexec` mount option can be used to prevent binaries from being executed out of `/dev/shm`. It can be dangerous to allow the execution of binaries from world-writable temporary storage directories such as `/dev/shm`. Add the `noexec` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/dev/shm`.
Rationale: | Allowing users to execute binaries from world-writable directories such as `/dev/shm` can expose the system to potential compromise.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_dev_shm_noexec
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
ism | 1409
cis | 1.1.2.2.4
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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

    function perform_remediation {

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

        # 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="tmpfs"
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo "tmpfs /dev/shm tmpfs 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 "/dev/shm"; then
            if mountpoint -q "/dev/shm"; then
                mount -o remount --target "/dev/shm"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_dev_shm_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /dev/shm: Check information associated to mountpoint'
      ansible.builtin.command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when: ( not ( "kernel-core" 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-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_dev_shm_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /dev/shm: 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-core" 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"] ) )
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - 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_dev_shm_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /dev/shm: If /dev/shm not mounted, craft mount_info
        manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /dev/shm
        - tmpfs
        - tmpfs
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - ("" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - 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_dev_shm_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /dev/shm: Make sure noexec option is part of the to
        /dev/shm options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''noexec''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - mount_info is defined and "noexec" not in (mount_info.options | default(''))
      tags:
      - 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_dev_shm_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /dev/shm: Ensure /dev/shm is mounted with noexec option'
      ansible.posix.mount:
        path: /dev/shm
        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-core" 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"] ) )
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
        length == 0)
      tags:
      - 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_dev_shm_noexec
      - no_reboot_needed
#### Rule Add nosuid Option to /dev/shm [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/dev/shm`. The SUID and SGID permissions should not be required in these world-writable directories. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/dev/shm`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_dev_shm_nosuid
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
ism | 1409
cis | 1.1.2.2.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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

    function perform_remediation {

        mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

        # 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="tmpfs"
            if [  "$fs_type" == "iso9660" ] ; then
                previous_mount_opts=$(sed 's/blocksize=/block=/' <<< "$previous_mount_opts")
            fi
            echo "tmpfs /dev/shm tmpfs 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 "/dev/shm"; then
            if mountpoint -q "/dev/shm"; then
                mount -o remount --target "/dev/shm"
            fi
        fi
    }

    perform_remediation

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_dev_shm_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /dev/shm: Check information associated to mountpoint'
      ansible.builtin.command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when: ( not ( "kernel-core" 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-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_dev_shm_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /dev/shm: 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-core" 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"] ) )
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length > 0)
      tags:
      - 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_dev_shm_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /dev/shm: If /dev/shm not mounted, craft mount_info
        manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /dev/shm
        - tmpfs
        - tmpfs
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - ("" | length == 0)
      - device_name.stdout is defined and device_name.stdout_lines is defined
      - (device_name.stdout | length == 0)
      tags:
      - 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_dev_shm_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /dev/shm: Make sure nosuid option is part of the to
        /dev/shm options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - 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_dev_shm_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /dev/shm: Ensure /dev/shm is mounted with nosuid option'
      ansible.posix.mount:
        path: /dev/shm
        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-core" 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"] ) )
      - mount_info is defined
      - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
        length == 0)
      tags:
      - 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_dev_shm_nosuid
      - no_reboot_needed
#### Rule Add nodev Option to /home [ref]
The `nodev` mount option can be used to prevent device files from being created in `/home`. 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 `/home`.
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: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_home_nodev
References: |
os-srg | SRG-OS-000368-GPOS-00154
cis | 1.1.2.3.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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 "/home" > /dev/null || findmnt --fstab "/home" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /home 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:]]" "/home")"

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

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

        # 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 " /home  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 "/home"; then
            if mountpoint -q "/home"; then
                mount -o remount --target "/home"
            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 /home --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity

    - name: 'Add nodev Option to /home: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/home'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity

    - name: 'Add nodev Option to /home: 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-core" 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"] ) )
      - '"/home" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity

    - name: 'Add nodev Option to /home: If /home not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /home
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity

    - name: 'Add nodev Option to /home: Make sure nodev option is part of the to /home
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity

    - name: 'Add nodev Option to /home: Ensure /home is mounted with nodev option'
      ansible.posix.mount:
        path: /home
        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-core" 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"] ) )
      - '"/home" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - mount_option_home_nodev
      - no_reboot_needed
      - unknown_severity
#### Rule Add nosuid Option to /home [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/home`. The SUID and SGID permissions should not be required in these user data directories. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/home`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from user home directory partitions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_home_nosuid
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154, SRG-OS-000480-GPOS-00227
anssi | R28
cis | 1.1.2.3.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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 "/home" > /dev/null || findmnt --fstab "/home" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /home 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:]]" "/home")"

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

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

        # 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 " /home  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 "/home"; then
            if mountpoint -q "/home"; then
                mount -o remount --target "/home"
            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 /home --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_home_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /home: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/home'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_home_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /home: 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-core" 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"] ) )
      - '"/home" 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:
      - 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_home_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /home: If /home not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /home
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" 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:
      - 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_home_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /home: Make sure nosuid option is part of the to /home
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/home" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - 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_home_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /home: Ensure /home is mounted with nosuid option'
      ansible.posix.mount:
        path: /home
        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-core" 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"] ) )
      - '"/home" 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:
      - 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_home_nosuid
      - no_reboot_needed
#### Rule Add nodev Option to /tmp [ref]
The `nodev` mount option can be used to prevent device files from being created in `/tmp`. Legitimate character and block devices should not exist within temporary directories like `/tmp`. Add the `nodev` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/tmp`.
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_tmp_nodev
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
cis | 1.1.2.1.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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 "/tmp" > /dev/null || findmnt --fstab "/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /tmp 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:]]" "/tmp")"

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

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

        # 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 " /tmp  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 "/tmp"; then
            if mountpoint -q "/tmp"; then
                mount -o remount --target "/tmp"
            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 /tmp --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /tmp: 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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /tmp: If /tmp not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /tmp: Make sure nodev option is part of the to /tmp options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - 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_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /tmp: Ensure /tmp is mounted with nodev option'
      ansible.posix.mount:
        path: /tmp
        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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nodev
      - no_reboot_needed
#### Rule Add noexec Option to /tmp [ref]
The `noexec` mount option can be used to prevent binaries from being executed out of `/tmp`. Add the `noexec` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/tmp`.
Rationale: | Allowing users to execute binaries from world-writable directories such as `/tmp` 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_tmp_noexec
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.1.4
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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 "/tmp" > /dev/null || findmnt --fstab "/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /tmp 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:]]" "/tmp")"

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

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

        # 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 " /tmp  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 "/tmp"; then
            if mountpoint -q "/tmp"; then
                mount -o remount --target "/tmp"
            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 /tmp --mountoptions="noexec"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /tmp: 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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /tmp: If /tmp not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /tmp: Make sure noexec option is part of the to /tmp
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''noexec''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "noexec" not in (mount_info.options | default(''))
      tags:
      - 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_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /tmp: Ensure /tmp is mounted with noexec option'
      ansible.posix.mount:
        path: /tmp
        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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_noexec
      - no_reboot_needed
#### Rule Add nosuid Option to /tmp [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/tmp`. The SUID and SGID permissions should not be required in these world-writable directories. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/tmp`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_tmp_nosuid
References: |
cis-csc | 11, 13, 14, 3, 8, 9
cobit5 | APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, 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.11.2.9, 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.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
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
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.1.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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 "/tmp" > /dev/null || findmnt --fstab "/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /tmp 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:]]" "/tmp")"

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

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

        # 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 " /tmp  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 "/tmp"; then
            if mountpoint -q "/tmp"; then
                mount -o remount --target "/tmp"
            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 /tmp --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /tmp: 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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /tmp: If /tmp not mounted, craft mount_info manually'
      set_fact:
        mount_info: '{{ mount_info|default({})|combine({item.0: item.1}) }}'
      with_together:
      - - target
        - source
        - fstype
        - options
      - - /tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /tmp: Make sure nosuid option is part of the to /tmp
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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"] ) )
      - '"/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - 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_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /tmp: Ensure /tmp is mounted with nosuid option'
      ansible.posix.mount:
        path: /tmp
        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-core" 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"] ) )
      - '"/tmp" 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:
      - 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_tmp_nosuid
      - no_reboot_needed
#### 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
cis | 1.1.2.7.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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
cis | 1.1.2.7.4
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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
cis | 1.1.2.7.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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-core" 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:
      - 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
#### Rule Add nodev Option to /var/log [ref]
The `nodev` mount option can be used to prevent device files from being created in `/var/log`. 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`.
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_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
os-srg | SRG-OS-000368-GPOS-00154
cis | 1.1.2.6.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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" > /dev/null || findmnt --fstab "/var/log" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log 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")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log' 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 in /etc/fstab" >&2; return 1; }

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

        # 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  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"; then
            if mountpoint -q "/var/log"; then
                mount -o remount --target "/var/log"
            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 --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log: 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-core" 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" 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:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log: If /var/log 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
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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" 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:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log: Make sure nodev option is part of the to /var/log
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/log: Ensure /var/log is mounted with nodev option'
      ansible.posix.mount:
        path: /var/log
        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-core" 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" 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:
      - 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_nodev
      - no_reboot_needed
#### Rule Add noexec Option to /var/log [ref]
The `noexec` mount option can be used to prevent binaries from being executed out of `/var/log`. Add the `noexec` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/log`.
Rationale: | Allowing users to execute binaries from directories containing log files such as `/var/log` 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_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
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.6.4
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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" > /dev/null || findmnt --fstab "/var/log" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log 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")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log' 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 in /etc/fstab" >&2; return 1; }

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

        # 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  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"; then
            if mountpoint -q "/var/log"; then
                mount -o remount --target "/var/log"
            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 --mountoptions="noexec"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log: 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-core" 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" 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:
      - 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_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log: If /var/log 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
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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" 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:
      - 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_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log: Make sure noexec option is part of the to
        /var/log options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''noexec''
          }) }}'
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "noexec" not in (mount_info.options | default(''))
      tags:
      - 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_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/log: Ensure /var/log is mounted with noexec option'
      ansible.posix.mount:
        path: /var/log
        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-core" 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" 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:
      - 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_noexec
      - no_reboot_needed
#### Rule Add nosuid Option to /var/log [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/var/log`. The SUID and SGID permissions should not be required in directories containing log files. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/log`.
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 log files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_log_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
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.6.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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" > /dev/null || findmnt --fstab "/var/log" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/log 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")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/log' 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 in /etc/fstab" >&2; return 1; }

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

        # 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  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"; then
            if mountpoint -q "/var/log"; then
                mount -o remount --target "/var/log"
            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 --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/log'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log: 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-core" 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" 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:
      - 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_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log: If /var/log 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
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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" 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:
      - 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_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log: Make sure nosuid option is part of the to
        /var/log options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - 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_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/log: Ensure /var/log is mounted with nosuid option'
      ansible.posix.mount:
        path: /var/log
        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-core" 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" 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:
      - 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_nosuid
      - no_reboot_needed
#### Rule Add nodev Option to /var [ref]
The `nodev` mount option can be used to prevent device files from being created in `/var`. 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`.
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_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
os-srg | SRG-OS-000368-GPOS-00154
cis | 1.1.2.4.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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" > /dev/null || findmnt --fstab "/var" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var 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")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var' 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 in /etc/fstab" >&2; return 1; }

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

        # 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  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"; then
            if mountpoint -q "/var"; then
                mount -o remount --target "/var"
            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 --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var: 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-core" 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" 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:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var: If /var 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
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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" 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:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var: Make sure nodev option is part of the to /var options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - 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_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var: Ensure /var is mounted with nodev option'
      ansible.posix.mount:
        path: /var
        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-core" 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" 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:
      - 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_nodev
      - no_reboot_needed
#### Rule Add nosuid Option to /var [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/var`. The SUID and SGID permissions should not be required for this directory. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_nosuid
References: |
anssi | R28
cis | 1.1.2.4.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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" > /dev/null || findmnt --fstab "/var" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var 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")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var' 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 in /etc/fstab" >&2; return 1; }

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

        # 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  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"; then
            if mountpoint -q "/var"; then
                mount -o remount --target "/var"
            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 --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var: 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-core" 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" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var: If /var 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
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var: Make sure nosuid option is part of the to /var
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var: Ensure /var is mounted with nosuid option'
      ansible.posix.mount:
        path: /var
        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-core" 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" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_nosuid
      - no_reboot_needed
#### Rule Add nodev Option to /var/tmp [ref]
The `nodev` mount option can be used to prevent device files from being created in `/var/tmp`. Legitimate character and block devices should not exist within temporary directories like `/var/tmp`. Add the `nodev` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/tmp`.
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_tmp_nodev
References: |
os-srg | SRG-OS-000368-GPOS-00154
cis | 1.1.2.5.2
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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/tmp" > /dev/null || findmnt --fstab "/var/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/tmp 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/tmp")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/tmp' 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/tmp in /etc/fstab" >&2; return 1; }

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

        # 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/tmp  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/tmp"; then
            if mountpoint -q "/var/tmp"; then
                mount -o remount --target "/var/tmp"
            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/tmp --mountoptions="nodev"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/tmp: 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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/tmp: If /var/tmp 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/tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/tmp: Make sure nodev option is part of the to /var/tmp
        options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nodev''
          }) }}'
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nodev" not in (mount_info.options | default(''))
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed

    - name: 'Add nodev Option to /var/tmp: Ensure /var/tmp is mounted with nodev option'
      ansible.posix.mount:
        path: /var/tmp
        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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nodev
      - no_reboot_needed
#### Rule Add noexec Option to /var/tmp [ref]
The `noexec` mount option can be used to prevent binaries from being executed out of `/var/tmp`. Add the `noexec` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/tmp`.
Rationale: | Allowing users to execute binaries from world-writable directories such as `/var/tmp` 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_tmp_noexec
References: |
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.5.4
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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/tmp" > /dev/null || findmnt --fstab "/var/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/tmp 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/tmp")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/tmp' 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/tmp in /etc/fstab" >&2; return 1; }

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

        # 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/tmp  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/tmp"; then
            if mountpoint -q "/var/tmp"; then
                mount -o remount --target "/var/tmp"
            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/tmp --mountoptions="noexec"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/tmp: 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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/tmp: If /var/tmp 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/tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/tmp: Make sure noexec option is part of the to
        /var/tmp options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''noexec''
          }) }}'
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "noexec" not in (mount_info.options | default(''))
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed

    - name: 'Add noexec Option to /var/tmp: Ensure /var/tmp is mounted with noexec option'
      ansible.posix.mount:
        path: /var/tmp
        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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_noexec
      - no_reboot_needed
#### Rule Add nosuid Option to /var/tmp [ref]
The `nosuid` mount option can be used to prevent execution of setuid programs in `/var/tmp`. The SUID and SGID permissions should not be required in these world-writable directories. Add the `nosuid` option to the fourth column of `/etc/fstab` for the line which controls mounting of `/var/tmp`.
Rationale: | The presence of SUID and SGID executables should be tightly controlled. Users should not be able to execute SUID or SGID binaries from temporary storage partitions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_mount_option_var_tmp_nosuid
References: |
os-srg | SRG-OS-000368-GPOS-00154
anssi | R28
cis | 1.1.2.5.3
Remediation Shell script ⇲
Reboot: | false
    # Remediation is applicable only in certain platforms
    if ( ! ( { rpm --quiet -q kernel-core ;} && { 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/tmp" > /dev/null || findmnt --fstab "/var/tmp" > /dev/null ); }; then

    function perform_remediation {

            # the mount point /var/tmp 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/tmp")"

        grep "$mount_point_match_regexp" -q /etc/fstab \
            || { echo "The mount point '/var/tmp' 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/tmp in /etc/fstab" >&2; return 1; }

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

        # 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/tmp  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/tmp"; then
            if mountpoint -q "/var/tmp"; then
                mount -o remount --target "/var/tmp"
            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/tmp --mountoptions="nosuid"
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | false
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/tmp: Check information associated to mountpoint'
      ansible.builtin.command: findmnt --fstab '/var/tmp'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      check_mode: false
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/tmp: 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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/tmp: If /var/tmp 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/tmp
        - ''
        - ''
        - defaults
      when:
      - ( not ( "kernel-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/tmp: Make sure nosuid option is part of the to
        /var/tmp options'
      set_fact:
        mount_info: '{{ mount_info | combine( {''options'':''''~(mount_info.options |
          default(''''))~('','' if (mount_info.options | default('''')) else '''')~''nosuid''
          }) }}'
      when:
      - ( not ( "kernel-core" 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/tmp" in ansible_mounts | map(attribute="mount") | list'
      - mount_info is defined and "nosuid" not in (mount_info.options | default(''))
      tags:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed

    - name: 'Add nosuid Option to /var/tmp: Ensure /var/tmp is mounted with nosuid option'
      ansible.posix.mount:
        path: /var/tmp
        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-core" 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/tmp" 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:
      - configure_strategy
      - high_disruption
      - low_complexity
      - medium_severity
      - mount_option_var_tmp_nosuid
      - no_reboot_needed
Group Restrict Programs from Dangerous Execution Patterns Group contains 2 groups and 8 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 4 rules
[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 core dump backtraces [ref]
The `ProcessSizeMax` option in `[Coredump]` section of `/etc/systemd/coredump.conf` or in a drop-in file under `/etc/systemd/coredump.conf.d/` specifies the maximum size in bytes of a core which will be processed. Core dumps exceeding this size may be stored, but the backtrace will not be generated. Warning: If the `/etc/systemd/coredump.conf` file or a drop-in file under `/etc/systemd/coredump.conf.d/` does not already contain the `[Coredump]` section, the value will not be configured correctly.
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 or system operators trying to debug problems. Enabling core dumps on production systems is not recommended, however there may be overriding operational requirements to enable advanced debugging. Permitting temporary enablement of core dumps during such situations should be reviewed through local needs and policy.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_coredump_disable_backtraces
References: |
nist | CM-6
pcidss | Req-3.2
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 3.3.1.1, 3.3.1, 3.3
cis | 1.5.9
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,%23%20%20This%20file%20is%20part%20of%20systemd.%0A%23%0A%23%20%20systemd%20is%20free%20software%3B%20you%20can%20redistribute%20it%20and/or%20modify%20it%0A%23%20%20under%20the%20terms%20of%20the%20GNU%20Lesser%20General%20Public%20License%20as%20published%20by%0A%23%20%20the%20Free%20Software%20Foundation%3B%20either%20version%202.1%20of%20the%20License%2C%20or%0A%23%20%20%28at%20your%20option%29%20any%20later%20version.%0A%23%0A%23%20Entries%20in%20this%20file%20show%20the%20compile%20time%20defaults.%0A%23%20You%20can%20change%20settings%20by%20editing%20this%20file.%0A%23%20Defaults%20can%20be%20restored%20by%20simply%20deleting%20this%20file.%0A%23%0A%23%20See%20coredump.conf%285%29%20for%20details.%0A%0A%5BCoredump%5D%0A%23Storage%3Dexternal%0A%23Compress%3Dyes%0A%23ProcessSizeMax%3D2G%0A%23ExternalSizeMax%3D2G%0A%23JournalSizeMax%3D767M%0A%23MaxUse%3D%0A%23KeepFree%3D%0AStorage%3Dnone%0AProcessSizeMax%3D0%0A
            mode: 0644
            path: /etc/systemd/coredump.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q systemd; }; then

    found=false

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

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

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

            fi

            found=true

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

                sed -i "/^[[:space:]]*\[Coredump\]/a ProcessSizeMax=0" "$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/coredump.conf.d/complianceascode_hardening.conf /etc/systemd/coredump.conf.d/*.conf /etc/systemd/coredump.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Coredump]\nProcessSizeMax=0" >> "$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:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_backtraces
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable core dump backtraces - Search for a section in files
      ansible.builtin.find:
        paths: '{{item.path}}'
        patterns: '{{item.pattern}}'
        contains: ^\s*\[Coredump\]
        read_whole_file: true
        use_regex: true
      register: systemd_dropin_files_with_section
      loop:
      - path: '{{ ''/etc/systemd/coredump.conf'' | dirname }}'
        pattern: '{{ ''/etc/systemd/coredump.conf'' | basename | regex_escape }}'
      - path: /etc/systemd/coredump.conf.d
        pattern: .*\.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_backtraces
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable core dump backtraces - Count number of files which contain the correct
        section
      ansible.builtin.set_fact:
        count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
          | map(attribute=''matched'') | list | map(''int'') | sum}}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_backtraces
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable core dump backtraces - Add missing configuration to correct section
      community.general.ini_file:
        path: '{{item}}'
        section: Coredump
        option: ProcessSizeMax
        value: '0'
        state: present
        no_extra_spaces: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int > 0
      loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
        | map(attribute=''path'') | list }}'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_backtraces
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable core dump backtraces - Add configuration to new remediation file
      community.general.ini_file:
        path: /etc/systemd/coredump.conf.d/complianceascode_hardening.conf
        section: Coredump
        option: ProcessSizeMax
        value: '0'
        state: present
        no_extra_spaces: true
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int == 0
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_backtraces
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Disable storing core dump [ref]
The `Storage` option in `[Coredump]` section of `/etc/systemd/coredump.conf` or a drop-in file in `/etc/systemd/coredump.conf.d/*.conf` can be set to `none` to disable storing core dumps permanently. Warning: If the `/etc/systemd/coredump.conf` file or a drop-in file under `/etc/systemd/coredump.conf.d/` does not already contain the `[Coredump]` section, the value will not be configured correctly.
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 or system operators trying to debug problems. Enabling core dumps on production systems is not recommended, however there may be overriding operational requirements to enable advanced debugging. Permitting temporary enablement of core dumps during such situations should be reviewed through local needs and policy.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_coredump_disable_storage
References: |
nist | CM-6
pcidss | Req-3.2
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 3.3.1.1, 3.3.1, 3.3
cis | 1.5.10
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,%23%20%20This%20file%20is%20part%20of%20systemd.%0A%23%0A%23%20%20systemd%20is%20free%20software%3B%20you%20can%20redistribute%20it%20and/or%20modify%20it%0A%23%20%20under%20the%20terms%20of%20the%20GNU%20Lesser%20General%20Public%20License%20as%20published%20by%0A%23%20%20the%20Free%20Software%20Foundation%3B%20either%20version%202.1%20of%20the%20License%2C%20or%0A%23%20%20%28at%20your%20option%29%20any%20later%20version.%0A%23%0A%23%20Entries%20in%20this%20file%20show%20the%20compile%20time%20defaults.%0A%23%20You%20can%20change%20settings%20by%20editing%20this%20file.%0A%23%20Defaults%20can%20be%20restored%20by%20simply%20deleting%20this%20file.%0A%23%0A%23%20See%20coredump.conf%285%29%20for%20details.%0A%0A%5BCoredump%5D%0A%23Storage%3Dexternal%0A%23Compress%3Dyes%0A%23ProcessSizeMax%3D2G%0A%23ExternalSizeMax%3D2G%0A%23JournalSizeMax%3D767M%0A%23MaxUse%3D%0A%23KeepFree%3D%0AStorage%3Dnone%0AProcessSizeMax%3D0%0A
            mode: 0644
            path: /etc/systemd/coredump.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q systemd; }; then

    found=false

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

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

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

            fi

            found=true

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

                sed -i "/^[[:space:]]*\[Coredump\]/a Storage=none" "$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/coredump.conf.d/complianceascode_hardening.conf /etc/systemd/coredump.conf.d/*.conf /etc/systemd/coredump.conf" | cut -f1 -d ' ')
        mkdir -p "$(dirname "$file")"

        echo -e "[Coredump]\nStorage=none" >> "$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:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable storing core dump - Search for a section in files
      ansible.builtin.find:
        paths: '{{item.path}}'
        patterns: '{{item.pattern}}'
        contains: ^\s*\[Coredump\]
        read_whole_file: true
        use_regex: true
      register: systemd_dropin_files_with_section
      loop:
      - path: '{{ ''/etc/systemd/coredump.conf'' | dirname }}'
        pattern: '{{ ''/etc/systemd/coredump.conf'' | basename | regex_escape }}'
      - path: /etc/systemd/coredump.conf.d
        pattern: .*\.conf
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable storing core dump - Count number of files which contain the correct
        section
      ansible.builtin.set_fact:
        count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
          | map(attribute=''matched'') | list | map(''int'') | sum}}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable storing core dump - Add missing configuration to correct section
      community.general.ini_file:
        path: '{{item}}'
        section: Coredump
        option: Storage
        value: none
        state: present
        no_extra_spaces: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int > 0
      loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
        | map(attribute=''path'') | list }}'
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable storing core dump - Add configuration to new remediation file
      community.general.ini_file:
        path: /etc/systemd/coredump.conf.d/complianceascode_hardening.conf
        section: Coredump
        option: Storage
        value: none
        state: present
        no_extra_spaces: true
        create: true
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"systemd" in ansible_facts.packages'
      - count_of_systemd_dropin_files_with_section | int == 0
      tags:
      - NIST-800-53-CM-6
      - PCI-DSS-Req-3.2
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - coredump_disable_storage
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Disable Core Dumps for All Users [ref]
To disable core dumps for all users, add the following line to `/etc/security/limits.conf`, or to a file within the `/etc/security/limits.d/` directory:
    *     hard   core    0
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_disable_users_coredumps
References: |
cis-csc | 1, 12, 13, 15, 16, 2, 7, 8
cobit5 | APO13.01, BAI04.04, DSS01.03, DSS03.05, DSS05.07
isa-62443-2013 | SR 6.2, SR 7.1, SR 7.2
iso27001-2013 | A.12.1.3, A.17.2.1
nist | CM-6, SC-7(10)
nist-csf | DE.CM-1, PR.DS-4
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 3.3.1.1, 3.3.1, 3.3
cis | 1.5.1
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,%2A%20%20%20%20%20hard%20%20%20core%20%20%20%200
            mode: 0644
            path: /etc/security/limits.d/75-disable_users_coredumps.conf
            overwrite: true
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q pam; }; then

    SECURITY_LIMITS_FILE="/etc/security/limits.conf"
    DROPIN_DIR="/etc/security/limits.d"
    DROPIN_FILE="$DROPIN_DIR/10-ssg-hardening.conf"
    REGEX_CORRECT_VALUE="^\s*\*\s+hard\s+core\s+0\s*$"

    # Remove bad configuration in drop-ins
    if [ -d "$DROPIN_DIR" ]; then
        for override in "$DROPIN_DIR"/*.conf; do
            if [ -f "$override" ] && ! grep -qE "$REGEX_CORRECT_VALUE" "$override"; then
                sed -ir -E '/^[[:space:]]*\*[[:space:]]+hard[[:space:]]+core[[:space:]]+/ s/^/#/' "$override"
            fi
        done
    fi

    if [ -d "$DROPIN_DIR" ] && grep -qEr "$REGEX_CORRECT_VALUE" "$DROPIN_DIR"; then
        exit 0
    elif [ ! -d "$DROPIN_DIR" ] && grep -qE "$REGEX_CORRECT_VALUE" "$SECURITY_LIMITS_FILE"; then
        exit 0
    else
        mkdir -p "$DROPIN_DIR"
        echo "*     hard   core    0" >> $DROPIN_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:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Set dirs, files and regex variables
      ansible.builtin.set_fact:
        limits_dropin_dir: /etc/security/limits.d
        limits_dropin_file: /etc/security/limits.d/10-ssg-hardening.conf
        limits_main_file: /etc/security/limits.conf
        limits_correct_regex: ^\s*\*\s+hard\s+core\s+0\s*$
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Find valid drop-ins for core limit
      ansible.builtin.find:
        paths: '{{ limits_dropin_dir }}'
        patterns: '*.conf'
        contains: '{{ limits_correct_regex }}'
        file_type: file
      register: valid_dropins
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Find all drop-ins with any core limit
      ansible.builtin.find:
        paths: '{{ limits_dropin_dir }}'
        patterns: '*.conf'
        contains: ^\s*\*\s+hard\s+core\s+
        file_type: file
      register: all_dropins
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Get invalid drop-ins
      ansible.builtin.set_fact:
        invalid_dropins: '{{ all_dropins.files | rejectattr(''path'', ''in'', valid_dropins.files
          | map(attribute=''path'') | list) | map(attribute=''path'') | list }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Comment invalid * hard core lines in drop-ins
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: (^\s*\*\s+hard\s+core\s+.*$)
        replace: '#\1'
      loop: '{{ invalid_dropins }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - invalid_dropins | length > 0
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Check if main limits.conf contains correct
        core limit
      ansible.builtin.find:
        paths: /etc/security
        patterns: limits.conf
        contains: '{{ limits_correct_regex }}'
        file_type: file
      register: main_valid
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not (valid_dropins.matched | default(0) > 0)
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Set fact if configuration is valid
      ansible.builtin.set_fact:
        core_limit_valid: '{{ (valid_dropins.matched | default(0)) > 0 or (main_valid.matched
          | default(0)) > 0 }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Ensure drop-in directory exists
      ansible.builtin.file:
        path: '{{ limits_dropin_dir }}'
        state: directory
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not core_limit_valid
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Disable Core Dumps for All Users - Deploy 10-ssg-hardening.conf drop-in with
        correct core limit
      ansible.builtin.copy:
        dest: '{{ limits_dropin_file }}'
        content: |
          *     hard   core    0
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"pam" in ansible_facts.packages'
      - not core_limit_valid
      tags:
      - NIST-800-53-CM-6
      - NIST-800-53-SC-7(10)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_users_coredumps
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Disable Core Dumps for SUID programs [ref]
To set the runtime status of the `fs.suid_dumpable` kernel parameter, run the following command:
    $ sudo sysctl -w fs.suid_dumpable=0
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    fs.suid_dumpable = 0
Rationale: | The core dump of a setuid program is more likely to contain sensitive data, as the program itself runs with greater privileges than the user who initiated execution of the program. Disabling the ability for any setuid program to write a core file decreases the risk of unauthorized access of such data.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_fs_suid_dumpable
References: |
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)
anssi | R14
pcidss4 | 3.3.1.1, 3.3.1, 3.3
cis | 1.5.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of fs.suid_dumpable 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]*fs.suid_dumpable.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "fs.suid_dumpable" 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/fs_suid_dumpable.conf'

    #
    # Set runtime for fs.suid_dumpable
    #
    if ! { rpm --quiet -q kernel rpm-ostree bootc && ! rpm --quiet -q openshift-kubelet && { [ -f "/run/.containerenv" ] || [ -f "/.containerenv" ]; }; } ; then
        /sbin/sysctl -q -n -w fs.suid_dumpable="0"
    fi

    #
    # If fs.suid_dumpable present in /etc/sysctl.conf, change value to "0"
    #	else, add "fs.suid_dumpable = 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' <<< "^fs.suid_dumpable")

    # 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 "^fs.suid_dumpable\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^fs.suid_dumpable\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Find all files that contain fs.suid_dumpable
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*fs.suid_dumpable\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Find all files that set fs.suid_dumpable
        to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*fs.suid_dumpable\s*=\s*0$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Comment out any occurrences of fs.suid_dumpable
        from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*fs.suid_dumpable
        replace: '#fs.suid_dumpable'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Comment out any occurrences of fs.suid_dumpable
        from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*fs.suid_dumpable
        replace: '#fs.suid_dumpable'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable

    - name: Disable Core Dumps for SUID programs - Ensure sysctl fs.suid_dumpable is set
        to 0
      ansible.posix.sysctl:
        name: fs.suid_dumpable
        value: '0'
        sysctl_file: /etc/sysctl.d/fs_suid_dumpable.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SI-11(a)
      - NIST-800-53-SI-11(b)
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_fs_suid_dumpable
Group Enable ExecShield Group contains 2 rules
[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
cis | 1.5.6
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,kernel.kptr_restrict%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_kernel_kptr_restrict.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" 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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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 Enable Randomized Layout of Virtual Address Space [ref]
To set the runtime status of the `kernel.randomize_va_space` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.randomize_va_space=2
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.randomize_va_space = 2
Rationale: | Address space layout randomization (ASLR) makes it more difficult for an attacker to predict the location of attack code they have introduced into a process's address space during an attempt at exploitation. Additionally, ASLR makes it more difficult for an attacker to know the location of existing code in order to re-purpose it using return oriented programming (ROP) techniques.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_randomize_va_space
References: |
cui | 3.1.7
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)
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), CM-6(a)
pcidss | Req-2.2.1
os-srg | SRG-OS-000433-GPOS-00193, SRG-OS-000480-GPOS-00227
app-srg-ctr | SRG-APP-000450-CTR-001105
anssi | R9
ism | 1409
pcidss4 | 3.3.1.1, 3.3.1, 3.3
cis | 1.5.8
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,kernel.randomize_va_space%3D2%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_kernel_randomize_va_space.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    # Comment out any occurrences of kernel.randomize_va_space 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.randomize_va_space.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          escaped_entry=$(sed -e 's|/|\\/|g' <<< "$entry")
          # comment out "kernel.randomize_va_space" 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_randomize_va_space.conf'

    #
    # Set runtime for kernel.randomize_va_space
    #
    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.randomize_va_space="2"
    fi

    #
    # If kernel.randomize_va_space present in /etc/sysctl.conf, change value to "2"
    #	else, add "kernel.randomize_va_space = 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.randomize_va_space")

    # 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.randomize_va_space\\>" "${SYSCONFIG_FILE}"; then
        escaped_formatted_output=$(sed -e 's|/|\\/|g' <<< "$formatted_output")
        LC_ALL=C sed -i --follow-symlinks "s/^kernel.randomize_va_space\\>.*/$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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Set fact for sysctl paths
      ansible.builtin.set_fact:
        sysctl_paths:
        - /etc/sysctl.d/
        - /run/sysctl.d/
        - /usr/local/lib/sysctl.d/
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Find all files that contain
        kernel.randomize_va_space
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.randomize_va_space\s*=\s*.*$'
      register: find_all_values
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Find all files that set
        kernel.randomize_va_space to correct value
      ansible.builtin.shell:
        cmd: find -L {{ sysctl_paths | join(" ") }} -type f -name '*.conf' | xargs grep
          -HP '^\s*kernel.randomize_va_space\s*=\s*2$'
      register: find_correct_value
      check_mode: false
      changed_when: false
      failed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Comment out any occurrences
        of kernel.randomize_va_space from config files
      ansible.builtin.replace:
        path: '{{ item | split(":") | first }}'
        regexp: ^[\s]*kernel.randomize_va_space
        replace: '#kernel.randomize_va_space'
      loop: '{{ find_all_values.stdout_lines }}'
      when:
      - '"kernel-core" 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-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Comment out any occurrences
        of kernel.randomize_va_space from /etc/sysctl.conf
      ansible.builtin.replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.randomize_va_space
        replace: '#kernel.randomize_va_space'
      with_fileglob:
      - /etc/sysctl.conf
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space

    - name: Enable Randomized Layout of Virtual Address Space - Ensure sysctl kernel.randomize_va_space
        is set to 2
      ansible.posix.sysctl:
        name: kernel.randomize_va_space
        value: '2'
        sysctl_file: /etc/sysctl.d/kernel_randomize_va_space.conf
        state: present
        reload: true
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.7
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-30
      - NIST-800-53-SC-30(2)
      - PCI-DSS-Req-2.2.1
      - PCI-DSSv4-3.3
      - PCI-DSSv4-3.3.1
      - PCI-DSSv4-3.3.1.1
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_randomize_va_space
#### 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
cis | 1.5.5
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,kernel.dmesg_restrict%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_kernel_dmesg_restrict.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" 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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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 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
cis | 1.5.7
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,kernel.yama.ptrace_scope%3D1%0A
            mode: 0644
            path: /etc/sysctl.d/75-sysctl_kernel_yama_ptrace_scope.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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
        cce=""
        printf '# Per %s: Set %s in %s\n' "${cce}" "${formatted_output}" "${SYSCONFIG_FILE}" >> "${SYSCONFIG_FILE}"
        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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" 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-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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-SC-7(10)
      - disable_strategy
      - low_complexity
      - medium_disruption
      - medium_severity
      - reboot_required
      - sysctl_kernel_yama_ptrace_scope
Group SELinux Group contains 5 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 Red Hat Enterprise Linux 10, 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 Red Hat Enterprise Linux 10 system, unless that system has unusual requirements which make a stronger policy appropriate.
#### Rule Install libselinux Package [ref]
The `libselinux` package can be installed with the following command:
    $ sudo dnf install libselinux
Rationale: | Security-enhanced Linux is a feature of the Linux kernel and a number of utilities with enhanced security functionality designed to add mandatory access controls to Linux. The `libselinux` package contains the core library of the Security-enhanced Linux system.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_libselinux_installed
References: |
pcidss4 | 1.2.6, 1.2
bsi | SYS.1.1.A37, SYS.1.1.A31, SYS.1.3.A4, SYS.1.3.A10
cis | 1.3.1.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install libselinux
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "libselinux"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install libselinux
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=libselinux
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - enable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_libselinux_installed

    - name: Ensure libselinux is installed
      ansible.builtin.package:
        name: libselinux
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - enable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_libselinux_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_libselinux

    class install_libselinux {
      package { 'libselinux':
        ensure => 'installed',
      }
    }
#### Rule Uninstall mcstrans Package [ref]
The `mcstransd` daemon provides category label information to client processes requesting information. The label translations are defined in `/etc/selinux/targeted/setrans.conf`. The `mcstrans` package can be removed with the following command:
    $ sudo dnf remove mcstrans
Rationale: | Since this service is not used very often, disable it to reduce the amount of potentially vulnerable code running on the system.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_mcstrans_removed
References: |
cis | 1.3.1.7
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove mcstrans
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    if rpm -q --quiet "mcstrans" ; then
    dnf remove -y --noautoremove "mcstrans"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove mcstrans
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=mcstrans
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_mcstrans_removed

    - name: 'Uninstall mcstrans Package: Ensure mcstrans is removed'
      ansible.builtin.package:
        name: mcstrans
        state: absent
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_mcstrans_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_mcstrans

    class remove_mcstrans {
      package { 'mcstrans':
        ensure => 'purged',
      }
    }
#### Rule Ensure SELinux Not Disabled in /etc/default/grub [ref]
SELinux can be disabled at boot time by an argument in `/etc/default/grub`. Remove any instances of `selinux=0` from the kernel arguments in that file to prevent SELinux from being disabled at boot.
Rationale: | Disabling a major host protection feature, such as SELinux, at boot time prevents it from confining system services at boot time. Further, it increases the chances that it will remain off during system operation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_grub2_enable_selinux
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.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-3(3)(a)
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
pcidss4 | 1.2.6, 1.2
bsi | SYS.1.1.A37, SYS.1.1.A31, SYS.1.3.A4, SYS.1.3.A10
cis | 1.3.1.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q grub2-common; }; then

    sed -i --follow-symlinks "s/selinux=0//gI" /etc/default/grub /etc/grub2.cfg /etc/grub.d/*
    sed -i --follow-symlinks "s/enforcing=0//gI" /etc/default/grub /etc/grub2.cfg /etc/grub.d/*

    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.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Find /etc/grub.d/ files
      ansible.builtin.find:
        paths:
        - /etc/grub.d/
        follow: true
      register: result_grub_d
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Ensure SELinux Not Disabled
        in /etc/grub.d/ files
      ansible.builtin.replace:
        dest: '{{ item.path }}'
        regexp: (selinux|enforcing)=0
      with_items:
      - '{{ result_grub_d.files }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Check if /etc/grub2.cfg
        exists
      ansible.builtin.stat:
        path: /etc/grub2.cfg
      register: result_grub2_cfg_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Check if /etc/default/grub
        exists
      ansible.builtin.stat:
        path: /etc/default/grub
      register: result_default_grub_present
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Ensure SELinux Not Disabled
        in /etc/grub2.cfg
      ansible.builtin.replace:
        dest: /etc/grub2.cfg
        regexp: (selinux|enforcing)=0
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      - result_grub2_cfg_present.stat.exists
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy

    - name: Ensure SELinux Not Disabled in /etc/default/grub - Ensure SELinux Not Disabled
        in /etc/default/grub
      ansible.builtin.replace:
        dest: /etc/default/grub
        regexp: (selinux|enforcing)=0
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"grub2-common" in ansible_facts.packages'
      - result_default_grub_present.stat.exists
      tags:
      - NIST-800-171-3.1.2
      - NIST-800-171-3.7.2
      - NIST-800-53-AC-3
      - NIST-800-53-AC-3(3)(a)
      - PCI-DSSv4-1.2
      - PCI-DSSv4-1.2.6
      - grub2_enable_selinux
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
#### Rule Ensure SELinux is Not Disabled [ref]
The SELinux state should be set to `enforcing` or `permissive` at system boot time. In the file `/etc/selinux/config`, add or correct the following line to configure the system to boot into enforcing or permissive mode:
    SELINUX=enforcing
OR
    SELINUX=permissive
Ensure that all files have correct SELinux labels by running:
    fixfiles onboot
Then reboot the system. Warning: In case the SELinux is "disabled", the automated remediation will adopt a more conservative approach and set it to "permissive" in order to avoid any system disruption and give the administrator the opportunity to assess the impact and necessary efforts before setting it to "enforcing", which is strongly recommended.
Rationale: | Running SELinux in disabled mode is strongly discouraged. It prevents enforcing the SELinux controls without a system reboot. It also avoids labeling any persistent objects such as files, making it difficult to enable SELinux in the future.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_selinux_not_disabled
References: |
bsi | SYS.1.1.A37, SYS.1.1.A31, SYS.1.3.A4, SYS.1.3.A10
cis | 1.3.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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=permissive" >> "/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: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - high_severity
      - low_complexity
      - low_disruption
      - reboot_required
      - restrict_strategy
      - selinux_not_disabled

    - name: Ensure SELinux is Not Disabled - Check current SELinux state
      ansible.builtin.command:
        cmd: getenforce
      register: current_selinux_state
      check_mode: false
      changed_when: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - high_severity
      - low_complexity
      - low_disruption
      - reboot_required
      - restrict_strategy
      - selinux_not_disabled

    - name: Ensure SELinux is Not Disabled
      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=permissive
          state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - high_severity
      - low_complexity
      - low_disruption
      - reboot_required
      - restrict_strategy
      - selinux_not_disabled

    - name: Ensure SELinux is Not Disabled - Mark system to relabel SELinux on next boot
      ansible.builtin.file:
        path: /.autorelabel
        state: touch
        access_time: preserve
        modification_time: preserve
      when:
      - '"kernel-core" in ansible_facts.packages'
      - current_selinux_state.stdout | lower != "permissive"
      tags:
      - high_severity
      - low_complexity
      - low_disruption
      - reboot_required
      - restrict_strategy
      - selinux_not_disabled
#### 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, SYS.1.1.A37, SYS.1.1.A31, SYS.1.3.A10
ism | 1409
pcidss4 | 1.2.6, 1.2
cis | 1.3.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; 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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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
Group Services Group contains 33 groups and 88 rules
[ref] The best protection against vulnerable software is running less software. This section describes how to review the software which Red Hat Enterprise Linux 10 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Red Hat Enterprise Linux 10 system and provides guidance about which ones can be safely disabled. Red Hat Enterprise Linux 10 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Red Hat Enterprise Linux 10 systems, it is highly recommended to select the minimal packages and then build up the system from there.
Group Cron and At Daemons Group contains 1 group and 33 rules
[ref] The cron and at services are used to allow commands to be executed at a later time. The cron service is required by almost all systems to perform necessary maintenance tasks, while at may or may not be required on a given system. Both daemons should be configured defensively.
Group Restrict at and cron to Authorized Users if Necessary Group contains 10 rules
- [ref] The `/etc/cron.allow` and `/etc/at.allow` files contain lists of users who are allowed to use `cron` and at to delay execution of processes. If these files exist and if the corresponding files `/etc/cron.deny` and `/etc/at.deny` do not exist, then only users listed in the relevant allow files can run the crontab and `at` commands to submit jobs to be run at scheduled intervals. On many systems, only the system administrator needs the ability to schedule jobs. Note that even if a given user is not listed in `cron.allow`, cron jobs can still be run as that user. The `cron.allow` file controls only administrative access to the crontab command for scheduling and modifying cron jobs. To restrict `at` and `cron` to only authorized users: Remove the `cron.deny` file:
      $ sudo rm /etc/cron.deny
- Edit `/etc/cron.allow`, adding one line for each user allowed to use the crontab command to create cron jobs. Remove the `at.deny` file:
      $ sudo rm /etc/at.deny
Edit `/etc/at.allow`, adding one line for each user allowed to use the at command to create at jobs.
#### Rule Ensure that /etc/at.allow exists [ref]
The file `/etc/at.allow` should exist and should be used instead of `/etc/at.deny`.
Rationale: | Using the at.allow file to control who can run at jobs enforces this who can schedule jobs. It is easier to manage an allow list than a deny list.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_at_allow_exists
References: |
cis | 2.4.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    touch /etc/at.allow
        chown 0 /etc/at.allow
        chmod 0640 /etc/at.allow

    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:
      - disable_strategy
      - file_at_allow_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure that /etc/at.allow exists - Add empty /etc/at.allow
      ansible.builtin.file:
        path: /etc/at.allow
        state: touch
        owner: '0'
        mode: '0640'
        modification_time: preserve
        access_time: preserve
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - file_at_allow_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure that /etc/at.deny does not exist [ref]
The file `/etc/at.deny` should not exist. Use `/etc/at.allow` instead.
Rationale: | Access to `at` should be restricted. It is easier to manage an allow list than a deny list.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_at_deny_not_exist
References: |
pcidss4 | 2.2.6, 2.2
cis | 2.4.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    if [[ -f  /etc/at.deny ]]; then
            rm /etc/at.deny
        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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - disable_strategy
      - file_at_deny_not_exist
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure that /etc/at.deny does not exist - Remove /etc/at.deny
      ansible.builtin.file:
        path: /etc/at.deny
        state: absent
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - disable_strategy
      - file_at_deny_not_exist
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure that /etc/cron.allow exists [ref]
The file `/etc/cron.allow` should exist and should be used instead of `/etc/cron.deny`.
Rationale: | Access to `crontab` should be restricted. It is easier to manage an allow list than a deny list. Therefore, `/etc/cron.allow` needs to be created and used instead of `/etc/cron.deny`. Regardless of the existence of any of these files, the root administrative user is always allowed to setup a crontab.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_cron_allow_exists
References: |
cis | 2.4.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    touch /etc/cron.allow
        chown 0 /etc/cron.allow
        chmod 0640 /etc/cron.allow

    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:
      - disable_strategy
      - file_cron_allow_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure that /etc/cron.allow exists - Add empty /etc/cron.allow
      ansible.builtin.file:
        path: /etc/cron.allow
        state: touch
        owner: '0'
        mode: '0640'
        modification_time: preserve
        access_time: preserve
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - disable_strategy
      - file_cron_allow_exists
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure that /etc/cron.deny does not exist [ref]
The file `/etc/cron.deny` should not exist. Use `/etc/cron.allow` instead.
Rationale: | Access to `cron` should be restricted. It is easier to manage an allow list than a deny list.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_cron_deny_not_exist
References: |
pcidss4 | 2.2.6, 2.2
cis | 2.4.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    if [[ -f  /etc/cron.deny ]]; then
            rm /etc/cron.deny
        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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - disable_strategy
      - file_cron_deny_not_exist
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure that /etc/cron.deny does not exist - Remove /etc/cron.deny
      ansible.builtin.file:
        path: /etc/cron.deny
        state: absent
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - disable_strategy
      - file_cron_deny_not_exist
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns /etc/at.allow file [ref]
If `/etc/at.allow` exists, it must be group-owned by `root`. To properly set the group owner of `/etc/at.allow`, run the command:
    $ sudo chgrp root /etc/at.allow
Rationale: | If the owner of the at.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_at_allow
References: |
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/at.allow" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/at.allow
    fi

    fi

    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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_at_allow_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_at_allow_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/at.allow
      ansible.builtin.stat:
        path: /etc/at.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/at.allow
      ansible.builtin.file:
        path: /etc/at.allow
        follow: false
        group: '{{ file_groupowner_at_allow_newgroup }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns /etc/cron.allow file [ref]
If `/etc/cron.allow` exists, it must be group-owned by `root`. To properly set the group owner of `/etc/cron.allow`, run the command:
    $ sudo chgrp root /etc/cron.allow
Rationale: | If the owner of the cron.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_allow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/cron.allow" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/cron.allow
    fi

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_allow_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_allow_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/cron.allow
      ansible.builtin.stat:
        path: /etc/cron.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.allow
      ansible.builtin.file:
        path: /etc/cron.allow
        follow: false
        group: '{{ file_groupowner_cron_allow_newgroup }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns /etc/at.allow file [ref]
If `/etc/at.allow` exists, it must be owned by `root`. To properly set the owner of `/etc/at.allow`, run the command:
    $ sudo chown root /etc/at.allow
Rationale: | If the owner of the at.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_at_allow
References: |
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/at.allow" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/at.allow
    fi

    fi

    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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_at_allow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_at_allow_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/at.allow
      ansible.builtin.stat:
        path: /etc/at.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/at.allow
      ansible.builtin.file:
        path: /etc/at.allow
        follow: false
        owner: '{{ file_owner_at_allow_newown }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify User Who Owns /etc/cron.allow file [ref]
If `/etc/cron.allow` exists, it must be owned by `root`. To properly set the owner of `/etc/cron.allow`, run the command:
    $ sudo chown root /etc/cron.allow
Rationale: | If the owner of the cron.allow file is not set to root, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_allow
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/cron.allow" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/cron.allow
    fi

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_allow_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_allow_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/cron.allow
      ansible.builtin.stat:
        path: /etc/cron.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/cron.allow
      ansible.builtin.file:
        path: /etc/cron.allow
        follow: false
        owner: '{{ file_owner_cron_allow_newown }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on /etc/at.allow file [ref]
If `/etc/at.allow` exists, it must have permissions `0640` or more restrictive. To properly set the permissions of `/etc/at.allow`, run the command:
    $ sudo chmod 0640 /etc/at.allow
Rationale: | If the permissions of the at.allow file are not set to 0640 or more restrictive, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_at_allow
References: |
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.2.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    chmod u-xs,g-xws,o-xwrt /etc/at.allow

    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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/at.allow
      ansible.builtin.stat:
        path: /etc/at.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwrt on /etc/at.allow
      ansible.builtin.file:
        path: /etc/at.allow
        mode: u-xs,g-xws,o-xwrt
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_at_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on /etc/cron.allow file [ref]
If `/etc/cron.allow` exists, it must have permissions `0640` or more restrictive. To properly set the permissions of `/etc/cron.allow`, run the command:
    $ sudo chmod 0640 /etc/cron.allow
Rationale: | If the permissions of the cron.allow file are not set to 0640 or more restrictive, the possibility exists for an unauthorized user to view or edit sensitive information.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_allow
References: |
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    chmod u-xs,g-xws,o-xwrt /etc/cron.allow

    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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/cron.allow
      ansible.builtin.stat:
        path: /etc/cron.allow
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xws,o-xwrt on /etc/cron.allow
      ansible.builtin.file:
        path: /etc/cron.allow
        mode: u-xs,g-xws,o-xwrt
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_allow
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Install the cron service [ref]
The Cron service should be installed.
Rationale: | The cron service allow periodic job execution, needed for almost all administrative tasks and services (software update, log rotating, etc.). Access to cron service should be restricted to administrative accounts only.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_cron_installed
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
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.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-6(a)
nist-csf | PR.IP-1, PR.PT-3
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    dnf install cronie
Remediation OSBuild Blueprint snippet ⇲
    [[packages]]
    name = "cronie"
    version = "*"
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package install cronie
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    package --add=cronie
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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_cron_installed

    - name: Ensure cronie is installed
      ansible.builtin.package:
        name: cronie
        state: present
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - 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_cron_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include install_cronie

    class install_cronie {
      package { 'cronie':
        ensure => 'installed',
      }
    }
#### Rule Enable cron Service [ref]
The `crond` service is used to execute commands at preconfigured times. It is required by almost all systems to perform necessary maintenance tasks, such as notifying root of system activity. The `crond` service can be enabled with the following command:
    $ sudo systemctl enable crond.service
Rationale: | Due to its usage for maintenance and security-supporting tasks, enabling the cron daemon is essential.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_crond_enabled
References: |
cis-csc | 11, 14, 3, 9
cobit5 | BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06
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.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-6(a)
nist-csf | PR.IP-1, PR.PT-3
cis | 2.4.1.1
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    enabled = ["crond"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service enable crond
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)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_crond_enabled

    - name: Enable cron Service - Enable service crond
      block:

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

      - name: Enable cron Service - Enable Service crond
        ansible.builtin.systemd:
          name: crond
          enabled: true
          state: started
          masked: false
        when:
        - '"cronie" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - enable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_crond_enabled
      - special_service_block
      when: '"kernel-core" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include enable_crond

    class enable_crond {
      service {'crond':
        enable => true,
        ensure => 'running',
      }
    }
#### Rule Verify Group Who Owns cron.d [ref]
To properly set the group owner of `/etc/cron.d`, run the command:
    $ sudo chgrp root /etc/cron.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.d/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_d_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_d_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.d/
      ansible.builtin.file:
        path: /etc/cron.d/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_d_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns cron.daily [ref]
To properly set the group owner of `/etc/cron.daily`, run the command:
    $ sudo chgrp root /etc/cron.daily
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_daily
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.daily/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_daily_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_daily_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.daily/
      ansible.builtin.file:
        path: /etc/cron.daily/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_daily_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns cron.hourly [ref]
To properly set the group owner of `/etc/cron.hourly`, run the command:
    $ sudo chgrp root /etc/cron.hourly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_hourly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.hourly/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_hourly_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_hourly_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.hourly/
      ansible.builtin.file:
        path: /etc/cron.hourly/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_hourly_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns cron.monthly [ref]
To properly set the group owner of `/etc/cron.monthly`, run the command:
    $ sudo chgrp root /etc/cron.monthly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_monthly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.monthly/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_monthly_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_monthly_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.monthly/
      ansible.builtin.file:
        path: /etc/cron.monthly/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_monthly_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns cron.weekly [ref]
To properly set the group owner of `/etc/cron.weekly`, run the command:
    $ sudo chgrp root /etc/cron.weekly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_weekly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.weekly/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_weekly_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_weekly_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.weekly/
      ansible.builtin.file:
        path: /etc/cron.weekly/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_weekly_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns cron.yearly [ref]
To properly set the group owner of `/etc/cron.yearly`, run the command:
    $ sudo chgrp root /etc/cron.yearly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_cron_yearly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 2.4.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/cron.yearly/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_cron_yearly_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_cron_yearly_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/cron.yearly/
      ansible.builtin.file:
        path: /etc/cron.yearly/
        follow: false
        state: directory
        group: '{{ file_groupowner_cron_yearly_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns Crontab [ref]
To properly set the group owner of `/etc/crontab`, run the command:
    $ sudo chgrp root /etc/crontab
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_crontab
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/crontab" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/crontab
    fi

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_crontab_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_crontab_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/crontab
      ansible.builtin.stat:
        path: /etc/crontab
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/crontab
      ansible.builtin.file:
        path: /etc/crontab
        follow: false
        group: '{{ file_groupowner_crontab_newgroup }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_groupowner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.d [ref]
To properly set the owner of `/etc/cron.d`, run the command:
    $ sudo chown root /etc/cron.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.d/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_d_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_d_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.d/
      ansible.builtin.file:
        path: /etc/cron.d/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_d_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.daily [ref]
To properly set the owner of `/etc/cron.daily`, run the command:
    $ sudo chown root /etc/cron.daily
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_daily
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.daily/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_daily_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_daily_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.daily/
      ansible.builtin.file:
        path: /etc/cron.daily/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_daily_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.hourly [ref]
To properly set the owner of `/etc/cron.hourly`, run the command:
    $ sudo chown root /etc/cron.hourly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_hourly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.hourly/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_hourly_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_hourly_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.hourly/
      ansible.builtin.file:
        path: /etc/cron.hourly/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_hourly_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.monthly [ref]
To properly set the owner of `/etc/cron.monthly`, run the command:
    $ sudo chown root /etc/cron.monthly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_monthly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.monthly/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_monthly_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_monthly_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.monthly/
      ansible.builtin.file:
        path: /etc/cron.monthly/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_monthly_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.weekly [ref]
To properly set the owner of `/etc/cron.weekly`, run the command:
    $ sudo chown root /etc/cron.weekly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_weekly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.weekly/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_weekly_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_weekly_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.weekly/
      ansible.builtin.file:
        path: /etc/cron.weekly/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_weekly_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on cron.yearly [ref]
To properly set the owner of `/etc/cron.yearly`, run the command:
    $ sudo chown root /etc/cron.yearly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_cron_yearly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 2.4.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/cron.yearly/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_cron_yearly_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_cron_yearly_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/cron.yearly/
      ansible.builtin.file:
        path: /etc/cron.yearly/
        follow: false
        state: directory
        owner: '{{ file_owner_cron_yearly_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on crontab [ref]
To properly set the owner of `/etc/crontab`, run the command:
    $ sudo chown root /etc/crontab
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct user to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_crontab
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/crontab" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/crontab
    fi

    fi

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_crontab_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_crontab_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/crontab
      ansible.builtin.stat:
        path: /etc/crontab
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/crontab
      ansible.builtin.file:
        path: /etc/crontab
        follow: false
        owner: '{{ file_owner_crontab_newown }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_owner_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.d [ref]
To properly set the permissions of `/etc/cron.d`, run the command:
    $ sudo chmod 0700 /etc/cron.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.d/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.d/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.d/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.d/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.daily [ref]
To properly set the permissions of `/etc/cron.daily`, run the command:
    $ sudo chmod 0700 /etc/cron.daily
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_daily
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.daily/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.daily/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.daily/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.daily/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_daily
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.hourly [ref]
To properly set the permissions of `/etc/cron.hourly`, run the command:
    $ sudo chmod 0700 /etc/cron.hourly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_hourly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.hourly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.hourly/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.hourly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.hourly/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_hourly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.monthly [ref]
To properly set the permissions of `/etc/cron.monthly`, run the command:
    $ sudo chmod 0700 /etc/cron.monthly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_monthly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.6
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.monthly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.monthly/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.monthly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.monthly/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_monthly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.weekly [ref]
To properly set the permissions of `/etc/cron.weekly`, run the command:
    $ sudo chmod 0700 /etc/cron.weekly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_weekly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.weekly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.weekly/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.weekly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.weekly/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_cron_weekly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on cron.yearly [ref]
To properly set the permissions of `/etc/cron.yearly`, run the command:
    $ sudo chmod 0700 /etc/cron.yearly
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_cron_yearly
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 2.4.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/cron.yearly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/cron.yearly/ file(s)
      ansible.builtin.command: 'find -P /etc/cron.yearly/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/cron.yearly/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_cron_yearly
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on crontab [ref]
To properly set the permissions of `/etc/crontab`, run the command:
    $ sudo chmod 0600 /etc/crontab
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should have the correct access rights to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_crontab
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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
nist | CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 2.4.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    chmod u-xs,g-xwrs,o-xwrt /etc/crontab

    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-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/crontab
      ansible.builtin.stat:
        path: /etc/crontab
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xwrs,o-xwrt on /etc/crontab
      ansible.builtin.file:
        path: /etc/crontab
        mode: u-xs,g-xwrs,o-xwrt
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_crontab
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Group DHCP Group contains 1 group and 1 rule
[ref] The Dynamic Host Configuration Protocol (DHCP) allows systems to request and obtain an IP address and other configuration parameters from a server. This guide recommends configuring networking on clients by manually editing the appropriate files under `/etc/sysconfig`. Use of DHCP can make client systems vulnerable to compromise by rogue DHCP servers, and should be avoided unless necessary. If using DHCP is necessary, however, there are best practices that should be followed to minimize security risk.
Group Disable DHCP Server Group contains 1 rule
[ref] The DHCP server `dhcpd` is not installed or activated by default. If the software was installed and activated, but the system does not need to act as a DHCP server, it should be disabled and removed.
#### Rule Uninstall kea Package [ref]
If the system does not need to act as a DHCP server, the kea package can be uninstalled.
Rationale: | Removing the DHCP server ensures that it cannot be easily or accidentally reactivated and disrupt network operation.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_kea_removed
References: |
anssi | R62
cis | 2.1.4
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove kea
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove kea
    # from the system, and may remove any packages
    # that depend on kea. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "kea" ; then
    dnf remove -y --noautoremove "kea"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove kea
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=kea
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall kea Package: Ensure kea is removed'
      ansible.builtin.package:
        name: kea
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_kea_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_kea

    class remove_kea {
      package { 'kea':
        ensure => 'purged',
      }
    }
Group DNS Server Group contains 1 group and 2 rules
[ref] Most organizations have an operational need to run at least one nameserver. However, there are many common attacks involving DNS server software, and this server software should be disabled on any system on which it is not needed.
Group Disable DNS Server Group contains 1 rule
[ref] DNS software should be disabled on any systems which does not need to be a nameserver. Note that the BIND DNS server software is not installed on Red Hat Enterprise Linux 10 by default. The remainder of this section discusses secure configuration of systems which must be nameservers.
#### Rule Uninstall bind Package [ref]
The `named` service is provided by the `bind` package. The `bind` package can be removed with the following command:
    $ sudo dnf remove bind
Rationale: | If there is no need to make DNS server software available, removing it provides a safeguard against its activation.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_bind_removed
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
cis | 2.1.5
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove bind
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove bind
    # from the system, and may remove any packages
    # that depend on bind. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "bind" ; then
    dnf remove -y --noautoremove "bind"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove bind
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=bind
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall bind Package: Ensure bind is removed'
      ansible.builtin.package:
        name: bind
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_bind_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_bind

    class remove_bind {
      package { 'bind':
        ensure => 'purged',
      }
    }
#### Rule Disable dnsmasq Service [ref]
The `dnsmasq` service can be disabled with the following command:
    $ sudo systemctl mask --now dnsmasq.service
Rationale: | Unless a system is specifically designated to act as a DNS caching, DNS forwarding and/or DHCP server, it is recommended that the package be removed to reduce the potential attack surface.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_dnsmasq_disabled
References: |
cis | 2.1.6
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: dnsmasq.service
            enabled: false
            mask: true
          - name: dnsmasq.socket
            enabled: false
            mask: true
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    masked = ["dnsmasq"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" stop 'dnsmasq.service'
    fi
    "$SYSTEMCTL_EXEC" disable 'dnsmasq.service'
    "$SYSTEMCTL_EXEC" mask 'dnsmasq.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" -q list-unit-files dnsmasq.socket; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop 'dnsmasq.socket'
        fi
        "$SYSTEMCTL_EXEC" mask 'dnsmasq.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 'dnsmasq.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service disable dnsmasq
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_dnsmasq_disabled

    - name: Disable dnsmasq Service - Disable service dnsmasq
      block:

      - name: Disable dnsmasq 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 dnsmasq Service - Ensure dnsmasq.service is Masked
        ansible.builtin.systemd:
          name: dnsmasq.service
          state: stopped
          enabled: false
          masked: true
        when: service_exists.stdout_lines is search("dnsmasq.service", multiline=True)

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

      - name: Disable dnsmasq Service - Disable Socket dnsmasq
        ansible.builtin.systemd:
          name: dnsmasq.socket
          enabled: false
          state: stopped
          masked: true
        when: socket_file_exists.stdout_lines is search("dnsmasq.socket", multiline=True)
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - service_dnsmasq_disabled
      - special_service_block
      when: '"kernel-core" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include disable_dnsmasq

    class disable_dnsmasq {
      service {'dnsmasq':
        enable => false,
        ensure => 'stopped',
      }
    }
Group FTP Server Group contains 1 group and 2 rules
[ref] FTP is a common method for allowing remote access to files. Like telnet, the FTP protocol is unencrypted, which means that passwords and other data transmitted during the session can be captured and that the session is vulnerable to hijacking. Therefore, running the FTP server software is not recommended. However, there are some FTP server configurations which may be appropriate for some environments, particularly those which allow only read-only anonymous access as a means of downloading data available to the public.
Group Disable vsftpd if Possible Group contains 1 rule
[ref] To minimize attack surface, disable vsftpd if at all possible.
#### Rule Uninstall vsftpd Package [ref]
The `vsftpd` package can be removed with the following command:
     $ sudo dnf remove vsftpd
Rationale: | Removing the `vsftpd` package decreases the risk of its accidental activation.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_vsftpd_removed
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), IA-5(1)(c), IA-5(1).1(v), CM-7, CM-7.1(ii)
nist-csf | PR.IP-1, PR.PT-3
os-srg | SRG-OS-000074-GPOS-00042, SRG-OS-000095-GPOS-00049, SRG-OS-000480-GPOS-00227
cis | 2.1.7
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove vsftpd
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove vsftpd
    # from the system, and may remove any packages
    # that depend on vsftpd. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "vsftpd" ; then
    dnf remove -y --noautoremove "vsftpd"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove vsftpd
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=vsftpd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall vsftpd Package: Ensure vsftpd is removed'
      ansible.builtin.package:
        name: vsftpd
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - NIST-800-53-CM-7.1(ii)
      - NIST-800-53-IA-5(1)(c)
      - NIST-800-53-IA-5(1).1(v)
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_vsftpd_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_vsftpd

    class remove_vsftpd {
      package { 'vsftpd':
        ensure => 'purged',
      }
    }
#### Rule Remove ftp Package [ref]
FTP (File Transfer Protocol) is a traditional and widely used standard tool for transferring files between a server and clients over a network, especially where no authentication is necessary (permits anonymous users to connect to a server). The `ftp` package can be removed with the following command:
    $ sudo dnf remove ftp
Rationale: | FTP does not protect the confidentiality of data or authentication credentials. It is recommended SFTP be used if file transfer is required. Unless there is a need to run the system as a FTP server (for example, to allow anonymous downloads), it is recommended that the package be removed to reduce the potential attack surface.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_ftp_removed
References: |
pcidss4 | 2.2.4, 2.2
cis | 2.2.1
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove ftp
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove ftp
    # from the system, and may remove any packages
    # that depend on ftp. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "ftp" ; then
    dnf remove -y --noautoremove "ftp"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove ftp
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=ftp
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Remove ftp Package: Ensure ftp is removed'
      ansible.builtin.package:
        name: ftp
        state: absent
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_ftp_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_ftp

    class remove_ftp {
      package { 'ftp':
        ensure => 'purged',
      }
    }
Group Web Server Group contains 2 groups and 2 rules
[ref] The web server is responsible for providing access to content via the HTTP protocol. Web servers represent a significant security risk because: The HTTP port is commonly probed by malicious sources Web server software is very complex, and includes a long history of vulnerabilities The HTTP protocol is unencrypted and vulnerable to passive monitoring The system's default web server software is Apache 2 and is provided in the RPM package `httpd`.
Group Disable Apache if Possible Group contains 1 rule
[ref] If Apache was installed and activated, but the system does not need to act as a web server, then it should be disabled and removed from the system.
#### Rule Uninstall httpd Package [ref]
The `httpd` package can be removed with the following command:
    $ sudo dnf remove httpd
Rationale: | If there is no need to make the web server software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_httpd_removed
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
cis | 2.1.18
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove httpd
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove httpd
    # from the system, and may remove any packages
    # that depend on httpd. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "httpd" ; then
    dnf remove -y --noautoremove "httpd"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove httpd
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=httpd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall httpd Package: Ensure httpd is removed'
      ansible.builtin.package:
        name: httpd
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_httpd_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_httpd

    class remove_httpd {
      package { 'httpd':
        ensure => 'purged',
      }
    }
Group Disable NGINX if Possible Group contains 1 rule
[ref] If NGINX was installed and activated, but the system does not need to act as a web server, then it should be removed from the system.
#### Rule Uninstall nginx Package [ref]
The `nginx` package can be removed with the following command:
    $ sudo dnf remove nginx
Rationale: | If there is no need to make the web server software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_nginx_removed
References: |
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
cis | 2.1.18
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove nginx
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove nginx
    # from the system, and may remove any packages
    # that depend on nginx. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "nginx" ; then
    dnf remove -y --noautoremove "nginx"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove nginx
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=nginx
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall nginx Package: Ensure nginx is removed'
      ansible.builtin.package:
        name: nginx
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_nginx_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_nginx

    class remove_nginx {
      package { 'nginx':
        ensure => 'purged',
      }
    }
Group IMAP and POP3 Server Group contains 2 groups and 2 rules
[ref] Dovecot provides IMAP and POP3 services. It is not installed by default. The project page at http://www.dovecot.org contains more detailed information about Dovecot configuration.
Group Disable Cyrus IMAP Group contains 1 rule
[ref] If the system does not need to operate as an IMAP or POP3 server, the Cyrus IMAP software should be removed.
#### Rule Uninstall cyrus-imapd Package [ref]
The `cyrus-imapd` package can be removed with the following command:
    $ sudo dnf remove cyrus-imapd
Rationale: | If there is no need to make the cyrus-imapd software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_cyrus-imapd_removed
References: |
cis | 2.1.8
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove cyrus-imapd
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove cyrus-imapd
    # from the system, and may remove any packages
    # that depend on cyrus-imapd. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "cyrus-imapd" ; then
    dnf remove -y --noautoremove "cyrus-imapd"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove cyrus-imapd
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=cyrus-imapd
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall cyrus-imapd Package: Ensure cyrus-imapd is removed'
      ansible.builtin.package:
        name: cyrus-imapd
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_cyrus-imapd_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_cyrus-imapd

    class remove_cyrus-imapd {
      package { 'cyrus-imapd':
        ensure => 'purged',
      }
    }
Group Disable Dovecot Group contains 1 rule
[ref] If the system does not need to operate as an IMAP or POP3 server, the dovecot software should be disabled and removed.
#### Rule Uninstall dovecot Package [ref]
The `dovecot` package can be removed with the following command:
    $ sudo dnf remove dovecot
Rationale: | If there is no need to make the Dovecot software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_dovecot_removed
References: |
cis | 2.1.8
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove dovecot
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove dovecot
    # from the system, and may remove any packages
    # that depend on dovecot. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "dovecot" ; then
    dnf remove -y --noautoremove "dovecot"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove dovecot
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=dovecot
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall dovecot Package: Ensure dovecot is removed'
      ansible.builtin.package:
        name: dovecot
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_dovecot_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_dovecot

    class remove_dovecot {
      package { 'dovecot':
        ensure => 'purged',
      }
    }
Group Mail Server Software Group contains 1 group and 2 rules
[ref] Mail servers are used to send and receive email over the network. Mail is a very common service, and Mail Transfer Agents (MTAs) are obvious targets of network attack. Ensure that systems are not running MTAs unnecessarily, and configure needed MTAs as defensively as possible. Very few systems at any site should be configured to directly receive email over the network. Users should instead use mail client programs to retrieve email from a central server that supports protocols such as IMAP or POP3. However, it is normal for most systems to be independently capable of sending email, for instance so that cron jobs can report output to an administrator. Most MTAs, including Postfix, support a submission-only mode in which mail can be sent from the local system to a central site MTA (or directly delivered to a local account), but the system still cannot receive mail directly over a network. The `alternatives` program in Red Hat Enterprise Linux 10 permits selection of other mail server software (such as Sendmail), but Postfix is the default and is preferred. Postfix was coded with security in mind and can also be more effectively contained by SELinux as its modular design has resulted in separate processes performing specific actions. More information is available on its website, http://www.postfix.org.
Group Configure SMTP For Mail Clients Group contains 1 rule
[ref] This section discusses settings for Postfix in a submission-only e-mail configuration.
#### Rule Disable Postfix Network Listening [ref]
Edit the file `/etc/postfix/main.cf` to ensure that only the following `inet_interfaces` line appears:
    inet_interfaces = loopback-only
Rationale: | This ensures `postfix` accepts mail messages (such as cron job reports) from the local system only, and not from the network, which protects it from network attack.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_postfix_network_listening_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
anssi | R74
pcidss4 | 1.4.2, 1.4
cis | 2.1.21
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q postfix; }; then

    var_postfix_inet_interfaces='loopback-only'

    if [ -e "/etc/postfix/main.cf" ] ; then

        LC_ALL=C sed -i "/^\s*inet_interfaces\s\+=\s\+/Id" "/etc/postfix/main.cf"
    else
        touch "/etc/postfix/main.cf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/postfix/main.cf"

    cp "/etc/postfix/main.cf" "/etc/postfix/main.cf.bak"
    # Insert at the end of the file
    printf '%s\n' "inet_interfaces=$var_postfix_inet_interfaces" >> "/etc/postfix/main.cf"
    # Clean up after ourselves.
    rm "/etc/postfix/main.cf.bak"

    systemctl restart postfix

    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-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - postfix_network_listening_disabled
      - restrict_strategy
    - name: XCCDF Value var_postfix_inet_interfaces # promote to variable
      set_fact:
        var_postfix_inet_interfaces: !!str loopback-only
      tags:
        - always

    - name: Gather list of packages
      ansible.builtin.package_facts:
        manager: auto
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"postfix" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - postfix_network_listening_disabled
      - restrict_strategy

    - name: Make changes to Postfix configuration file
      ansible.builtin.lineinfile:
        path: /etc/postfix/main.cf
        create: false
        regexp: (?i)^inet_interfaces\s*=\s.*
        line: inet_interfaces = {{ var_postfix_inet_interfaces }}
        state: present
        insertafter: ^inet_interfaces\s*=\s.*
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"postfix" in ansible_facts.packages'
      - '"postfix" in ansible_facts.packages'
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-1.4
      - PCI-DSSv4-1.4.2
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - postfix_network_listening_disabled
      - restrict_strategy
#### Rule Ensure Mail Transfer Agent is not Listening on any non-loopback Address [ref]
Mail Transfer Agents (MTA), such as sendmail and Postfix, are used to listen for incoming mail and transfer the messages to the appropriate user or mail server. If the system is not intended to be a mail server, it is recommended that the MTA be configured to only process local mail.
Rationale: | The software for all Mail Transfer Agents is complex and most have a long history of security issues. While it is important to ensure that the system can process local mail messages, it is not necessary to have the MTA's daemon listening on a port unless the server is intended to be a mail server that receives and processes mail from other systems.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_has_nonlocal_mta
References: |
cis | 2.1.21
Group NFS and RPC Group contains 4 groups and 2 rules
[ref] The Network File System is a popular distributed filesystem for the Unix environment, and is very widely deployed. This section discusses the circumstances under which it is possible to disable NFS and its dependencies, and then details steps which should be taken to secure NFS's configuration. This section is relevant to systems operating as NFS clients, as well as to those operating as NFS servers.
Group Disable All NFS Services if Possible Group contains 1 group and 1 rule
[ref] If there is not a reason for the system to operate as either an NFS client or an NFS server, follow all instructions in this section to disable subsystems required by NFS. Warning: The steps in this section will prevent a system from operating as either an NFS client or an NFS server. Only perform these steps on systems which do not need NFS at all.
Group Disable Services Used Only by NFS Group contains 1 rule
[ref] If NFS is not needed, disable the NFS client daemons nfslock, rpcgssd, and rpcidmapd. All of these daemons run with elevated privileges, and many listen for network connections. If they are not needed, they should be disabled to improve system security posture.
#### Rule Disable rpcbind Service [ref]
The rpcbind utility maps RPC services to the ports on which they listen. RPC processes notify rpcbind when they start, registering the ports they are listening on and the RPC program numbers they expect to serve. The rpcbind service redirects the client to the proper port number so it can communicate with the requested service. If the system does not require RPC (such as for NFS servers) then this service should be disabled. The `rpcbind` service can be disabled with the following command:
    $ sudo systemctl mask --now rpcbind.service
Rationale: | If the system does not require rpc based services, it is recommended that rpcbind be disabled to reduce the attack surface.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_service_rpcbind_disabled
References: |
pcidss4 | 2.2.4, 2.2
cis | 2.1.11
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: rpcbind.service
            enabled: false
            mask: true
          - name: rpcbind.socket
            enabled: false
            mask: true
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    masked = ["rpcbind"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" stop 'rpcbind.service'
    fi
    "$SYSTEMCTL_EXEC" disable 'rpcbind.service'
    "$SYSTEMCTL_EXEC" mask 'rpcbind.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" -q list-unit-files rpcbind.socket; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop 'rpcbind.socket'
        fi
        "$SYSTEMCTL_EXEC" mask 'rpcbind.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 'rpcbind.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service disable rpcbind
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - service_rpcbind_disabled

    - name: Disable rpcbind Service - Disable service rpcbind
      block:

      - name: Disable rpcbind 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 rpcbind Service - Ensure rpcbind.service is Masked
        ansible.builtin.systemd:
          name: rpcbind.service
          state: stopped
          enabled: false
          masked: true
        when: service_exists.stdout_lines is search("rpcbind.service", multiline=True)

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

      - name: Disable rpcbind Service - Disable Socket rpcbind
        ansible.builtin.systemd:
          name: rpcbind.socket
          enabled: false
          state: stopped
          masked: true
        when: socket_file_exists.stdout_lines is search("rpcbind.socket", multiline=True)
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - service_rpcbind_disabled
      - special_service_block
      when: '"kernel-core" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include disable_rpcbind

    class disable_rpcbind {
      service {'rpcbind':
        enable => false,
        ensure => 'stopped',
      }
    }
Group Configure NFS Clients Group contains 1 group and 1 rule
[ref] The steps in this section are appropriate for systems which operate as NFS clients.
Group Disable NFS Server Daemons Group contains 1 rule
[ref] There is no need to run the NFS server daemons `nfs` and `rpcsvcgssd` except on a small number of properly secured systems designated as NFS servers. Ensure that these daemons are turned off on clients.
#### Rule Disable Network File System (nfs) [ref]
The Network File System (NFS) service allows remote hosts to mount and interact with shared filesystems on the local system. If the local system is not designated as a NFS server then this service should be disabled. The `nfs-server` service can be disabled with the following command:
    $ sudo systemctl mask --now nfs-server.service
Rationale: | Unnecessary services should be disabled to decrease the attack surface of the system.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_service_nfs_disabled
References: |
cis-csc | 11, 12, 14, 15, 16, 18, 3, 5
cobit5 | DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
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.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
nist | CM-7(a), CM-7(b), CM-6(a)
nist-csf | PR.AC-4, PR.AC-6, PR.PT-3
cis | 2.1.9
Remediation Kubernetes snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: nfs-server.service
            enabled: false
            mask: true
          - name: nfs-server.socket
            enabled: false
            mask: true
Remediation OSBuild Blueprint snippet ⇲
    [customizations.services]
    masked = ["nfs-server"]
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
      "$SYSTEMCTL_EXEC" stop 'nfs-server.service'
    fi
    "$SYSTEMCTL_EXEC" disable 'nfs-server.service'
    "$SYSTEMCTL_EXEC" mask 'nfs-server.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" -q list-unit-files nfs-server.socket; then
        if [[ $("$SYSTEMCTL_EXEC" is-system-running) != "offline" ]]; then
          "$SYSTEMCTL_EXEC" stop 'nfs-server.socket'
        fi
        "$SYSTEMCTL_EXEC" mask 'nfs-server.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 'nfs-server.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    service disable nfs-server
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - service_nfs_disabled
      - unknown_severity

    - name: Disable Network File System (nfs) - Disable service nfs-server
      block:

      - name: Disable Network File System (nfs) - 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 Network File System (nfs) - Ensure nfs-server.service is Masked
        ansible.builtin.systemd:
          name: nfs-server.service
          state: stopped
          enabled: false
          masked: true
        when: service_exists.stdout_lines is search("nfs-server.service", multiline=True)

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

      - name: Disable Network File System (nfs) - Disable Socket nfs-server
        ansible.builtin.systemd:
          name: nfs-server.socket
          enabled: false
          state: stopped
          masked: true
        when: socket_file_exists.stdout_lines is search("nfs-server.socket", multiline=True)
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - service_nfs_disabled
      - special_service_block
      - unknown_severity
      when: '"kernel-core" in ansible_facts.packages'
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | enable
    include disable_nfs-server

    class disable_nfs-server {
      service {'nfs-server':
        enable => false,
        ensure => 'stopped',
      }
    }
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.redhat.com/en/documentation/red_hat_enterprise_linux/10/html/configuring_time_synchronization/index 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 A remote time server for Chrony is configured [ref]
`Chrony` is a daemon which implements the Network Time Protocol (NTP). It is designed to synchronize system clocks across a variety of systems and use a source that is highly accurate. More information on `chrony` can be found at https://chrony-project.org/. `Chrony` can be configured to be a client and/or a server. Add or edit server or pool lines to `/etc/chrony.conf` as appropriate:
    server <remote-server>
Alternatively, server or pool directives can be specified in files included via `sourcedir` or `confdir` directives in `/etc/chrony.conf`. When using `sourcedir`, create `.sources` files in the specified directory:
    # In /etc/chrony.conf:
    sourcedir /etc/chrony/sources.d

    # In /etc/chrony/sources.d/ntp.sources:
    server 0.pool.ntp.org
When using `confdir`, create `.conf` files in the specified directory:
    # In /etc/chrony.conf:
    confdir /etc/chrony/conf.d

    # In /etc/chrony/conf.d/ntp-servers.conf:
    pool 1.pool.ntp.org
Multiple servers may be configured.
Rationale: | If `chrony` is in use on the system proper configuration is vital to ensuring time synchronization is working properly.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_chronyd_specify_remote_server
References: |
nist | CM-6(a), AU-8(1)(a)
pcidss | Req-10.4.3
os-srg | SRG-OS-000355-GPOS-00143
anssi | R71
ism | 0988, 1405
pcidss4 | 10.6.2, 10.6
cis | 2.3.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q chrony; }; then

    var_multiple_time_servers='0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org'

    config_file="/etc/chrony.conf"

    if ! grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$config_file" ; then
      if ! grep -q '#[[:space:]]*server' "$config_file" ; then
        for server in $(echo "$var_multiple_time_servers" | tr ',' '\n') ; do
          printf '\nserver %s' "$server" >> "$config_file"
        done
      else
        sed -i 's/#[ \t]*server/server/g' "$config_file"
      fi
      if [[ -s "$config_file" ]] && [[ -n "$(tail -c 1 -- "$config_file" || true)" ]]; then
          LC_ALL=C sed -i --follow-symlinks '$a'\\ "$config_file"
      fi
    fi

    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-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
    - name: XCCDF Value var_multiple_time_servers # promote to variable
      set_fact:
        var_multiple_time_servers: !!str 0.rhel.pool.ntp.org,1.rhel.pool.ntp.org,2.rhel.pool.ntp.org,3.rhel.pool.ntp.org
      tags:
        - always

    - name: Check if chrony main config has active server/pool entries
      ansible.builtin.command:
        cmd: grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' /etc/chrony.conf
      register: chrony_conf_has_servers
      changed_when: false
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Extract sourcedir paths from chrony configuration
      ansible.builtin.shell:
        cmd: grep '^[[:space:]]*sourcedir[[:space:]]\+' /etc/chrony.conf | awk '{print
          $2}'
      register: chrony_sourcedir_paths
      changed_when: false
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Check for server/pool entries in sourcedir .sources files
      ansible.builtin.shell:
        cmd: |
          for dir in {{ chrony_sourcedir_paths.stdout_lines | join(' ') }}; do
            if [ -d "$dir" ]; then
              grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$dir"/*.sources 2>/dev/null && exit 0
            fi
          done
          exit 1
      register: chrony_sourcedir_has_servers
      changed_when: false
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - chrony_sourcedir_paths.stdout_lines | length > 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Extract confdir paths from chrony configuration
      ansible.builtin.shell:
        cmd: grep '^[[:space:]]*confdir[[:space:]]\+' /etc/chrony.conf | awk '{print $2}'
      register: chrony_confdir_paths
      changed_when: false
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - (chrony_sourcedir_paths.stdout_lines | default([]) | length == 0 or chrony_sourcedir_has_servers.rc
        != 0)
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Check for server/pool entries in confdir .conf files
      ansible.builtin.shell:
        cmd: |
          for dir in {{ chrony_confdir_paths.stdout_lines | join(' ') }}; do
            if [ -d "$dir" ]; then
              grep -q '^[[:space:]]*\(server\|pool\)[[:space:]]\+[[:graph:]]\+' "$dir"/*.conf 2>/dev/null && exit 0
            fi
          done
          exit 1
      register: chrony_confdir_has_servers
      changed_when: false
      failed_when: false
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - chrony_confdir_paths.stdout_lines | default([]) | length > 0
      - (chrony_sourcedir_paths.stdout_lines | default([]) | length == 0 or chrony_sourcedir_has_servers.rc
        != 0)
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Create sourcedir directory if needed
      ansible.builtin.file:
        path: '{{ chrony_sourcedir_paths.stdout_lines[0] }}'
        state: directory
        mode: '0755'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - chrony_sourcedir_paths.stdout_lines | default([]) | length > 0
      - chrony_sourcedir_has_servers.rc != 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Add remote time servers to sourcedir .sources file
      ansible.builtin.lineinfile:
        path: '{{ chrony_sourcedir_paths.stdout_lines[0] }}/ntp-servers.sources'
        line: server {{ item }}
        state: present
        create: true
        mode: '0644'
      loop: '{{ var_multiple_time_servers.split(",") }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - chrony_sourcedir_paths.stdout_lines | default([]) | length > 0
      - chrony_sourcedir_has_servers.rc != 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Create confdir directory if needed
      ansible.builtin.file:
        path: '{{ chrony_confdir_paths.stdout_lines[0] }}'
        state: directory
        mode: '0755'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - (chrony_sourcedir_paths.stdout_lines | default([]) | length == 0 or chrony_sourcedir_has_servers.rc
        != 0)
      - chrony_confdir_paths.stdout_lines | default([]) | length > 0
      - chrony_confdir_has_servers.rc != 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Add remote time servers to confdir .conf file
      ansible.builtin.lineinfile:
        path: '{{ chrony_confdir_paths.stdout_lines[0] }}/ntp-servers.conf'
        line: server {{ item }}
        state: present
        create: true
        mode: '0644'
      loop: '{{ var_multiple_time_servers.split(",") }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - (chrony_sourcedir_paths.stdout_lines | default([]) | length == 0 or chrony_sourcedir_has_servers.rc
        != 0)
      - chrony_confdir_paths.stdout_lines | default([]) | length > 0
      - chrony_confdir_has_servers.rc != 0
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Add remote time servers to main chrony configuration
      ansible.builtin.lineinfile:
        path: /etc/chrony.conf
        line: server {{ item }}
        state: present
        create: true
      loop: '{{ var_multiple_time_servers.split(",") }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chrony_conf_has_servers.rc != 0
      - (chrony_sourcedir_paths.stdout_lines | default([]) | length == 0 or chrony_sourcedir_has_servers.rc
        != 0)
      - (chrony_confdir_paths.stdout_lines | default([]) | length == 0 or chrony_confdir_has_servers.rc
        != 0)
      tags:
      - NIST-800-53-AU-8(1)(a)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-10.4.3
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.2
      - chronyd_specify_remote_server
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Ensure that chronyd is running under chrony user account [ref]
chrony is a daemon which implements the Network Time Protocol (NTP). It is designed to synchronize system clocks across a variety of systems and use a source that is highly accurate. More information on chrony can be found at https://chrony-project.org/. Chrony can be configured to be a client and/or a server. To ensure that chronyd is running under chrony user account, remove any `-u ...` option from `OPTIONS` other than `-u chrony`, as chrony is run under its own user by default. This recommendation only applies if chrony is in use on the system.
Rationale: | If chrony is in use on the system proper configuration is vital to ensuring time synchronization is working properly.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_chronyd_run_as_chrony_user
References: |
pcidss4 | 10.6.3, 10.6
cis | 2.3.3
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core && { rpm --quiet -q chrony; }; then

    if grep -q 'OPTIONS=.*' /etc/sysconfig/chronyd; then
    	# trying to solve cases where the parameter after OPTIONS
    	#may or may not be enclosed in quotes
    	sed -i -E -e 's/\s*-u\s*\w+\s*/ /' -e 's/^([\s]*OPTIONS=["]?[^"]*)("?)/\1\2/' /etc/sysconfig/chronyd
    fi

    if grep -q 'OPTIONS=.*' /etc/sysconfig/chronyd; then
    	# trying to solve cases where the parameter after OPTIONS
    	#may or may not be enclosed in quotes
    	sed -i -E -e 's/\s*-u\s*\w+\s*/ /' -e 's/^([\s]*OPTIONS=["]?[^"]*)("?)/\1 -u chrony\2/' /etc/sysconfig/chronyd
    else
    	echo 'OPTIONS="-u chrony"' >> /etc/sysconfig/chronyd
    fi

    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:
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.3
      - chronyd_run_as_chrony_user
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Detect if file /etc/sysconfig/chronyd is not empty or missing
      ansible.builtin.find:
        path: /etc/sysconfig/
        patterns: chronyd
        contains: ^([\s]*OPTIONS=["]?[^"]*)("?)
      register: chronyd_file
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.3
      - chronyd_run_as_chrony_user
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Remove any previous configuration of user used to run chronyd process
      ansible.builtin.replace:
        path: /etc/sysconfig/chronyd
        regexp: \s*-u\s*\w+\s*
        replace: ' '
      when:
      - '"kernel-core" in ansible_facts.packages'
      - '"chrony" in ansible_facts.packages'
      - chronyd_file is defined and chronyd_file.matched > 0
      tags:
      - PCI-DSSv4-10.6
      - PCI-DSSv4-10.6.3
      - chronyd_run_as_chrony_user
      - configure_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Group Obsolete Services Group contains 2 groups and 5 rules
[ref] This section discusses a number of network-visible services which have historically caused problems for system security, and for which disabling or severely limiting the service has been the best available guidance for some time. As a result of this, many of these services are not installed as part of Red Hat Enterprise Linux 10 by default. Organizations which are running these services should switch to more secure equivalents as soon as possible. If it remains absolutely necessary to run one of these services for legacy reasons, care should be taken to restrict the service as much as possible, for instance by configuring host firewall software such as `firewalld` to restrict access to the vulnerable service to only those remote hosts which have a known need to use it.
Group Telnet Group contains 2 rules
[ref] The telnet protocol does not provide confidentiality or integrity for information transmitted on the network. This includes authentication information such as passwords. Organizations which use telnet should be actively working to migrate to a more secure protocol.
#### Rule Uninstall telnet-server Package [ref]
The `telnet-server` package can be removed with the following command:
    $ sudo dnf remove telnet-server
Rationale: | It is detrimental for operating systems to provide, or install by default, functionality exceeding requirements or mission objectives. These unnecessary capabilities are often overlooked and therefore may remain insecure. They increase the risk to the platform by providing additional attack vectors. The telnet service provides an unencrypted remote access service which does not provide for the confidentiality and integrity of user passwords or the remote session. If a privileged user were to login using this service, the privileged user password could be compromised. Removing the `telnet-server` package decreases the risk of the telnet service's accidental (or intentional) activation.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_telnet-server_removed
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)(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.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
pcidss | Req-2.2.2
os-srg | SRG-OS-000095-GPOS-00049
anssi | R62
ism | 1409
pcidss4 | 2.2.4, 2.2
bsi | SYS.1.3.A8
cis | 2.1.15
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove telnet-server
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove telnet-server
    # from the system, and may remove any packages
    # that depend on telnet-server. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "telnet-server" ; then
    dnf remove -y --noautoremove "telnet-server"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove telnet-server
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=telnet-server
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall telnet-server Package: Ensure telnet-server is removed'
      ansible.builtin.package:
        name: telnet-server
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSS-Req-2.2.2
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_telnet-server_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_telnet-server

    class remove_telnet-server {
      package { 'telnet-server':
        ensure => 'purged',
      }
    }
#### Rule Remove telnet Clients [ref]
The telnet client allows users to start connections to other systems via the telnet protocol.
Rationale: | The `telnet` protocol is insecure and unencrypted. The use of an unencrypted transmission medium could allow an unauthorized user to steal credentials. The `ssh` package provides an encrypted session and stronger security and is included in Red Hat Enterprise Linux 10.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_telnet_removed
References: |
cui | 3.1.13
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)
iso27001-2013 | A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3
anssi | R62
ism | 1409
pcidss4 | 2.2.4, 2.2
bsi | SYS.1.3.A8
cis | 2.2.3
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove telnet
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove telnet
    # from the system, and may remove any packages
    # that depend on telnet. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "telnet" ; then
    dnf remove -y --noautoremove "telnet"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove telnet
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=telnet
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Remove telnet Clients: Ensure telnet is removed'
      ansible.builtin.package:
        name: telnet
        state: absent
      tags:
      - NIST-800-171-3.1.13
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_telnet_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_telnet

    class remove_telnet {
      package { 'telnet':
        ensure => 'purged',
      }
    }
Group TFTP Server Group contains 2 rules
[ref] TFTP is a lightweight version of the FTP protocol which has traditionally been used to configure networking equipment. However, TFTP provides little security, and modern versions of networking operating systems frequently support configuration via SSH or other more secure protocols. A TFTP server should be run only if no more secure method of supporting existing equipment can be found.
#### Rule Uninstall tftp-server Package [ref]
The `tftp-server` package can be removed with the following command:
     $ sudo dnf remove tftp-server
Rationale: | Removing the `tftp-server` package decreases the risk of the accidental (or intentional) activation of tftp services. If TFTP is required for operational support (such as transmission of router configurations), its use must be documented with the Information Systems Security Manager (ISSM), restricted to only authorized personnel, and have access control rules established.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_tftp-server_removed
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
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
os-srg | SRG-OS-000480-GPOS-00227
anssi | R62
pcidss4 | 2.2.4, 2.2
cis | 2.1.16
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove tftp-server
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove tftp-server
    # from the system, and may remove any packages
    # that depend on tftp-server. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "tftp-server" ; then
    dnf remove -y --noautoremove "tftp-server"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove tftp-server
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=tftp-server
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall tftp-server Package: Ensure tftp-server is removed'
      ansible.builtin.package:
        name: tftp-server
        state: absent
      tags:
      - NIST-800-53-CM-6(a)
      - NIST-800-53-CM-7(a)
      - NIST-800-53-CM-7(b)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - high_severity
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_tftp-server_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_tftp-server

    class remove_tftp-server {
      package { 'tftp-server':
        ensure => 'purged',
      }
    }
#### Rule Remove tftp Daemon [ref]
Trivial File Transfer Protocol (TFTP) is a simple file transfer protocol, typically used to automatically transfer configuration or boot files between systems. TFTP does not support authentication and can be easily hacked. The package `tftp` is a client program that allows for connections to a `tftp` server.
Rationale: | It is recommended that TFTP be removed, unless there is a specific need for TFTP (such as a boot server). In that case, use extreme caution when configuring the services.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_tftp_removed
References: |
os-srg | SRG-OS-000074-GPOS-00042
anssi | R62
pcidss4 | 2.2.4, 2.2
cis | 2.2.4
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove tftp
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove tftp
    # from the system, and may remove any packages
    # that depend on tftp. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "tftp" ; then
    dnf remove -y --noautoremove "tftp"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove tftp
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=tftp
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Remove tftp Daemon: Ensure tftp is removed'
      ansible.builtin.package:
        name: tftp
        state: absent
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - low_severity
      - no_reboot_needed
      - package_tftp_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_tftp

    class remove_tftp {
      package { 'tftp':
        ensure => 'purged',
      }
    }
#### Rule Uninstall rsync Package [ref]
The rsyncd service can be used to synchronize files between systems over network links. The `rsync-daemon` package can be removed with the following command:
    $ sudo dnf remove rsync-daemon
Rationale: | The rsyncd service presents a security risk as it uses unencrypted protocols for communication.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_rsync_removed
References: |
cis | 2.1.12
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove rsync-daemon
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove rsync-daemon
    # from the system, and may remove any packages
    # that depend on rsync-daemon. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "rsync-daemon" ; then
    dnf remove -y --noautoremove "rsync-daemon"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove rsync-daemon
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=rsync-daemon
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall rsync Package: Ensure rsync-daemon is removed'
      ansible.builtin.package:
        name: rsync-daemon
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - package_rsync_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_rsync-daemon

    class remove_rsync-daemon {
      package { 'rsync-daemon':
        ensure => 'purged',
      }
    }
Group Proxy Server Group contains 1 group and 1 rule
[ref] A proxy server is a very desirable target for a potential adversary because much (or all) sensitive data for a given infrastructure may flow through it. Therefore, if one is required, the system acting as a proxy server should be dedicated to that purpose alone and be stored in a physically secure location. The system's default proxy server software is Squid, and provided in an RPM package of the same name.
Group Disable Squid if Possible Group contains 1 rule
[ref] If Squid was installed and activated, but the system does not need to act as a proxy server, then it should be disabled and removed.
#### Rule Uninstall squid Package [ref]
The `squid` package can be removed with the following command:
     $ sudo dnf remove squid
Rationale: | If there is no need to make the proxy server software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_squid_removed
References: |
ism | 1409
cis | 2.1.17
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove squid
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove squid
    # from the system, and may remove any packages
    # that depend on squid. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "squid" ; then
    dnf remove -y --noautoremove "squid"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove squid
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=squid
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall squid Package: Ensure squid is removed'
      ansible.builtin.package:
        name: squid
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_squid_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_squid

    class remove_squid {
      package { 'squid':
        ensure => 'purged',
      }
    }
Group Samba(SMB) Microsoft Windows File Sharing Server Group contains 1 group and 1 rule
[ref] When properly configured, the Samba service allows Linux systems to provide file and print sharing to Microsoft Windows systems. There are two software packages that provide Samba support. The first, `samba-client`, provides a series of command line tools that enable a client system to access Samba shares. The second, simply labeled `samba`, provides the Samba service. It is this second package that allows a Linux system to act as an Active Directory server, a domain controller, or as a domain member. Only the `samba-client` package is installed by default.
Group Disable Samba if Possible Group contains 1 rule
[ref] Even after the Samba server package has been installed, it will remain disabled. Do not enable this service unless it is absolutely necessary to provide Microsoft Windows file and print sharing functionality.
#### Rule Uninstall Samba Package [ref]
The `samba` package can be removed with the following command:
     $ sudo dnf remove samba
Rationale: | If there is no need to make the Samba software available, removing it provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_samba_removed
References: |
cis | 2.1.13
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove samba
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove samba
    # from the system, and may remove any packages
    # that depend on samba. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "samba" ; then
    dnf remove -y --noautoremove "samba"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove samba
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=samba
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall Samba Package: Ensure samba is removed'
      ansible.builtin.package:
        name: samba
        state: absent
      tags:
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_samba_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_samba

    class remove_samba {
      package { 'samba':
        ensure => 'purged',
      }
    }
Group SNMP Server Group contains 1 group and 1 rule
[ref] The Simple Network Management Protocol allows administrators to monitor the state of network devices, including computers. Older versions of SNMP were well-known for weak security, such as plaintext transmission of the community string (used for authentication) and usage of easily-guessable choices for the community string.
Group Disable SNMP Server if Possible Group contains 1 rule
[ref] The system includes an SNMP daemon that allows for its remote monitoring, though it not installed by default. If it was installed and activated but is not needed, the software should be disabled and removed.
#### Rule Uninstall net-snmp Package [ref]
The `net-snmp` package provides the snmpd service. The `net-snmp` package can be removed with the following command:
    $ sudo dnf remove net-snmp
Rationale: | If there is no need to run SNMP server software, removing the package provides a safeguard against its activation.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_net-snmp_removed
References: |
pcidss4 | 2.2.4, 2.2
cis | 2.1.14
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    dnf remove net-snmp
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    # CAUTION: This remediation script will remove net-snmp
    # from the system, and may remove any packages
    # that depend on net-snmp. Execute this
    # remediation AFTER testing on a non-production
    # system!

    if rpm -q --quiet "net-snmp" ; then
    dnf remove -y --noautoremove "net-snmp"
    fi
Remediation script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package remove net-snmp
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    package --remove=net-snmp
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    - name: 'Uninstall net-snmp Package: Ensure net-snmp is removed'
      ansible.builtin.package:
        name: net-snmp
        state: absent
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.4
      - disable_strategy
      - low_complexity
      - low_disruption
      - no_reboot_needed
      - package_net-snmp_removed
      - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | disable
    include remove_net-snmp

    class remove_net-snmp {
      package { 'net-snmp':
        ensure => 'purged',
      }
    }
Group SSH Server Group contains 1 group and 32 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 Server if Necessary Group contains 17 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 Set SSH Client Alive Count Max [ref]
The SSH server sends at most `ClientAliveCountMax` messages during a SSH session and waits for a response from the SSH client. The option `ClientAliveInterval` configures timeout after each `ClientAliveCountMax` message. If the SSH server does not receive a response from the client, then the connection is considered unresponsive and terminated. For SSH earlier than v8.2, a `ClientAliveCountMax` value of `0` causes a timeout precisely when the `ClientAliveInterval` is set. Starting with v8.2, a value of `0` disables the timeout functionality completely. If the option is set to a number greater than `0`, then the session will be disconnected after `ClientAliveInterval * ClientAliveCountMax` seconds without receiving a keep alive message.
Rationale: | This ensures a user login will be terminated as soon as the `ClientAliveInterval` is reached.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_keepalive
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
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.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 | 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
pcidss | Req-8.1.8
os-srg | SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109
pcidss4 | 8.2.8, 8.2
cis | 5.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_sshd_set_keepalive='1'

    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*ClientAliveCountMax\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*ClientAliveCountMax\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*ClientAliveCountMax\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' "ClientAliveCountMax $var_sshd_set_keepalive" > "/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
      - NIST-800-171-3.1.11
      - NIST-800-53-AC-12
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_keepalive
    - name: XCCDF Value var_sshd_set_keepalive # promote to variable
      set_fact:
        var_sshd_set_keepalive: !!str 1
      tags:
        - always

    - name: Set SSH Client Alive Count Max - Check if the parameter ClientAliveCountMax
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      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-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_keepalive

    - name: Set SSH Client Alive Count Max - Check if the parameter ClientAliveCountMax
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "ClientAliveCountMax"| regex_escape }}\s+{{ var_sshd_set_keepalive
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      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-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_keepalive

    - name: Set SSH Client Alive Count Max
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "ClientAliveCountMax"| 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 ClientAliveCountMax is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "ClientAliveCountMax"| 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*{{ "ClientAliveCountMax"| 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*{{ "ClientAliveCountMax"| regex_escape }}\s+
          line: ClientAliveCountMax {{ var_sshd_set_keepalive }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      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-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_keepalive

    - name: Set SSH Client Alive Count Max - 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-core" in ansible_facts.packages'
      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-2(5)
      - NIST-800-53-CM-6(a)
      - NIST-800-53-SC-10
      - PCI-DSS-Req-8.1.8
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_keepalive
#### Rule Set SSH Client Alive Interval [ref]
SSH allows administrators to set a network responsiveness timeout interval. After this interval has passed, the unresponsive client will be automatically logged out. To set this timeout interval, edit the following line in `/etc/ssh/sshd_config` as follows:
    ClientAliveInterval 300
The timeout interval is given in seconds. For example, have a timeout of 10 minutes, set interval to 600. If a shorter timeout has already been set for the login shell, that value will preempt any SSH setting made in `/etc/ssh/sshd_config`. Keep in mind that some processes may stop SSH from correctly detecting that the user is idle. Warning: SSH disconnecting unresponsive clients will not have desired effect without also configuring ClientAliveCountMax in the SSH service configuration. Warning: Following conditions may prevent the SSH session to time out: Remote processes on the remote machine generates output. As the output has to be transferred over the network to the client, the timeout is reset every time such transfer happens. Any `scp` or `sftp` activity by the same user to the host resets the timeout.
Rationale: | Terminating an idle ssh 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_sshd_set_idle_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
pcidss | Req-8.1.8
os-srg | SRG-OS-000126-GPOS-00066, SRG-OS-000163-GPOS-00072, SRG-OS-000279-GPOS-00109, SRG-OS-000395-GPOS-00175
pcidss4 | 8.2.8, 8.2
cis | 5.1.7
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    sshd_idle_timeout_value='300'

    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*ClientAliveInterval\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*ClientAliveInterval\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*ClientAliveInterval\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' "ClientAliveInterval $sshd_idle_timeout_value" > "/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
      - 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
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_idle_timeout
    - name: XCCDF Value sshd_idle_timeout_value # promote to variable
      set_fact:
        sshd_idle_timeout_value: !!str 300
      tags:
        - always

    - name: Set SSH Client Alive Interval - Check if the parameter ClientAliveInterval
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      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
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_idle_timeout

    - name: Set SSH Client Alive Interval - Check if the parameter ClientAliveInterval
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "ClientAliveInterval"| regex_escape }}\s+{{ sshd_idle_timeout_value
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      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
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_idle_timeout

    - name: Set SSH Client Alive Interval
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "ClientAliveInterval"| 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 ClientAliveInterval is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "ClientAliveInterval"| 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*{{ "ClientAliveInterval"| 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*{{ "ClientAliveInterval"| regex_escape }}\s+
          line: ClientAliveInterval {{ sshd_idle_timeout_value }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      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
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_idle_timeout

    - name: Set SSH Client Alive Interval - 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-core" in ansible_facts.packages'
      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
      - PCI-DSSv4-8.2
      - PCI-DSSv4-8.2.8
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_idle_timeout
#### 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
cis | 5.1.10
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      annotations:
        complianceascode.io/ocp-version: '<=4.12'
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,%23%09%24OpenBSD%3A%20sshd_config%2Cv%201.103%202018%2F04%2F09%2020%3A41%3A22%20tj%20Exp%20%24%0A%0A%23%20This%20is%20the%20sshd%20server%20system-wide%20configuration%20file.%20%20See%0A%23%20sshd_config%285%29%20for%20more%20information.%0A%0A%23%20This%20sshd%20was%20compiled%20with%20PATH%3D%2Fusr%2Flocal%2Fbin%3A%2Fusr%2Fbin%3A%2Fusr%2Flocal%2Fsbin%3A%2Fusr%2Fsbin%0A%0A%23%20The%20strategy%20used%20for%20options%20in%20the%20default%20sshd_config%20shipped%20with%0A%23%20OpenSSH%20is%20to%20specify%20options%20with%20their%20default%20value%20where%0A%23%20possible%2C%20but%20leave%20them%20commented.%20%20Uncommented%20options%20override%20the%0A%23%20default%20value.%0A%0A%23%20If%20you%20want%20to%20change%20the%20port%20on%20a%20SELinux%20system%2C%20you%20have%20to%20tell%0A%23%20SELinux%20about%20this%20change.%0A%23%20semanage%20port%20-a%20-t%20ssh_port_t%20-p%20tcp%20%23PORTNUMBER%0A%23%0A%23Port%2022%0A%23AddressFamily%20any%0A%23ListenAddress%200.0.0.0%0A%23ListenAddress%20%3A%3A%0A%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_rsa_key%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_ecdsa_key%0AHostKey%20%2Fetc%2Fssh%2Fssh_host_ed25519_key%0A%0A%23%20Ciphers%20and%20keying%0ARekeyLimit%20512M%201h%0A%0A%23%20System-wide%20Crypto%20policy%3A%0A%23%20This%20system%20is%20following%20system-wide%20crypto%20policy.%20The%20changes%20to%0A%23%20Ciphers%2C%20MACs%2C%20KexAlgoritms%20and%20GSSAPIKexAlgorithsm%20will%20not%20have%20any%0A%23%20effect%20here.%20They%20will%20be%20overridden%20by%20command-line%20options%20passed%20on%0A%23%20the%20server%20start%20up.%0A%23%20To%20opt%20out%2C%20uncomment%20a%20line%20with%20redefinition%20of%20%20CRYPTO_POLICY%3D%0A%23%20variable%20in%20%20%2Fetc%2Fsysconfig%2Fsshd%20%20to%20overwrite%20the%20policy.%0A%23%20For%20more%20information%2C%20see%20manual%20page%20for%20update-crypto-policies%288%29.%0A%0A%23%20Logging%0A%23SyslogFacility%20AUTH%0ASyslogFacility%20AUTHPRIV%0A%23LogLevel%20INFO%0A%0A%23%20Authentication%3A%0A%0A%23LoginGraceTime%202m%0APermitRootLogin%20no%0AStrictModes%20yes%0A%23MaxAuthTries%206%0A%23MaxSessions%2010%0A%0APubkeyAuthentication%20yes%0A%0A%23%20The%20default%20is%20to%20check%20both%20.ssh%2Fauthorized_keys%20and%20.ssh%2Fauthorized_keys2%0A%23%20but%20this%20is%20overridden%20so%20installations%20will%20only%20check%20.ssh%2Fauthorized_keys%0AAuthorizedKeysFile%09.ssh%2Fauthorized_keys%0A%0A%23AuthorizedPrincipalsFile%20none%0A%0A%23AuthorizedKeysCommand%20none%0A%23AuthorizedKeysCommandUser%20nobody%0A%0A%23%20For%20this%20to%20work%20you%20will%20also%20need%20host%20keys%20in%20%2Fetc%2Fssh%2Fssh_known_hosts%0AHostbasedAuthentication%20no%0A%23%20Change%20to%20yes%20if%20you%20don%27t%20trust%20~%2F.ssh%2Fknown_hosts%20for%0A%23%20HostbasedAuthentication%0AIgnoreUserKnownHosts%20yes%0A%23%20Don%27t%20read%20the%20user%27s%20~%2F.rhosts%20and%20~%2F.shosts%20files%0AIgnoreRhosts%20yes%0A%0A%23%20To%20disable%20tunneled%20clear%20text%20passwords%2C%20change%20to%20no%20here%21%0A%23PasswordAuthentication%20yes%0APermitEmptyPasswords%20no%0APasswordAuthentication%20no%0A%0A%23%20Change%20to%20no%20to%20disable%20s%2Fkey%20passwords%0A%23ChallengeResponseAuthentication%20yes%0AChallengeResponseAuthentication%20no%0A%0A%23%20Kerberos%20options%0AKerberosAuthentication%20no%0A%23KerberosOrLocalPasswd%20yes%0A%23KerberosTicketCleanup%20yes%0A%23KerberosGetAFSToken%20no%0A%23KerberosUseKuserok%20yes%0A%0A%23%20GSSAPI%20options%0AGSSAPIAuthentication%20no%0AGSSAPICleanupCredentials%20no%0A%23GSSAPIStrictAcceptorCheck%20yes%0A%23GSSAPIKeyExchange%20no%0A%23GSSAPIEnablek5users%20no%0A%0A%23%20Set%20this%20to%20%27yes%27%20to%20enable%20PAM%20authentication%2C%20account%20processing%2C%0A%23%20and%20session%20processing.%20If%20this%20is%20enabled%2C%20PAM%20authentication%20will%0A%23%20be%20allowed%20through%20the%20ChallengeResponseAuthentication%20and%0A%23%20PasswordAuthentication.%20%20Depending%20on%20your%20PAM%20configuration%2C%0A%23%20PAM%20authentication%20via%20ChallengeResponseAuthentication%20may%20bypass%0A%23%20the%20setting%20of%20%22PermitRootLogin%20without-password%22.%0A%23%20If%20you%20just%20want%20the%20PAM%20account%20and%20session%20checks%20to%20run%20without%0A%23%20PAM%20authentication%2C%20then%20enable%20this%20but%20set%20PasswordAuthentication%0A%23%20and%20ChallengeResponseAuthentication%20to%20%27no%27.%0A%23%20WARNING%3A%20%27UsePAM%20no%27%20is%20not%20supported%20in%20Fedora%20and%20may%20cause%20several%0A%23%20problems.%0AUsePAM%20yes%0A%0A%23AllowAgentForwarding%20yes%0A%23AllowTcpForwarding%20yes%0A%23GatewayPorts%20no%0AX11Forwarding%20yes%0A%23X11DisplayOffset%2010%0A%23X11UseLocalhost%20yes%0A%23PermitTTY%20yes%0A%0A%23%20It%20is%20recommended%20to%20use%20pam_motd%20in%20%2Fetc%2Fpam.d%2Fsshd%20instead%20of%20PrintMotd%2C%0A%23%20as%20it%20is%20more%20configurable%20and%20versatile%20than%20the%20built-in%20version.%0APrintMotd%20no%0A%0APrintLastLog%20yes%0A%23TCPKeepAlive%20yes%0APermitUserEnvironment%20no%0ACompression%20no%0AClientAliveInterval%20600%0AClientAliveCountMax%200%0A%23UseDNS%20no%0A%23PidFile%20%2Fvar%2Frun%2Fsshd.pid%0A%23MaxStartups%2010%3A30%3A100%0A%23PermitTunnel%20no%0A%23ChrootDirectory%20none%0A%23VersionAddendum%20none%0A%0A%23%20no%20default%20banner%20path%0ABanner%20%2Fetc%2Fissue%0A%0A%23%20Accept%20locale-related%20environment%20variables%0AAcceptEnv%20LANG%20LC_CTYPE%20LC_NUMERIC%20LC_TIME%20LC_COLLATE%20LC_MONETARY%20LC_MESSAGES%0AAcceptEnv%20LC_PAPER%20LC_NAME%20LC_ADDRESS%20LC_TELEPHONE%20LC_MEASUREMENT%0AAcceptEnv%20LC_IDENTIFICATION%20LC_ALL%20LANGUAGE%0AAcceptEnv%20XMODIFIERS%0A%0A%23%20override%20default%20of%20no%20subsystems%0ASubsystem%09sftp%09%2Fusr%2Flibexec%2Fopenssh%2Fsftp-server%0A%0A%23%20Example%20of%20overriding%20settings%20on%20a%20per-user%20basis%0A%23Match%20User%20anoncvs%0A%23%09X11Forwarding%20no%0A%23%09AllowTcpForwarding%20no%0A%23%09PermitTTY%20no%0A%23%09ForceCommand%20cvs%20server%0A%0AUsePrivilegeSeparation%20sandbox
            mode: 0600
            path: /etc/ssh/sshd_config
            overwrite: true
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    metadata:
      annotations:
        complianceascode.io/ocp-version: '>=4.13.0'
    spec:
      config:
        ignition:
          version: 3.1.0
        storage:
          files:
          - contents:
              source: data:,{{ HostbasedAuthentication%20no }}
            mode: 0600
            path: /etc/ssh/sshd_config.d/00-complianceascode-disable_host_auth.conf
            overwrite: true
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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
bsi | SYS.1.3.A8
cis | 5.1.19
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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 SSH Forwarding [ref]
The DisableForwarding parameter disables all forwarding features, including X11, ssh-agent(1), TCP and StreamLocal. This option overrides all other forwarding-related options and may simplify restricted configurations. To explicitly disable SSHD forwarding, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    DisableForwarding yes
Rationale: | Disable ssh forwarding unless there is an operational requirement to use it. Leaving port forwarding enabled can expose the organization to security risks.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_forwarding
References: |
cis | 5.1.8
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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*DisableForwarding\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*DisableForwarding\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*DisableForwarding\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' "DisableForwarding yes" > "/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:
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_forwarding

    - name: Disable SSH Forwarding - Check if the parameter DisableForwarding is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "DisableForwarding"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_forwarding

    - name: Disable SSH Forwarding - Check if the parameter DisableForwarding is configured
        correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "DisableForwarding"| regex_escape }}\s+yes$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_forwarding

    - name: Disable SSH Forwarding
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "DisableForwarding"| 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 DisableForwarding is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "DisableForwarding"| 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*{{ "DisableForwarding"| 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*{{ "DisableForwarding"| regex_escape }}\s+
          line: DisableForwarding yes
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_forwarding

    - name: Disable SSH Forwarding - 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-core" in ansible_facts.packages'
      tags:
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_forwarding
#### 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
cis | 5.1.9
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - 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-core" in ansible_facts.packages'
      tags:
      - 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 SSH Support for .rhosts Files [ref]
SSH can emulate the behavior of the obsolete rsh command in allowing users to enable insecure access to their accounts via `.rhosts` files. The default SSH configuration disables support for `.rhosts`. The appropriate configuration is used if no value is set for `IgnoreRhosts`. To explicitly disable support for .rhosts files, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    IgnoreRhosts yes
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_sshd_disable_rhosts
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
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
nist | 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
os-srg | SRG-OS-000480-GPOS-00227
ism | 1546
pcidss4 | 2.2.6, 2.2
cis | 5.1.11
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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*IgnoreRhosts\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*IgnoreRhosts\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*IgnoreRhosts\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' "IgnoreRhosts yes" > "/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
      - 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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_rhosts

    - name: Disable SSH Support for .rhosts Files - Check if the parameter IgnoreRhosts
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_rhosts

    - name: Disable SSH Support for .rhosts Files - Check if the parameter IgnoreRhosts
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "IgnoreRhosts"| regex_escape }}\s+yes$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_rhosts

    - name: Disable SSH Support for .rhosts Files
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "IgnoreRhosts"| 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 IgnoreRhosts is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "IgnoreRhosts"| 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*{{ "IgnoreRhosts"| 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*{{ "IgnoreRhosts"| regex_escape }}\s+
          line: IgnoreRhosts yes
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_rhosts

    - name: Disable SSH Support for .rhosts Files - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_disable_rhosts
#### 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
cis | 5.1.20
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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 Do Not Allow SSH Environment Options [ref]
Ensure that users are not able to override environment variables of the SSH daemon. The default SSH configuration disables environment processing. The appropriate configuration is used if no value is set for `PermitUserEnvironment`. To explicitly disable Environment options, add or correct the following `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    PermitUserEnvironment no
Rationale: | SSH environment options potentially allow users to bypass access restriction in some configurations.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_do_not_permit_user_env
References: |
cis-csc | 11, 3, 9
cjis | 5.5.6
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
pcidss | Req-2.2.4
os-srg | SRG-OS-000480-GPOS-00229
ism | 1546
pcidss4 | 2.2.6, 2.2
cis | 5.1.21
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*PermitUserEnvironment\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*PermitUserEnvironment\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' "PermitUserEnvironment 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
      - 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)
      - 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_do_not_permit_user_env

    - name: Do Not Allow SSH Environment Options - Check if the parameter PermitUserEnvironment
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - 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_do_not_permit_user_env

    - name: Do Not Allow SSH Environment Options - Check if the parameter PermitUserEnvironment
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "PermitUserEnvironment"| regex_escape }}\s+no$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - 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_do_not_permit_user_env

    - name: Do Not Allow SSH Environment Options
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "PermitUserEnvironment"| 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 PermitUserEnvironment is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "PermitUserEnvironment"| 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*{{ "PermitUserEnvironment"| 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*{{ "PermitUserEnvironment"| regex_escape }}\s+
          line: PermitUserEnvironment no
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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)
      - 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_do_not_permit_user_env

    - name: Do Not Allow SSH Environment Options - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - 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_do_not_permit_user_env
#### Rule Enable PAM [ref]
UsePAM Enables the Pluggable Authentication Module interface. If set to “yes” this will enable PAM authentication using ChallengeResponseAuthentication and PasswordAuthentication in addition to PAM account and session module processing for all authentication types. To enable PAM authentication, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    UsePAM yes
Rationale: | When UsePAM is set to yes, PAM runs through account and session types properly. This is important if you want to restrict access to services based off of IP, time or other factors of the account. Additionally, you can make sure users inherit certain environment variables on login or disallow access to the server.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_enable_pam
References: |
os-srg | SRG-OS-000125-GPOS-00065
pcidss4 | 2.2.6, 2.2
cis | 5.1.22
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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*UsePAM\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*UsePAM\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*UsePAM\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' "UsePAM yes" > "/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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_pam

    - name: Enable PAM - Check if the parameter UsePAM is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "UsePAM"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_pam

    - name: Enable PAM - Check if the parameter UsePAM is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "UsePAM"| regex_escape }}\s+yes$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_pam

    - name: Enable PAM
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "UsePAM"| 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 UsePAM is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "UsePAM"| 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*{{ "UsePAM"| 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*{{ "UsePAM"| regex_escape }}\s+
          line: UsePAM yes
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_pam

    - name: Enable PAM - 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-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_pam
#### 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.net
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_net
References: |
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
os-srg | SRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis | 5.1.5
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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.net" > "/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
      - 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)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner_net

    - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner_net

    - 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.net$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner_net

    - 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.net
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - CJIS-5.5.6
      - 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)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner_net

    - 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-core" in ansible_facts.packages'
      tags:
      - CJIS-5.5.6
      - 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)
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_enable_warning_banner_net
#### Rule Limit Users' SSH Access [ref]
By default, the SSH configuration allows any user with an account to access the system. There are several options available to limit which users and group can access the system via SSH. It is recommended that at least one of the following options be leveraged: - AllowUsers variable gives the system administrator the option of allowing specific users to ssh into the system. The list consists of space separated user names. Numeric user IDs are not recognized with this variable. If a system administrator wants to restrict user access further by specifically allowing a user's access only from a particular host, the entry can be specified in the form of user@host. - AllowGroups variable gives the system administrator the option of allowing specific groups of users to ssh into the system. The list consists of space separated group names. Numeric group IDs are not recognized with this variable. - DenyUsers variable gives the system administrator the option of denying specific users to ssh into the system. The list consists of space separated user names. Numeric user IDs are not recognized with this variable. If a system administrator wants to restrict user access further by specifically denying a user's access from a particular host, the entry can be specified in the form of user@host. - DenyGroups variable gives the system administrator the option of denying specific groups of users to ssh into the system. The list consists of space separated group names. Numeric group IDs are not recognized with this variable. Warning: Automated remediation is not available for this configuration check because each system has unique user names and group names.
Rationale: | Specifying which accounts are allowed SSH access into the system reduces the possibility of unauthorized access to the system.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_limit_user_access
References: |
cis-csc | 11, 12, 14, 15, 16, 18, 3, 5
cobit5 | DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui | 3.1.12
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.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, CM-6(a)
nist-csf | PR.AC-4, PR.AC-6, PR.PT-3
pcidss | Req-2.2.4
pcidss4 | 2.2.6, 2.2
cis | 5.1.4
#### Rule Ensure SSH LoginGraceTime is configured [ref]
The `LoginGraceTime` parameter to the SSH server specifies the time allowed for successful authentication to the SSH server. The longer the Grace period is the more open unauthenticated connections can exist. Like other session controls in this session the Grace Period should be limited to appropriate limits to ensure the service is available for needed access.
Rationale: | Setting the `LoginGraceTime` parameter to a low number will minimize the risk of successful brute force attacks to the SSH server. It will also limit the number of concurrent unauthenticated connections.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_login_grace_time
References: |
pcidss4 | 2.2.6, 2.2
cis | 5.1.13
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_sshd_set_login_grace_time='60'

    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*LoginGraceTime\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*LoginGraceTime\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*LoginGraceTime\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' "LoginGraceTime $var_sshd_set_login_grace_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: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_login_grace_time
    - name: XCCDF Value var_sshd_set_login_grace_time # promote to variable
      set_fact:
        var_sshd_set_login_grace_time: !!str 60
      tags:
        - always

    - name: Ensure SSH LoginGraceTime is configured - Check if the parameter LoginGraceTime
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "LoginGraceTime"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_login_grace_time

    - name: Ensure SSH LoginGraceTime is configured - Check if the parameter LoginGraceTime
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "LoginGraceTime"| regex_escape }}\s+{{ var_sshd_set_login_grace_time
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_login_grace_time

    - name: Ensure SSH LoginGraceTime is configured
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "LoginGraceTime"| 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 LoginGraceTime is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "LoginGraceTime"| 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*{{ "LoginGraceTime"| 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*{{ "LoginGraceTime"| regex_escape }}\s+
          line: LoginGraceTime {{ var_sshd_set_login_grace_time }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_login_grace_time

    - name: Ensure SSH LoginGraceTime is configured - 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-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_login_grace_time
#### Rule Set SSH Daemon LogLevel to VERBOSE [ref]
The `VERBOSE` parameter configures the SSH daemon to record login and logout activity. To specify the log level in SSH, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:
    LogLevel VERBOSE
Rationale: | SSH provides several logging levels with varying amounts of verbosity. `DEBUG` is specifically not recommended other than strictly for debugging SSH communications since it provides so much data that it is difficult to identify important security information. `INFO` or `VERBOSE` level is the basic level that only records login activity of SSH users. In many situations, such as Incident Response, it is important to determine when a particular user was active on a system. The logout record can eliminate those users who disconnected, which helps narrow the field.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_loglevel_verbose
References: |
nerc-cip | CIP-007-3 R7.1
nist | AC-17(a), AC-17(1), CM-6(a)
pcidss | Req-2.2.4
os-srg | SRG-OS-000032-GPOS-00013
pcidss4 | 2.2.6, 2.2
cis | 5.1.14
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    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*LogLevel\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*LogLevel\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*LogLevel\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' "LogLevel VERBOSE" > "/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:
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - 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_set_loglevel_verbose

    - name: Set SSH Daemon LogLevel to VERBOSE - Check if the parameter LogLevel is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "LogLevel"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - 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_set_loglevel_verbose

    - name: Set SSH Daemon LogLevel to VERBOSE - Check if the parameter LogLevel is configured
        correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "LogLevel"| regex_escape }}\s+VERBOSE$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - 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_set_loglevel_verbose

    - name: Set SSH Daemon LogLevel to VERBOSE
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "LogLevel"| 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 LogLevel is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "LogLevel"| 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*{{ "LogLevel"| 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*{{ "LogLevel"| regex_escape }}\s+
          line: LogLevel VERBOSE
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - 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_set_loglevel_verbose

    - name: Set SSH Daemon LogLevel to VERBOSE - 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-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(1)
      - NIST-800-53-AC-17(a)
      - NIST-800-53-CM-6(a)
      - 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_set_loglevel_verbose
#### Rule Set SSH authentication attempt limit [ref]
The `MaxAuthTries` parameter specifies the maximum number of authentication attempts permitted per connection. Once the number of failures reaches half this value, additional failures are logged. to set MaxAUthTries edit `/etc/ssh/sshd_config` as follows:
    MaxAuthTries 4
Rationale: | Setting the MaxAuthTries parameter to a low number will minimize the risk of successful brute force attacks to the SSH server.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_max_auth_tries
References: |
ism | 0421, 0422, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
pcidss4 | 2.2.6, 2.2
cis | 5.1.16
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    sshd_max_auth_tries_value='4'

    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*MaxAuthTries\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*MaxAuthTries\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*MaxAuthTries\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' "MaxAuthTries $sshd_max_auth_tries_value" > "/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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_auth_tries
    - name: XCCDF Value sshd_max_auth_tries_value # promote to variable
      set_fact:
        sshd_max_auth_tries_value: !!str 4
      tags:
        - always

    - name: Set SSH authentication attempt limit - Check if the parameter MaxAuthTries
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_auth_tries

    - name: Set SSH authentication attempt limit - Check if the parameter MaxAuthTries
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxAuthTries"| regex_escape }}\s+{{ sshd_max_auth_tries_value
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_auth_tries

    - name: Set SSH authentication attempt limit
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "MaxAuthTries"| 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 MaxAuthTries is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "MaxAuthTries"| 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*{{ "MaxAuthTries"| 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*{{ "MaxAuthTries"| regex_escape }}\s+
          line: MaxAuthTries {{ sshd_max_auth_tries_value }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_auth_tries

    - name: Set SSH authentication attempt limit - 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-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_auth_tries
#### Rule Set SSH MaxSessions limit [ref]
The `MaxSessions` parameter specifies the maximum number of open sessions permitted from a given connection. To set MaxSessions edit `/etc/ssh/sshd_config` as follows:
    MaxSessions 10
Rationale: | To protect a system from denial of service due to a large number of concurrent sessions, use the rate limiting function of MaxSessions to protect availability of sshd logins and prevent overwhelming the daemon.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_max_sessions
References: |
pcidss4 | 2.2.6, 2.2
cis | 5.1.18
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_sshd_max_sessions='10'

    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*MaxSessions\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*MaxSessions\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*MaxSessions\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' "MaxSessions $var_sshd_max_sessions" > "/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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_sessions
    - name: XCCDF Value var_sshd_max_sessions # promote to variable
      set_fact:
        var_sshd_max_sessions: !!str 10
      tags:
        - always

    - name: Set SSH MaxSessions limit - Check if the parameter MaxSessions is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxSessions"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_sessions

    - name: Set SSH MaxSessions limit - Check if the parameter MaxSessions is configured
        correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxSessions"| regex_escape }}\s+{{ var_sshd_max_sessions
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_sessions

    - name: Set SSH MaxSessions limit
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "MaxSessions"| 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 MaxSessions is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "MaxSessions"| 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*{{ "MaxSessions"| 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*{{ "MaxSessions"| regex_escape }}\s+
          line: MaxSessions {{ var_sshd_max_sessions }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_sessions

    - name: Set SSH MaxSessions limit - 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-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_max_sessions
#### Rule Ensure SSH MaxStartups is configured [ref]
The MaxStartups parameter specifies the maximum number of concurrent unauthenticated connections to the SSH daemon. Additional connections will be dropped until authentication succeeds or the LoginGraceTime expires for a connection. To configure MaxStartups, you should add or edit the following line in the `/etc/ssh/sshd_config` file:
    MaxStartups 10:30:60
Rationale: | To protect a system from denial of service due to a large number of pending authentication connection attempts, use the rate limiting function of MaxStartups to protect availability of sshd logins and prevent overwhelming the daemon.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_maxstartups
References: |
pcidss4 | 2.2.6, 2.2
cis | 5.1.17
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    var_sshd_set_maxstartups='10:30:60'

    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*MaxStartups\s\+/Id" "/etc/ssh/sshd_config"
    LC_ALL=C sed -i "/^\s*MaxStartups\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*MaxStartups\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' "MaxStartups $var_sshd_set_maxstartups" > "/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:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_maxstartups
    - name: XCCDF Value var_sshd_set_maxstartups # promote to variable
      set_fact:
        var_sshd_set_maxstartups: !!str 10:30:60
      tags:
        - always

    - name: Ensure SSH MaxStartups is configured - Check if the parameter MaxStartups
        is configured
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxStartups"| regex_escape }}\s+
      register: _sshd_config_has_parameter
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_maxstartups

    - name: Ensure SSH MaxStartups is configured - Check if the parameter MaxStartups
        is configured correctly
      ansible.builtin.find:
        paths:
        - /etc/ssh/sshd_config
        - /etc/ssh/sshd_config.d
        contains: (?i)^\s*{{ "MaxStartups"| regex_escape }}\s+{{ var_sshd_set_maxstartups
          }}$
      register: _sshd_config_correctly
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_maxstartups

    - name: Ensure SSH MaxStartups is configured
      block:

      - name: Deduplicate values from /etc/ssh/sshd_config
        ansible.builtin.lineinfile:
          path: /etc/ssh/sshd_config
          create: false
          regexp: (?i)(?i)^\s*{{ "MaxStartups"| 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 MaxStartups is present in /etc/ssh/sshd_config.d
        ansible.builtin.find:
          paths: /etc/ssh/sshd_config.d
          recurse: 'yes'
          follow: 'no'
          contains: (?i)^\s*{{ "MaxStartups"| 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*{{ "MaxStartups"| 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*{{ "MaxStartups"| regex_escape }}\s+
          line: MaxStartups {{ var_sshd_set_maxstartups }}
          state: present
          insertbefore: BOF
          validate: /usr/sbin/sshd -t -f %s
      when:
      - '"kernel-core" in ansible_facts.packages'
      - _sshd_config_correctly.matched == 0 or _sshd_config_has_parameter.matched != 1
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_maxstartups

    - name: Ensure SSH MaxStartups is configured - 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-core" in ansible_facts.packages'
      tags:
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
      - restrict_strategy
      - sshd_set_maxstartups
#### Rule Verify Group Who Owns SSH Server Configuration Files [ref]
To properly set the group owner of `/etc/ssh/sshd_config.d`, run the command:
    $ sudo chgrp root /etc/ssh/sshd_config.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_directory_groupowner_sshd_config_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/ssh/sshd_config.d/ -maxdepth 0 -type d  ! -group 0 -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_groupowner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the directory_groupowner_sshd_config_d_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        directory_groupowner_sshd_config_d_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_groupowner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/ssh/sshd_config.d/
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/
        follow: false
        state: directory
        group: '{{ directory_groupowner_sshd_config_d_newgroup }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_groupowner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on SSH Server Configuration Files [ref]
To properly set the owner of `/etc/ssh/sshd_config.d`, run the command:
    $ sudo chown root /etc/ssh/sshd_config.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_directory_owner_sshd_config_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    find -P /etc/ssh/sshd_config.d/ -maxdepth 0 -type d  ! -user 0 -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_owner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the directory_owner_sshd_config_d_newown variable if represented by uid
      ansible.builtin.set_fact:
        directory_owner_sshd_config_d_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_owner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on directory /etc/ssh/sshd_config.d/
      ansible.builtin.file:
        path: /etc/ssh/sshd_config.d/
        follow: false
        state: directory
        owner: '{{ directory_owner_sshd_config_d_newown }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_owner_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on SSH Server Config File [ref]
To properly set the permissions of `/etc/ssh/sshd_config.d`, run the command:
    $ sudo chmod 0700 /etc/ssh/sshd_config.d
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_directory_permissions_sshd_config_d
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -H /etc/ssh/sshd_config.d/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt -type d -exec chmod u-s,g-xwrs,o-xwrt {} \;

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_permissions_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/sshd_config.d/ file(s)
      ansible.builtin.command: 'find -P /etc/ssh/sshd_config.d/ -maxdepth 0 -perm /u+s,g+xwrs,o+xwrt  -type
        d '
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_permissions_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/ssh/sshd_config.d/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-s,g-xwrs,o-xwrt
        state: directory
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - directory_permissions_sshd_config_d
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns SSH Server config file [ref]
To properly set the group owner of `/etc/ssh/sshd_config`, run the command:
    $ sudo chgrp root /etc/ssh/sshd_config
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_sshd_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    if ! stat -c "%g %G" "/etc/ssh/sshd_config" | grep -E -w -q "0"; then
        chgrp --no-dereference "$newgroup" /etc/ssh/sshd_config
    fi

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_sshd_config_newgroup variable if represented by gid
      ansible.builtin.set_fact:
        file_groupowner_sshd_config_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/ssh/sshd_config
      ansible.builtin.stat:
        path: /etc/ssh/sshd_config
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/ssh/sshd_config
      ansible.builtin.file:
        path: /etc/ssh/sshd_config
        follow: false
        group: '{{ file_groupowner_sshd_config_newgroup }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Who Owns SSH Server Configuration Files [ref]
To properly set the group owner of files in `/etc/ssh/sshd_config.d`, run the command:
    find -H /etc/ssh/sshd_config.d -type d -exec chgrp -L root {} \;
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupowner_sshd_drop_in_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -type f  ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupowner_sshd_drop_in_config_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupowner_sshd_drop_in_config_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/sshd_config.d/ file(s) matching ^.*$
      ansible.builtin.command: find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -type f  !
        -group 0 -regextype posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/ssh/sshd_config.d/ file(s) matching ^.*$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        group: '{{ file_groupowner_sshd_drop_in_config_newgroup }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_groupowner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Ownership on SSH Server Private *_key Key Files [ref]
SSH server private keys, files that match the `/etc/ssh/*_key` glob, must be group-owned by `root` group. Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupownership_sshd_private_key
References: |
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/ssh/ -maxdepth 1 -type f  ! -group 0 -regextype posix-extended -regex '^.*_key$' -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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:
      - configure_strategy
      - file_groupownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupownership_sshd_private_key_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupownership_sshd_private_key_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/ file(s) matching ^.*_key$
      ansible.builtin.command: find -P /etc/ssh/ -maxdepth 1 -type f  ! -group 0 -regextype
        posix-extended -regex "^.*_key$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/ssh/ file(s) matching ^.*_key$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        group: '{{ file_groupownership_sshd_private_key_newgroup }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Group Ownership on SSH Server Public *.pub Key Files [ref]
SSH server public keys, files that match the `/etc/ssh/*.pub` glob, must be group-owned by `root` group. Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_groupownership_sshd_pub_key
References: |
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newgroup=""
    if getent group "0" >/dev/null 2>&1; then
      newgroup="0"
    fi

    if [[ -z "${newgroup}" ]]; then
      >&2 echo "0 is not a defined group on the system"
    else
    find -P /etc/ssh/ -maxdepth 1 -type f  ! -group 0 -regextype posix-extended -regex '^.*\.pub$' -exec chgrp --no-dereference "$newgroup" {} \;

    fi

    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:
      - configure_strategy
      - file_groupownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_groupownership_sshd_pub_key_newgroup variable if represented
        by gid
      ansible.builtin.set_fact:
        file_groupownership_sshd_pub_key_newgroup: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/ file(s) matching ^.*\.pub$
      ansible.builtin.command: find -P /etc/ssh/ -maxdepth 1 -type f  ! -group 0 -regextype
        posix-extended -regex "^.*\.pub$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure group owner on /etc/ssh/ file(s) matching ^.*\.pub$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        group: '{{ file_groupownership_sshd_pub_key_newgroup }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_groupownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on SSH Server config file [ref]
To properly set the owner of `/etc/ssh/sshd_config`, run the command:
    $ sudo chown root /etc/ssh/sshd_config
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_sshd_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else
    if ! stat -c "%u %U" "/etc/ssh/sshd_config" | grep -E -w -q "0"; then
        chown --no-dereference "$newown" /etc/ssh/sshd_config
    fi

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_sshd_config_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_sshd_config_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/ssh/sshd_config
      ansible.builtin.stat:
        path: /etc/ssh/sshd_config
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/ssh/sshd_config
      ansible.builtin.file:
        path: /etc/ssh/sshd_config
        follow: false
        owner: '{{ file_owner_sshd_config_newown }}'
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Owner on SSH Server Configuration Files [ref]
To properly set the owner of files in `/etc/ssh/sshd_config.d`, run the command:
    find -H /etc/ssh/sshd_config.d -type d -exec chown -L root {} \;
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_owner_sshd_drop_in_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else

    find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -type f  ! -user 0 -regextype posix-extended -regex '^.*$' -exec chown --no-dereference "$newown" {} \;

    fi

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_owner_sshd_drop_in_config_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_owner_sshd_drop_in_config_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/sshd_config.d/ file(s) matching ^.*$
      ansible.builtin.command: find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -type f  !
        -user 0 -regextype posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/ssh/sshd_config.d/ file(s) matching ^.*$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        owner: '{{ file_owner_sshd_drop_in_config_newown }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_owner_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Ownership on SSH Server Private *_key Key Files [ref]
SSH server private keys, files that match the `/etc/ssh/*_key` glob, must be owned by `root` user. Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_ownership_sshd_private_key
References: |
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.2
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else

    find -P /etc/ssh/ -maxdepth 1 -type f  ! -user 0 -regextype posix-extended -regex '^.*_key$' -exec chown --no-dereference "$newown" {} \;

    fi

    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:
      - configure_strategy
      - file_ownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_ownership_sshd_private_key_newown variable if represented by
        uid
      ansible.builtin.set_fact:
        file_ownership_sshd_private_key_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/ file(s) matching ^.*_key$
      ansible.builtin.command: find -P /etc/ssh/ -maxdepth 1 -type f  ! -user 0 -regextype
        posix-extended -regex "^.*_key$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/ssh/ file(s) matching ^.*_key$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        owner: '{{ file_ownership_sshd_private_key_newown }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Ownership on SSH Server Public *.pub Key Files [ref]
SSH server public keys, files that match the `/etc/ssh/*.pub` glob, must be owned by `root` user. Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_ownership_sshd_pub_key
References: |
anssi | R50
bsi | SYS.1.3.A14
cis | 5.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    newown=""
    if id "0" >/dev/null 2>&1; then
      newown="0"
    fi

    if [[ -z "$newown" ]]; then
      >&2 echo "0 is not a defined user on the system"
    else

    find -P /etc/ssh/ -maxdepth 1 -type f  ! -user 0 -regextype posix-extended -regex '^.*\.pub$' -exec chown --no-dereference "$newown" {} \;

    fi

    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:
      - configure_strategy
      - file_ownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set the file_ownership_sshd_pub_key_newown variable if represented by uid
      ansible.builtin.set_fact:
        file_ownership_sshd_pub_key_newown: '0'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/ file(s) matching ^.*\.pub$
      ansible.builtin.command: find -P /etc/ssh/ -maxdepth 1 -type f  ! -user 0 -regextype
        posix-extended -regex "^.*\.pub$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure owner on /etc/ssh/ file(s) matching ^.*\.pub$
      ansible.builtin.file:
        path: '{{ item }}'
        follow: false
        owner: '{{ file_ownership_sshd_pub_key_newown }}'
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - configure_strategy
      - file_ownership_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on SSH Server config file [ref]
To properly set the permissions of `/etc/ssh/sshd_config`, run the command:
    $ sudo chmod 0600 /etc/ssh/sshd_config
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_sshd_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    chmod u-xs,g-xwrs,o-xwrt /etc/ssh/sshd_config

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Test for existence /etc/ssh/sshd_config
      ansible.builtin.stat:
        path: /etc/ssh/sshd_config
      register: file_exists
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Ensure permission u-xs,g-xwrs,o-xwrt on /etc/ssh/sshd_config
      ansible.builtin.file:
        path: /etc/ssh/sshd_config
        mode: u-xs,g-xwrs,o-xwrt
      when:
      - '"kernel-core" in ansible_facts.packages'
      - file_exists.stat is defined and file_exists.stat.exists
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on SSH Server Config File [ref]
To properly set the permissions of files in `/etc/ssh/sshd_config.d`, run the command:
    find -H /etc/ssh/sshd_config.d -type d -exec chown 0600 {} \;
Rationale: | Service configuration files enable or disable features of their respective services that if configured incorrectly can lead to insecure and vulnerable configurations. Therefore, service configuration files should be owned by the correct group to prevent unauthorized changes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_sshd_drop_in_config
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
os-srg | SRG-OS-000480-GPOS-00227
bsi | SYS.1.3.A14
cis | 5.1.1
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt  -type f -regextype posix-extended -regex '^.*$' -exec chmod u-xs,g-xwrs,o-xwrt {} \;

    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-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/sshd_config.d/ file(s)
      ansible.builtin.command: find -P /etc/ssh/sshd_config.d/ -maxdepth 1 -perm /u+xs,g+xwrs,o+xwrt  -type
        f -regextype posix-extended -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/ssh/sshd_config.d/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-xs,g-xwrs,o-xwrt
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - configure_strategy
      - file_permissions_sshd_drop_in_config
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
#### Rule Verify Permissions on SSH Server Private *_key Key Files [ref]
SSH server private keys - files that match the `/etc/ssh/*_key` glob, have to have restricted permissions. If those files are owned by the `root` user and the `root` group, they have to have the `0600` permission or stricter. Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If an unauthorized user obtains the private SSH host key file, the host could be impersonated.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_sshd_private_key
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.13, 3.13.10
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
pcidss | Req-2.2.4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
ism | 1449
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 5.1.2
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    for keyfile in /etc/ssh/*_key; do
        test -f "$keyfile" || continue
        if test root:root = "$(stat -c "%U:%G" "$keyfile")"; then

    	chmod u-xs,g-xwrs,o-xwrt "$keyfile"

        else
            echo "Key-like file '$keyfile' is owned by an unexpected user:group combination"
        fi
    done

    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-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find root:root-owned keys
      ansible.builtin.command: find -H /etc/ssh/ -maxdepth 1 -user root -regex ".*_key$"
        -type f -group root -perm /u+xs,g+xwrs,o+xwrt
      register: root_owned_keys
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for root:root-owned keys
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-xs,g-xwrs,o-xwrt
        state: file
      with_items:
      - '{{ root_owned_keys.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_private_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Remediation Puppet snippet ⇲
    include ssh_private_key_perms

    class ssh_private_key_perms {
      exec { 'sshd_priv_key':
        command => "chmod 0640 /etc/ssh/*_key",
        path    => '/bin:/usr/bin'
      }
    }
#### Rule Verify Permissions on SSH Server Public *.pub Key Files [ref]
To properly set the permissions of `/etc/ssh/*.pub`, run the command:
    $ sudo chmod 0644 /etc/ssh/*.pub
Warning: Remediation is not possible at bootable container build time because SSH host keys are generated post-deployment.
Rationale: | If a public host key file is modified by an unauthorized user, the SSH service may be compromised.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_sshd_pub_key
References: |
cis-csc | 12, 13, 14, 15, 16, 18, 3, 5
cobit5 | APO01.06, DSS05.04, DSS05.07, DSS06.02
cui | 3.1.13, 3.13.10
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 | AC-17(a), CM-6(a), AC-6(1)
nist-csf | PR.AC-4, PR.DS-5
pcidss | Req-2.2.4
os-srg | SRG-OS-000480-GPOS-00227
anssi | R50
pcidss4 | 2.2.6, 2.2
bsi | SYS.1.3.A14
cis | 5.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | false
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q kernel-core; then

    find -P /etc/ssh/ -maxdepth 1 -perm /u+xs,g+xws,o+xwt  -type f -regextype posix-extended -regex '^.*\.pub$' -exec chmod u-xs,g-xws,o-xwt {} \;

    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-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Find /etc/ssh/ file(s)
      ansible.builtin.command: find -P /etc/ssh/ -maxdepth 1 -perm /u+xs,g+xws,o+xwt  -type
        f -regextype posix-extended -regex "^.*\.pub$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed

    - name: Set permissions for /etc/ssh/ file(s)
      ansible.builtin.file:
        path: '{{ item }}'
        mode: u-xs,g-xws,o-xwt
        state: file
      with_items:
      - '{{ files_found.stdout_lines }}'
      when: '"kernel-core" in ansible_facts.packages'
      tags:
      - NIST-800-171-3.1.13
      - NIST-800-171-3.13.10
      - NIST-800-53-AC-17(a)
      - NIST-800-53-AC-6(1)
      - NIST-800-53-CM-6(a)
      - PCI-DSS-Req-2.2.4
      - PCI-DSSv4-2.2
      - PCI-DSSv4-2.2.6
      - configure_strategy
      - file_permissions_sshd_pub_key
      - low_complexity
      - low_disruption
      - medium_severity
      - no_reboot_needed
Remediation Puppet snippet ⇲
    include ssh_public_key_perms

    class ssh_public_key_perms {
      exec { 'sshd_pub_key':
        command => "chmod 0644 /etc/ssh/*.pub",
        path    => '/bin:/usr/bin'
      }
    }

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.