## Guide to the Secure Configuration of Oracle Linux 8

> with profile [DRAFT] Australian Cyber Security Centre (ACSC) Essential Eight
> This profile contains configuration checks for Oracle Linux 8 that align to the Australian Cyber Security Centre (ACSC) Essential Eight. A copy of the Essential Eight in Linux Environments guide can be found at the ACSC website: https://www.cyber.gov.au/acsc/view-all-content/publications/hardening-linux-workstations-and-servers

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

## Profile Information

Profile Title | [DRAFT] Australian Cyber Security Centre (ACSC) Essential Eight
Profile ID | xccdf_org.ssgproject.content_profile_e8

#### CPE Platforms

- cpe:/o:oracle:linux:8

## Revision History

Current version: 0.1.61

- draft (as of 2022-04-01)

## Table of Contents

1. System Settings
   1. Installing and Maintaining Software
   2. Account and Access Control
   3. System Accounting with auditd
   4. Configure Syslog
   5. Network Configuration and Firewalls
   6. File Permissions and Masks
   7. SELinux
2. Services
   1. Avahi Server
   2. Application Whitelisting Daemon
   3. Obsolete Services
   4. Proxy Server
   5. Network Routing
   6. SSH Server

## Checklist

Group Guide to the Secure Configuration of Oracle Linux 8 Group contains 48 groups and 95 rules
Group System Settings Group contains 31 groups and 69 rules
[ref] Contains rules that check correct system settings.
Group Installing and Maintaining Software Group contains 7 groups and 15 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 3 rules
[ref] Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes. The RPM package management system can conduct integrity checks by comparing information in its metadata database with files installed on the system.
Group Verify Integrity with RPM Group contains 3 rules
[ref] The RPM package management system includes the ability to verify the integrity of installed packages by comparing the installed files with information about the files taken from the package metadata stored in the RPM database. Although an attacker could corrupt the RPM database (analogous to attacking the AIDE database as described above), this check can still reveal modification of important files. To list which files on the system differ from what is expected by the RPM database:
    $ rpm -qVa
See the man page for `rpm` to see a complete explanation of each column.
#### Rule Verify File Hashes with RPM [ref]
Without cryptographic integrity protections, system executables and files can be altered by unauthorized users without detection. The RPM package management system can check the hashes of installed software packages, including many that are important to system security. To verify that the cryptographic hash of system files and commands matches vendor values, run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:
    $ rpm -Va --noconfig | grep '^..5'
A "c" in the second column indicates that a file is a configuration file, which may appropriately be expected to change. If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:
    $ rpm -qf FILENAME
The package can be reinstalled from a yum repository using the command:
    $ sudo yum reinstall PACKAGENAME
Alternatively, the package can be reinstalled from trusted media using the command:
    $ sudo rpm -Uvh PACKAGENAME
Rationale: | The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_rpm_verify_hashes
Identifiers and References | References: 11, 2, 3, 9, 5.10.4.1, APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02, 3.3.8, 3.4.1, CCI-000366, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6, 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, CM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3), PR.DS-6, PR.DS-8, PR.IP-1, Req-11.5, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
    # Find which files have incorrect hash (not in /etc, because of the system related config files) and then get files names
    files_with_incorrect_hash="$(rpm -Va --noconfig | grep -E '^..5' | awk '{print $NF}' )"

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

    yum reinstall -y $packages_to_reinstall
Remediation Ansible snippet ⇲
Complexity: | high
Disruption: | medium
Strategy: | restrict
    - name: 'Set fact: Package manager reinstall command (dnf)'
      set_fact:
        package_manager_reinstall_cmd: dnf reinstall -y
      when: ansible_distribution == "Fedora"
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_hashes

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

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

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

    - name: Reinstall packages of files with incorrect hash
      command: '{{ package_manager_reinstall_cmd }} ''{{ item }}'''
      args:
        warn: false
      with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
        | unique }}'
      when:
        - files_with_incorrect_hash.stdout_lines is defined
        - (package_manager_reinstall_cmd is defined and (files_with_incorrect_hash.stdout_lines
          | length > 0))
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_hashes
#### Rule Verify and Correct Ownership with RPM [ref]
The RPM package management system can check file ownership permissions of installed software packages, including many that are important to system security. After locating a file with incorrect permissions, which can be found with
    rpm -Va | awk '{ if (substr($0,6,1)=="U" || substr($0,7,1)=="G") print $NF }'
run the following command to determine which package owns it:
    $ rpm -qf FILENAME
Next, run the following command to reset its permissions to the correct values:
    $ sudo rpm --setugids PACKAGENAME
Warning: Profiles may require that specific files be owned by root while the default owner defined by the vendor is different. Such files will be reported as a finding and need to be evaluated according to your policy and deployment environment.
Rationale: | Ownership of binaries and configuration files that is incorrect could allow an unauthorized user to gain privileges that they should not have. The ownership set by the vendor should be maintained. Any deviations from this baseline should be investigated.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_rpm_verify_ownership
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 18, 3, 5, 6, 9, 5.10.4.1, APO01.06, APO11.04, BAI03.05, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.8, 3.4.1, CCI-001494, CCI-001496, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.5.1, A.12.6.2, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3), PR.AC-4, PR.DS-5, PR.IP-1, PR.PT-1, Req-11.5, SRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098, SRG-OS-000278-GPOS-00108
Remediation Shell script ⇲
Complexity: | high
Disruption: | medium
Strategy: | restrict
    # Declare array to hold set of RPM packages we need to correct permissions for
    declare -A SETPERMS_RPM_DICT

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

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

    # For each of the RPM packages left in the list -- reset its permissions to the
    # correct values
    for RPM_PACKAGE in "${!SETPERMS_RPM_DICT[@]}"
    do
            rpm --setugids "${RPM_PACKAGE}"
    done
Remediation Ansible snippet ⇲
Complexity: | high
Disruption: | medium
Strategy: | restrict
    - name: Read list of files with incorrect ownership
      command: rpm -Va --nodeps --nosignature --nofiledigest --nosize --nomtime --nordev
        --nocaps --nolinkto --nomode
      args:
        warn: false
      register: files_with_incorrect_ownership
      failed_when: files_with_incorrect_ownership.rc > 1
      changed_when: false
      check_mode: false
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_ownership

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

    - name: Correct file ownership with RPM
      command: rpm --quiet --setugids '{{ item }}'
      args:
        warn: false
      with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
        | unique }}'
      when: (files_with_incorrect_ownership.stdout_lines | length > 0)
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_ownership
#### Rule Verify and Correct File Permissions with RPM [ref]
The RPM package management system can check file access permissions of installed software packages, including many that are important to system security. Verify that the file permissions of system files and commands match vendor values. Check the file permissions with the following command:
    $ sudo rpm -Va | awk '{ if (substr($0,2,1)=="M") print $NF }'
Output indicates files that do not match vendor defaults. After locating a file with incorrect permissions, run the following command to determine which package owns it:
    $ rpm -qf FILENAME
Next, run the following command to reset its permissions to the correct values:
    $ sudo rpm --setperms PACKAGENAME
Warning: Profiles may require that specific files have stricter file permissions than defined by the vendor. Such files will be reported as a finding and need to be evaluated according to your policy and deployment environment.
Rationale: | Permissions on system binaries and configuration files that are too generous could allow an unauthorized user to gain privileges that they should not have. The permissions set by the vendor should be maintained. Any deviations from this baseline should be investigated.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_rpm_verify_permissions
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 18, 3, 5, 6, 9, 5.10.4.1, APO01.06, APO11.04, BAI03.05, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.07, DSS06.02, MEA02.01, 3.3.8, 3.4.1, CCI-001493, CCI-001494, CCI-001495, CCI-001496, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), 4.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 7.6, A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.5.1, A.12.6.2, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5, CIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3), CM-6(a), PR.AC-4, PR.DS-5, PR.IP-1, PR.PT-1, Req-11.5, SRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098, SRG-OS-000258-GPOS-00099, SRG-OS-000278-GPOS-00108
Remediation Shell script ⇲
Complexity: | high
Disruption: | medium
Strategy: | restrict
    # Declare array to hold set of RPM packages we need to correct permissions for
    declare -A SETPERMS_RPM_DICT

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

    for FILE_PATH in "${FILES_WITH_INCORRECT_PERMS[@]}"
    do
            # NOTE: some files maybe controlled by more then one package
            readarray -t RPM_PACKAGES < <(rpm -qf "${FILE_PATH}")
            for RPM_PACKAGE in "${RPM_PACKAGES[@]}"
            do
                    # Use an associative array to store packages as it's keys, not having to care about duplicates.
                    SETPERMS_RPM_DICT["$RPM_PACKAGE"]=1
            done
    done

    # For each of the RPM packages left in the list -- reset its permissions to the
    # correct values
    for RPM_PACKAGE in "${!SETPERMS_RPM_DICT[@]}"
    do
    	rpm --restore "${RPM_PACKAGE}"
    done
Remediation Ansible snippet ⇲
Complexity: | high
Disruption: | medium
Strategy: | restrict
    - name: Read list of files with incorrect permissions
      command: rpm -Va --nodeps --nosignature --nofiledigest --nosize --nomtime --nordev
        --nocaps --nolinkto --nouser --nogroup
      args:
        warn: false
      register: files_with_incorrect_permissions
      failed_when: files_with_incorrect_permissions.rc > 1
      changed_when: false
      check_mode: false
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_permissions

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

    - name: Correct file permissions with RPM
      command: rpm --setperms '{{ item }}'
      args:
        warn: false
      with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
        | unique }}'
      when: (files_with_incorrect_permissions.stdout_lines | length > 0)
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.3.8
        - NIST-800-171-3.4.1
        - NIST-800-53-AU-9(3)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-6(c)
        - NIST-800-53-CM-6(d)
        - NIST-800-53-SI-7
        - NIST-800-53-SI-7(1)
        - NIST-800-53-SI-7(6)
        - PCI-DSS-Req-11.5
        - high_complexity
        - high_severity
        - medium_disruption
        - no_reboot_needed
        - restrict_strategy
        - rpm_verify_permissions
Group System Cryptographic Policies Group contains 2 rules
[ref] Linux has the capability to centrally configure cryptographic polices. The command `update-crypto-policies` is used to set the policy applicable for the various cryptographic back-ends, such as SSL/TLS libraries. The configured cryptographic policies will be the default policy used by these backends unless the application user configures them otherwise. When the system has been configured to use the centralized cryptographic policies, the administrator is assured that any application that utilizes the supported backends will follow a policy that adheres to the configured profile. Currently the supported backends are: GnuTLS library OpenSSL library NSS library OpenJDK Libkrb5 BIND OpenSSH Applications and languages which rely on any of these backends will follow the system policies as well. Examples are apache httpd, nginx, php, and others.
#### Rule Configure System Cryptography Policy [ref]
To configure the system cryptography policy to use ciphers only from the `DEFAULT:NO-SHA1` policy, run the following command:
    $ sudo update-crypto-policies --set DEFAULT:NO-SHA1
The rule checks if settings for selected crypto policy are configured as expected. Configuration files in the `/etc/crypto-policies/back-ends` are either symlinks to correct files provided by Crypto-policies package or they are regular files in case crypto policy customizations are applied. Crypto policies may be customized by crypto policy modules, in which case it is delimited from the base policy using a colon. Warning: The system needs to be rebooted for these changes to take effect. Warning: System Crypto Modules must be provided by a vendor that undergoes FIPS-140 certifications. FIPS-140 is applicable to all Federal agencies that use cryptographic-based security systems to protect sensitive information in computer and telecommunication systems (including voice systems) as defined in Section 5131 of the Information Technology Management Reform Act of 1996, Public Law 104-106. This standard shall be used in designing and implementing cryptographic modules that Federal departments and agencies operate or are operated for them under contract. See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf To meet this, the system has to have cryptographic software provided by a vendor that has undergone this certification. This means providing documentation, test results, design information, and independent third party review by an accredited lab. While open source software is capable of meeting this, it does not meet FIPS-140 unless the vendor submits to this process.
Rationale: | Centralized cryptographic policies simplify applying secure ciphers across an operating system and the applications that run on that operating system. Use of weak or untested encryption algorithms undermines the purposes of utilizing encryption to protect data.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_configure_crypto_policy
Identifiers and References | References: 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii), 1446, CIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1, AC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SC-12(2), SC-12(3), FCS_COP.1(1), FCS_COP.1(2), FCS_COP.1(3), FCS_COP.1(4), FCS_CKM.1, FCS_CKM.2, FCS_TLSC_EXT.1, SRG-OS-000396-GPOS-00176, SRG-OS-000393-GPOS-00173, SRG-OS-000394-GPOS-00174, OL08-00-010020, SV-248524r779138_rule
Remediation Shell script ⇲
    var_system_crypto_policy='DEFAULT:NO-SHA1'

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

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

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

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

    - name: Verify that Crypto Policy is Set (runtime)
      command: /usr/bin/update-crypto-policies --set {{ var_system_crypto_policy }}
      tags:
        - DISA-STIG-OL08-00-010020
        - NIST-800-53-AC-17(2)
        - NIST-800-53-AC-17(a)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-MA-4(6)
        - NIST-800-53-SC-12(2)
        - NIST-800-53-SC-12(3)
        - NIST-800-53-SC-13
        - configure_crypto_policy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - restrict_strategy
#### Rule Configure SSH to use System Crypto Policy [ref]
Crypto Policies provide a centralized control over crypto algorithms usage of many packages. SSH is supported by crypto policy, but the SSH configuration may be set up to ignore it. To check that Crypto Policies settings are configured correctly, ensure that the `CRYPTO_POLICY` variable is either commented or not set at all in the `/etc/sysconfig/sshd`.
Rationale: | Overriding the system crypto policy makes the behavior of the SSH service violate expectations, and makes system configuration more fragmented.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_configure_ssh_crypto_policy
Identifiers and References | References: 164.308(a)(4)(i), 164.308(b)(1), 164.308(b)(3), 164.312(e)(1), 164.312(e)(2)(ii), CIP-003-8 R4.2, CIP-007-3 R5.1, CIP-007-3 R7.1, AC-17(a), AC-17(2), CM-6(a), MA-4(6), SC-13, SRG-OS-000250-GPOS-00093, OL08-00-010287, SV-248560r779246_rule
Remediation Shell script ⇲
    SSH_CONF="/etc/sysconfig/sshd"

    sed -i "/^\s*CRYPTO_POLICY.*$/d" $SSH_CONF
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    - name: Configure SSH to use System Crypto Policy
      lineinfile:
        dest: /etc/sysconfig/sshd
        state: absent
        regexp: ^\s*CRYPTO_POLICY.*$
      tags:
        - DISA-STIG-OL08-00-010287
        - NIST-800-53-AC-17(2)
        - NIST-800-53-AC-17(a)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-MA-4(6)
        - NIST-800-53-SC-13
        - configure_ssh_crypto_policy
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
Group Sudo Group contains 3 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 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
Identifiers and References | References: BP28(R5), BP28(R59), 1, 12, 15, 16, 5, DSS05.04, DSS05.10, DSS06.03, DSS06.10, CCI-002038, 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, 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, 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, IA-11, CM-6(a), PR.AC-1, PR.AC-7, SRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158, SRG-OS-000373-VMM-001470, SRG-OS-000373-VMM-001480, SRG-OS-000373-VMM-001490, OL08-00-010381, SV-248582r779312_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    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
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Find /etc/sudoers.d/ files
      find:
        paths:
          - /etc/sudoers.d/
      register: sudoers
      tags:
        - DISA-STIG-OL08-00-010381
        - 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
      replace:
        regexp: (^(?!#).*[\s]+\!authenticate.*$)
        replace: '# \g<1>'
        path: '{{ item.path }}'
        validate: /usr/sbin/visudo -cf %s
      with_items:
        - path: /etc/sudoers
        - '{{ sudoers.files }}'
      tags:
        - DISA-STIG-OL08-00-010381
        - 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 Ensure Users Re-Authenticate for Privilege Escalation - sudo NOPASSWD [ref]
The sudo `NOPASSWD` tag, when specified, allows a user to execute commands using sudo without having to authenticate. This should be disabled by making sure that the `NOPASSWD` tag 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_nopasswd
Identifiers and References | References: BP28(R5), BP28(R59), 1, 12, 15, 16, 5, DSS05.04, DSS05.10, DSS06.03, DSS06.10, CCI-002038, 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, 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, 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, IA-11, CM-6(a), PR.AC-1, PR.AC-7, SRG-OS-000373-GPOS-00156, SRG-OS-000373-GPOS-00157, SRG-OS-000373-GPOS-00158, SRG-OS-000373-VMM-001470, SRG-OS-000373-VMM-001480, SRG-OS-000373-VMM-001490, OL08-00-010380, SV-248581r779309_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    for f in /etc/sudoers /etc/sudoers.d/* ; do
      if [ ! -e "$f" ] ; then
        continue
      fi
      matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          # comment out "NOPASSWD" matches to preserve user data
          sed -i "s/^${entry}$/# &/g" $f
        done <<< "$matching_list"

        /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
      fi
    done
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Find /etc/sudoers.d/ files
      find:
        paths:
          - /etc/sudoers.d/
      register: sudoers
      tags:
        - DISA-STIG-OL08-00-010380
        - NIST-800-53-CM-6(a)
        - NIST-800-53-IA-11
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sudo_remove_nopasswd

    - name: Remove lines containing NOPASSWD from sudoers files
      replace:
        regexp: (^(?!#).*[\s]+NOPASSWD[\s]*\:.*$)
        replace: '# \g<1>'
        path: '{{ item.path }}'
        validate: /usr/sbin/visudo -cf %s
      with_items:
        - path: /etc/sudoers
        - '{{ sudoers.files }}'
      tags:
        - DISA-STIG-OL08-00-010380
        - NIST-800-53-CM-6(a)
        - NIST-800-53-IA-11
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sudo_remove_nopasswd
#### Rule Ensure Users Re-Authenticate for Privilege Escalation - sudo [ref]
The sudo `NOPASSWD` and `!authenticate` option, when specified, allows a user to execute commands using sudo without having to authenticate. This should be disabled by making sure that `NOPASSWD` and/or `!authenticate` do not exist in `/etc/sudoers` configuration file or any sudo configuration snippets in `/etc/sudoers.d/`."
Rationale: | Without re-authentication, users may access resources or perform tasks for which they do not have authorization. When operating systems provide the capability to escalate a functional capability, it is critical that the user re-authenticate.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sudo_require_authentication
Identifiers and References | References: 1, 12, 15, 16, 5, DSS05.04, DSS05.10, DSS06.03, DSS06.10, CCI-002038, 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, 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, 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, IA-11, CM-6(a), PR.AC-1, PR.AC-7, SRG-OS-000373-GPOS-00156
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    for f in /etc/sudoers /etc/sudoers.d/* ; do
      if [ ! -e "$f" ] ; then
        continue
      fi
      matching_list=$(grep -P '^(?!#).*[\s]+NOPASSWD[\s]*\:.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          # comment out "NOPASSWD" matches to preserve user data
          sed -i "s/^${entry}$/# &/g" $f
        done <<< "$matching_list"

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

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

        /usr/sbin/visudo -cf $f &> /dev/null || echo "Fail to validate $f with visudo"
      fi
    done
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Find /etc/sudoers.d/ files
      find:
        paths:
          - /etc/sudoers.d/
      register: sudoers
      tags:
        - NIST-800-53-CM-6(a)
        - NIST-800-53-IA-11
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sudo_require_authentication

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

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

    - name: Remove lines containing !authenticate from sudoers files
      replace:
        regexp: (^(?!#).*[\s]+\!authenticate.*$)
        replace: '# \g<1>'
        path: '{{ item.path }}'
        validate: /usr/sbin/visudo -cf %s
      with_items:
        - path: /etc/sudoers
        - '{{ sudoers.files }}'
      tags:
        - NIST-800-53-CM-6(a)
        - NIST-800-53-IA-11
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sudo_require_authentication
Group System Tooling / Utilities Group contains 1 rule
[ref] The following checks evaluate the system for recommended base packages -- both for installation and removal.
#### Rule Install rear Package [ref]
The `rear` package can be installed with the following command:
    $ sudo yum install rear
Rationale: | `rear` contains the Relax-and-Recover (ReaR) utility. ReaR produces a bootable image of a system and restores from backup using this image.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_rear_installed
Identifiers and References |
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    if ! rpm -q --quiet "rear" ; then
        yum install -y "rear"
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Ensure rear is installed
      package:
        name: rear
        state: present
      tags:
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_rear_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include install_rear

    class install_rear {
      package { 'rear':
        ensure => 'installed',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    package --add=rear
Remediation script ⇲
    [[packages]]
    name = "rear"
    version = "*"
Group Updating Software Group contains 6 rules
[ref] The `yum` command line tool is used to install and update software packages. The system also provides a graphical software update tool in the System menu, in the Administration submenu, called Software Update. Oracle Linux 8 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using `yum` or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.
#### Rule Configure dnf-automatic to Install Only Security Updates [ref]
To configure `dnf-automatic` to install only security updates automatically, set `upgrade_type` to `security` under `[commands]` section in `/etc/dnf/automatic.conf`.
Rationale: | By default, `dnf-automatic` installs all available updates. Reducing the amount of updated packages only to updates that were issued as a part of a security advisory increases the system stability.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_dnf-automatic_security_updates_only
Identifiers and References | References: BP28(R8), SI-2(5), CM-6(a), SI-2(c), FMT_SMF_EXT.1, SRG-OS-000191-GPOS-00080
Remediation Shell script ⇲
    found=false

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

        # find key in section and change value
        if grep -qzosP "[[:space:]]*\[commands\]([^\n\[]*\n+)+?[[:space:]]*upgrade_type" "$f"; then
                sed -i "s/upgrade_type[^(\n)]*/upgrade_type = security/" "$f"
                found=true

        # find section and add key = value to it
        elif grep -qs "[[:space:]]*\[commands\]" "$f"; then
                sed -i "/[[:space:]]*commands/a upgrade_type = security" "$f"
                found=true
        fi
    done

    # if section not in any file, append section with key = value to FIRST file in files parameter
    if ! $found ; then
        file=$(echo "/etc/dnf/automatic.conf" | cut -f1 -d' ')
        mkdir -p "$(dirname "$file")"
        echo -e "[commands]\nupgrade_type = security" >> "$file"
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
    - name: Configure dnf-automatic to Install Only Security Updates
      ini_file:
        dest: /etc/dnf/automatic.conf
        section: commands
        option: upgrade_type
        value: security
        create: true
      tags:
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-2(5)
        - NIST-800-53-SI-2(c)
        - dnf-automatic_security_updates_only
        - low_complexity
        - low_severity
        - medium_disruption
        - no_reboot_needed
        - unknown_strategy
#### Rule Ensure gpgcheck Enabled In Main yum Configuration [ref]
The `gpgcheck` option controls whether RPM packages' signatures are always checked prior to installation. To configure yum to check package signatures before installing them, ensure the following line appears in `/etc/yum.conf` in the `[main]` section:
    gpgcheck=1
Rationale: | Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization. Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
Identifiers and References | References: BP28(R15), 11, 2, 3, 9, 5.10.4.1, APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6, 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, CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b), PR.DS-6, PR.DS-8, PR.IP-1, FPT_TUD_EXT.1, FPT_TUD_EXT.2, Req-6.2, SRG-OS-000366-GPOS-00153, SRG-OS-000366-VMM-001430, SRG-OS-000370-VMM-001460, SRG-OS-000404-VMM-001650, OL08-00-010370, SV-248574r779288_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q yum; then

    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/yum.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^gpgcheck")

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^gpgcheck\\>" "/etc/yum.conf"; then
        "${sed_command[@]}" "s/^gpgcheck\\>.*/$formatted_output/gi" "/etc/yum.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/yum.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Strategy: | configure
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.10.4.1
        - DISA-STIG-OL08-00-010370
        - 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
        - configure_strategy
        - ensure_gpgcheck_globally_activated
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed

    - name: Ensure GPG check is globally activated
      ini_file:
        dest: /etc/yum.conf
        section: main
        option: gpgcheck
        value: 1
        no_extra_spaces: true
        create: false
      when: '"yum" in ansible_facts.packages'
      tags:
        - CJIS-5.10.4.1
        - DISA-STIG-OL08-00-010370
        - 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
        - configure_strategy
        - ensure_gpgcheck_globally_activated
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed
#### Rule Ensure gpgcheck Enabled for Local Packages [ref]
`yum` should be configured to verify the signature(s) of local packages prior to installation. To configure `yum` to verify signatures of local packages, set the `localpkg_gpgcheck` to `1` in `/etc/yum.conf`.
Rationale: | Changes to any software components can have significant effects to the overall security of the operating system. This requirement ensures the software has not been tampered and has been provided by a trusted vendor. Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages
Identifiers and References | References: BP28(R15), 11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, CM-11(a), CM-11(b), CM-6(a), CM-5(3), SA-12, SA-12(10), PR.IP-1, FPT_TUD_EXT.1, FPT_TUD_EXT.2, SRG-OS-000366-GPOS-00153, SRG-OS-000366-VMM-001430, SRG-OS-000370-VMM-001460, SRG-OS-000404-VMM-001650, OL08-00-010371, SV-248575r779291_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q yum; then

    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/yum.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^localpkg_gpgcheck")

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^localpkg_gpgcheck\\>" "/etc/yum.conf"; then
        "${sed_command[@]}" "s/^localpkg_gpgcheck\\>.*/$formatted_output/gi" "/etc/yum.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/yum.conf"
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-010371
        - NIST-800-171-3.4.8
        - NIST-800-53-CM-11(a)
        - NIST-800-53-CM-11(b)
        - NIST-800-53-CM-5(3)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SA-12
        - NIST-800-53-SA-12(10)
        - ensure_gpgcheck_local_packages
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed
        - unknown_strategy

    - name: Ensure GPG check Enabled for Local Packages (Yum)
      ini_file:
        dest: /etc/yum.conf
        section: main
        option: localpkg_gpgcheck
        value: 1
        no_extra_spaces: true
        create: true
      when: '"yum" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-010371
        - NIST-800-171-3.4.8
        - NIST-800-53-CM-11(a)
        - NIST-800-53-CM-11(b)
        - NIST-800-53-CM-5(3)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SA-12
        - NIST-800-53-SA-12(10)
        - ensure_gpgcheck_local_packages
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed
        - unknown_strategy
#### Rule Ensure gpgcheck Enabled for All yum Package Repositories [ref]
To ensure signature checking is not disabled for any repos, remove any lines from files in `/etc/yum.repos.d` of the form:
    gpgcheck=0
Rationale: | Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA)."
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_gpgcheck_never_disabled
Identifiers and References | References: BP28(R15), 11, 2, 3, 9, 5.10.4.1, APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02, 3.4.8, CCI-001749, 164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i), 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6, 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, CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b), PR.DS-6, PR.DS-8, PR.IP-1, FPT_TUD_EXT.1, FPT_TUD_EXT.2, Req-6.2, SRG-OS-000366-GPOS-00153, SRG-OS-000366-VMM-001430, SRG-OS-000370-VMM-001460, SRG-OS-000404-VMM-001650
Remediation Shell script ⇲
    sed -i 's/gpgcheck\s*=.*/gpgcheck=1/g' /etc/yum.repos.d/*
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Strategy: | enable
    - name: Grep for yum repo section names
      shell: |
        set -o pipefail
        grep -HEr '^\[.+\]' -r /etc/yum.repos.d/
      register: repo_grep_results
      ignore_errors: true
      changed_when: false
      tags:
        - CJIS-5.10.4.1
        - NIST-800-171-3.4.8
        - NIST-800-53-CM-11(a)
        - NIST-800-53-CM-11(b)
        - NIST-800-53-CM-5(3)
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SA-12
        - NIST-800-53-SA-12(10)
        - NIST-800-53-SC-12
        - NIST-800-53-SC-12(3)
        - NIST-800-53-SI-7
        - PCI-DSS-Req-6.2
        - enable_strategy
        - ensure_gpgcheck_never_disabled
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed

    - name: Set gpgcheck=1 for each yum repo
      ini_file:
        path: '{{ item[0] }}'
        section: '{{ item[1] }}'
        option: gpgcheck
        value: '1'
        no_extra_spaces: true
      loop: '{{ repo_grep_results.stdout | regex_findall( ''(.+\.repo):\[(.+)\]\n?'' )
        }}'
      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
        - enable_strategy
        - ensure_gpgcheck_never_disabled
        - high_severity
        - low_complexity
        - medium_disruption
        - no_reboot_needed
#### Rule Ensure Oracle Linux GPG Key Installed [ref]
To ensure the system can cryptographically verify base software packages come from Oracle (and to connect to the Unbreakable Linux Network to receive them), the Oracle GPG key must properly be installed. To install the Oracle GPG key, run:
    $ sudo uln_register
If the system is not connected to the Internet, then install the Oracle GPG key from trusted media such as the Oracle installation CD-ROM or DVD. Assuming the disc is mounted in `/media/cdrom`, use the following command as the root user to import it into the keyring:
    $ sudo rpm --import /media/cdrom/RPM-GPG-KEY-oracle
Alternatively, the key may be pre-loaded during the Oracle installation. In such cases, the key can be installed by running the following command:
    sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
Rationale: | Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Oracle GPG key is necessary to cryptographically verify packages are from Oracle.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_ensure_oracle_gpgkey_installed
Identifiers and References | References: 11, 2, 3, 9, APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02, CCI-001749, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4, SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6, 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, CM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), CM-11(a), CM-11(b), PR.DS-6, PR.DS-8, PR.IP-1, Req-6.2, 1.2.2
Remediation Shell script ⇲
    # OL fingerprints below retrieved from Oracle Linux Yum Server "Frequently Asked Questions"
    # https://yum.oracle.com/faq.html#a10
    readonly OL_RELEASE_FINGERPRINT="76FD3DB13AB67410B89DB10E82562EA9AD986DA3"
    readonly OL_AUXILIARY_FINGERPRINT=""

    FINGERPRINTS_REGEX="${OL_RELEASE_FINGERPRINT}"

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

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

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

    # Verify /etc/pki/rpm-gpg directory permissions are safe
    if [ "${RPM_GPG_DIR_PERMS}" -le "755" ]
    then
      # If they are safe, try to obtain fingerprints from the key file
      # (to ensure there won't be e.g. CRC error)
      readarray -t GPG_OUT < <(gpg --with-fingerprint --with-colons "$OL_RELEASE_KEY" | grep "^fpr" | cut -d ":" -f 10)
      GPG_RESULT=$?
      # No CRC error, safe to proceed
      if [ "${GPG_RESULT}" -eq "0" ]
      then
        # Filter just hexadecimal fingerprints from gpg's output from
        # processing of a key file
        echo "${GPG_OUT[*]}" | grep -vE "$FINGERPRINTS_REGEX" || {
          # If $ OL_RELEASE_KEY file doesn't contain any keys with unknown fingerprint, import it
          rpm --import "${OL_RELEASE_KEY}"
        }
      fi
    fi
#### Rule Ensure Software Patches Installed [ref]
If the system is joined to the ULN or a yum server, run the following command to install updates:
    $ sudo yum update
If the system is not configured to use one of these sources, updates (in the form of RPM packages) can be manually downloaded from the ULN and installed using `rpm`. NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates. Warning: The OVAL feed of Oracle Linux 8 is not a XML file, which may not be understood by all scanners.
Rationale: | Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities. If the most recent security patches and updates are not installed, unauthorized users may take advantage of weaknesses in the unpatched software. The lack of prompt attention to patching could result in a system compromise.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_security_patches_up_to_date
Identifiers and References | References: BP28(R08), 18, 20, 4, 5.10.4.1, APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02, CCI-000366, CCI-001227, 4.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9, A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3, SI-2(5), SI-2(c), CM-6(a), ID.RA-1, PR.IP-12, FMT_MOF_EXT.1, Req-6.2, SRG-OS-000480-GPOS-00227, SRG-OS-000480-VMM-002000, OL08-00-010010, SV-248523r779135_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch
    yum -y update
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | high
Reboot: | true
Strategy: | patch
    - name: Security patches are up to date
      package:
        name: '*'
        state: latest
      tags:
        - CJIS-5.10.4.1
        - DISA-STIG-OL08-00-010010
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-2(5)
        - NIST-800-53-SI-2(c)
        - PCI-DSS-Req-6.2
        - high_disruption
        - low_complexity
        - medium_severity
        - patch_strategy
        - reboot_required
        - security_patches_up_to_date
        - skip_ansible_lint
Group Account and Access Control Group contains 3 groups and 2 rules
[ref] In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Oracle Linux 8.
Group Protect Accounts by Restricting Password-Based Login Group contains 2 groups and 2 rules
[ref] Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the `/etc/passwd` and `/etc/shadow` files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.
Group Verify Proper Storage and Existence of Password Hashes Group contains 1 rule
[ref] By default, password hashes for local accounts are stored in the second field (colon-separated) in `/etc/shadow`. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as `/etc/passwd`, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.
#### Rule Prevent Login to Accounts With Empty Password [ref]
If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the `nullok` in `/etc/pam.d/system-auth` 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
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 18, 3, 5, 5.5.2, APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10, 3.1.1, 3.1.5, CCI-000366, 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), 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, 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, 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, IA-5(1)(a), IA-5(c), CM-6(a), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, FIA_UAU.1, Req-8.2.3, SRG-OS-000480-GPOS-00227, OL08-00-020331, SV-248715r779711_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Strategy: | configure
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEM_AUTH="/etc/pam.d/system-auth"
    PASSWORD_AUTH="/etc/pam.d/password-auth"

    if [ -f /usr/bin/authselect ]; then
        if authselect check; then
            authselect enable-feature without-nullok
            authselect apply-changes
        else
            echo "
    authselect integrity check failed. Remediation aborted!
    This remediation could not be applied because the authselect profile is not intact.
    It is not recommended to manually edit the PAM files when authselect is available
    In cases where the default authselect profile does not cover a specific demand, a custom authselect profile is recommended."
            false
        fi
    else
        sed --follow-symlinks -i 's/\<nullok\>//g' $SYSTEM_AUTH
        sed --follow-symlinks -i 's/\<nullok\>//g' $PASSWORD_AUTH
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | medium
Strategy: | configure
    - name: Check if system relies on authselect
      ansible.builtin.stat:
        path: /usr/bin/authselect
      register: result_authselect_present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed

    - name: Check integrity of authselect current profile
      ansible.builtin.command:
        cmd: authselect check
      register: result_authselect_check_cmd
      changed_when: false
      ignore_errors: true
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - result_authselect_present.stat.exists
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed

    - name: Get authselect current features
      ansible.builtin.shell:
        cmd: authselect current | tail -n+3 | awk '{ print $2 }'
      register: result_authselect_features
      changed_when: false
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - result_authselect_present.stat.exists
        - result_authselect_check_cmd is success
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed

    - name: Ensure nullok property is absent via authselect tool
      ansible.builtin.command:
        cmd: authselect enable-feature without-nullok
      register: result_authselect_cmd
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - result_authselect_present.stat.exists
        - result_authselect_check_cmd is success
        - result_authselect_features.stdout is not search("without-nullok")
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed

    - name: Informative message based on the authselect integrity check result
      ansible.builtin.assert:
        that:
          - result_authselect_check_cmd is success
        fail_msg:
          - authselect integrity check failed. Remediation aborted!
          - This remediation could not be applied because the authselect profile is not
            intact.
          - It is not recommended to manually edit the PAM files when authselect is available
          - In cases where the default authselect profile does not cover a specific demand,
            a custom authselect profile is recommended.
        success_msg:
          - authselect integrity check passed
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed

    - name: Ensure nullok property is absent by directly editing pam files
      replace:
        dest: '{{ item }}'
        regexp: nullok
      loop:
        - /etc/pam.d/system-auth
        - /etc/pam.d/password-auth
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - not result_authselect_present.stat.exists
      tags:
        - CJIS-5.5.2
        - DISA-STIG-OL08-00-020331
        - 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
        - configure_strategy
        - high_severity
        - low_complexity
        - medium_disruption
        - no_empty_passwords
        - no_reboot_needed
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
Group Restrict Root Logins Group contains 1 rule
[ref] Direct root logins should be allowed only for emergency use. In normal situations, the administrator should access the system via a unique unprivileged account, and then use `su` or `sudo` to execute privileged commands. Discouraging administrators from accessing the root account directly ensures an audit trail in organizations with multiple administrators. Locking down the channels through which root can connect directly also reduces opportunities for password-guessing against the root account. The `login` program uses the file `/etc/securetty` to determine which interfaces should allow root logins. The virtual devices `/dev/console` and `/dev/tty*` represent the system consoles (accessible via the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default installation). The default securetty file also contains `/dev/vc/*`. These are likely to be deprecated in most environments, but may be retained for compatibility. Root should also be prohibited from connecting via network protocols. Other sections of this document include guidance describing how to prevent root from logging in via SSH.
#### Rule 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
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10, 3.1.1, 3.1.5, CCI-000366, 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, 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, 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, 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, IA-2, AC-6(5), IA-4(b), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, SRG-OS-000480-GPOS-00227, OL08-00-040200, SV-248874r780188_rule
Remediation Shell script ⇲
    awk -F: '$3 == 0 && $1 != "root" { print $1 }' /etc/passwd | xargs --no-run-if-empty --max-lines=1 passwd -l
Group System Accounting with auditd Group contains 7 groups and 27 rules
[ref] The audit service provides substantial capabilities for recording system activities. By default, the service audits about SELinux AVC denials and certain types of security-relevant events such as system logins, account modifications, and authentication events performed by programs such as sudo. Under its default configuration, `auditd` has modest disk space requirements, and should not noticeably impact system performance. NOTE: The Linux Audit daemon `auditd` can be configured to use the `augenrules` program to read audit rules files (`*.rules`) located in `/etc/audit/rules.d` location and compile them to create the resulting form of the `/etc/audit/audit.rules` configuration file during the daemon startup (default configuration). Alternatively, the `auditd` daemon can use the `auditctl` utility to read audit rules from the `/etc/audit/audit.rules` configuration file during daemon startup, and load them into the kernel. The expected behavior is configured via the appropriate `ExecStartPost` directive setting in the `/usr/lib/systemd/system/auditd.service` configuration file. To instruct the `auditd` daemon to use the `augenrules` program to read audit rules (default configuration), use the following setting:
    ExecStartPost=-/sbin/augenrules --load
in the `/usr/lib/systemd/system/auditd.service` configuration file. In order to instruct the `auditd` daemon to use the `auditctl` utility to read audit rules, use the following setting:
    ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the `/usr/lib/systemd/system/auditd.service` configuration file. Refer to `[Service]` section of the `/usr/lib/systemd/system/auditd.service` configuration file for further details. Government networks often have substantial auditing requirements and `auditd` can be configured to meet these requirements. Examining some example audit records demonstrates how the Linux audit system satisfies common requirements. The following example from Fedora Documentation available at `https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/selinux_users_and_administrators_guide/index#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages` shows the substantial amount of information captured in a two typical "raw" audit messages, followed by a breakdown of the most important fields. In this example the message is SELinux-related and reports an AVC denial (and the associated system call) that occurred when the Apache HTTP Server attempted to access the `/var/www/html/file1` file (labeled with the `samba_share_t` type):
    type=AVC msg=audit(1226874073.147:96): avc:  denied  { getattr } for pid=2465 comm="httpd"
    path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
    tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file

    type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
    a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
    gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
    exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)
`msg=audit(1226874073.147:96)` The number in parentheses is the unformatted time stamp (Epoch time) for the event, which can be converted to standard time by using the `date` command. `{ getattr }` The item in braces indicates the permission that was denied. `getattr` indicates the source process was trying to read the target file's status information. This occurs before reading files. This action is denied due to the file being accessed having the wrong label. Commonly seen permissions include `getattr`, `read`, and `write`. `comm="httpd"` The executable that launched the process. The full path of the executable is found in the `exe=` section of the system call (`SYSCALL`) message, which in this case, is `exe="/usr/sbin/httpd"`. `path="/var/www/html/file1"` The path to the object (target) the process attempted to access. `scontext="unconfined_u:system_r:httpd_t:s0"` The SELinux context of the process that attempted the denied action. In this case, it is the SELinux context of the Apache HTTP Server, which is running in the `httpd_t` domain. `tcontext="unconfined_u:object_r:samba_share_t:s0"` The SELinux context of the object (target) the process attempted to access. In this case, it is the SELinux context of `file1`. Note: the `samba_share_t` type is not accessible to processes running in the `httpd_t` domain. From the system call (`SYSCALL`) message, two items are of interest: `success=no`: indicates whether the denial (AVC) was enforced or not. `success=no` indicates the system call was not successful (SELinux denied access). `success=yes` indicates the system call was successful - this can be seen for permissive domains or unconfined domains, such as `initrc_t` and `kernel_t`. `exe="/usr/sbin/httpd"`: the full path to the executable that launched the process, which in this case, is `exe="/usr/sbin/httpd"`.
Group Configure auditd Rules for Comprehensive Auditing Group contains 5 groups and 20 rules
[ref] The `auditd` program can perform comprehensive monitoring of system activity. This section describes recommended configuration settings for comprehensive auditing, but a full description of the auditing system's capabilities is beyond the scope of this guide. The mailing list linux-audit@redhat.com exists to facilitate community discussion of the auditing system. The audit subsystem supports extensive collection of events, including: Tracing of arbitrary system calls (identified by name or number) on entry or exit. Filtering by PID, UID, call success, system call argument (with some limitations), etc. Monitoring of specific files for modifications to the file's contents or metadata. Auditing rules at startup are controlled by the file `/etc/audit/audit.rules`. Add rules to it to meet the auditing requirements for your organization. Each line in `/etc/audit/audit.rules` represents a series of arguments that can be passed to `auditctl` and can be individually tested during runtime. See documentation in `/usr/share/doc/audit-VERSION` and in the related man pages for more details. If copying any example audit rulesets from `/usr/share/doc/audit-VERSION`, be sure to comment out the lines containing `arch=` which are not appropriate for your system's architecture. Then review and understand the following rules, ensuring rules are activated as needed for the appropriate architecture. After reviewing all the rules, reading the following sections, and editing as needed, the new rules can be activated as follows:
    $ sudo service auditd restart
Group Record Events that Modify the System's Discretionary Access Controls Group contains 2 rules
[ref] At a minimum, the audit system should collect file permission changes for all users and root. Note that the "-F arch=b32" lines should be present even on a 64 bit system. These commands identify system calls for auditing. Even if the system is 64 bit it can still execute 32 bit system calls. Additionally, these rules can be configured in a number of ways while still achieving the desired effect. An example of this is that the "-S" calls could be split up and placed on separate lines, however, this is less efficient. Add the following to `/etc/audit/audit.rules`:
    -a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
        -a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
        -a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
If your system is 64 bit then these lines should be duplicated and the arch=b32 replaced with arch=b64 as follows:
    -a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -F key=perm_mod
        -a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -F key=perm_mod
        -a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -F key=perm_mod
#### Rule Record Events that Modify the System's Discretionary Access Controls - chmod [ref]
At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -a always,exit -F arch=b32 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S chmod -F auid>=1000 -F auid!=unset -F key=perm_mod
Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale: | The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chmod
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940, OL08-00-030490, SV-248791r779939_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
    	OTHER_FILTERS=""
    	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    	SYSCALL="chmod"
    	KEY="perm_mod"
    	SYSCALL_GROUPING="chmod fchmod fchmodat"

    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030490
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chmod
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Set architecture for audit chmod tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030490
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chmod
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for chmod for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chmod
            syscall_grouping:
              - chmod
              - fchmod
              - fchmodat

        - name: Check existence of chmod in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
          set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chmod
            syscall_grouping:
              - chmod
              - fchmod
              - fchmodat

        - name: Check existence of chmod in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030490
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chmod
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for chmod for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chmod
            syscall_grouping:
              - chmod
              - fchmod
              - fchmodat

        - name: Check existence of chmod in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
          set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chmod
            syscall_grouping:
              - chmod
              - fchmod
              - fchmodat

        - name: Check existence of chmod in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030490
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chmod
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
#### Rule Record Events that Modify the System's Discretionary Access Controls - chown [ref]
At a minimum, the audit system should collect file permission changes for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -a always,exit -F arch=b32 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S chown -F auid>=1000 -F auid!=unset -F key=perm_mod
Warning: Note that these rules can be configured in a number of ways while still achieving the desired effect. Here the system calls have been placed independent of other system calls. Grouping these system calls with others as identifying earlier in this guide is more efficient.
Rationale: | The changing of file permissions could indicate that a user is attempting to gain access to information that would otherwise be disallowed. Auditing DAC modifications can facilitate the identification of patterns of abuse among both authorized and unauthorized users.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_dac_modification_chown
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.5.5, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000064-GPOS-00033, SRG-OS-000466-GPOS-00210, SRG-OS-000458-GPOS-00203, SRG-OS-000474-GPOS-00219, SRG-OS-000458-VMM-001810, SRG-OS-000474-VMM-001940, OL08-00-030480, SV-248790r779936_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
    	OTHER_FILTERS=""
    	AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    	SYSCALL="chown"
    	KEY="perm_mod"
    	SYSCALL_GROUPING="chown fchown fchownat lchown"

    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030480
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chown
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Set architecture for audit chown tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030480
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chown
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for chown for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chown
            syscall_grouping:
              - chown
              - fchown
              - fchownat
              - lchown

        - name: Check existence of chown in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
          set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chown
            syscall_grouping:
              - chown
              - fchown
              - fchownat
              - lchown

        - name: Check existence of chown in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030480
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chown
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for chown for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chown
            syscall_grouping:
              - chown
              - fchown
              - fchownat
              - lchown

        - name: Check existence of chown in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/perm_mod.rules
          set_fact: audit_file="/etc/audit/rules.d/perm_mod.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - chown
            syscall_grouping:
              - chown
              - fchown
              - fchownat
              - lchown

        - name: Check existence of chown in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F auid>=1000 -F auid!=unset (?:-k
              |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F auid>=1000
              -F auid!=unset -F key=perm_mod
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030480
        - NIST-800-171-3.1.7
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_dac_modification_chown
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
Group Record Execution Attempts to Run SELinux Privileged Commands Group contains 6 rules
[ref] At a minimum, the audit system should collect the execution of SELinux privileged commands for all users and root.
#### Rule Record Any Attempts to Run chcon [ref]
At a minimum, the audit system should collect any execution attempt of the `chcon` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/bin/chcon -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/bin/chcon -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_chcon
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000130, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000463-VMM-001850, OL08-00-030260, SV-248754r779828_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/bin/chcon"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030260
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_chcon
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/bin/chcon
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/bin/chcon -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F auid>=1000
              -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/bin/chcon -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/bin/chcon -F auid>=1000 -F auid!=unset
              (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/bin/chcon -F auid>=1000
              -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030260
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_chcon
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Any Attempts to Run restorecon [ref]
At a minimum, the audit system should collect any execution attempt of the `restorecon` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/sbin/restorecon -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/sbin/restorecon -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_restorecon
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, SRG-OS-000392-GPOS-00172, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000463-VMM-001850
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/sbin/restorecon"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_restorecon
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/sbin/restorecon
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/restorecon -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/restorecon -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/restorecon
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/restorecon -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/restorecon -F auid>=1000 -F
              auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/restorecon
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_restorecon
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Any Attempts to Run semanage [ref]
At a minimum, the audit system should collect any execution attempt of the `semanage` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/sbin/semanage -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/sbin/semanage -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_semanage
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, 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, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000463-VMM-001850, OL08-00-030313, SV-248764r779858_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/sbin/semanage"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030313
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(4)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_semanage
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/sbin/semanage
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/semanage -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/semanage -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/semanage
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/semanage -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/semanage -F auid>=1000 -F auid!=unset
              (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/semanage
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030313
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(4)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_semanage
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Any Attempts to Run setfiles [ref]
At a minimum, the audit system should collect any execution attempt of the `setfiles` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/sbin/setfiles -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/sbin/setfiles -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_setfiles
Identifiers and References | References: CCI-000169, CCI-000172, CCI-002884, AU-2(d), AU-12(c), AC-6(9), CM-6(a), SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000463-VMM-001850, OL08-00-030314, SV-248765r779861_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/sbin/setfiles"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030314
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_setfiles
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/sbin/setfiles
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/setfiles -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/setfiles -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/setfiles
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/setfiles -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/setfiles -F auid>=1000 -F auid!=unset
              (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/setfiles
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030314
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_setfiles
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Any Attempts to Run setsebool [ref]
At a minimum, the audit system should collect any execution attempt of the `setsebool` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/sbin/setsebool -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/sbin/setsebool -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_setsebool
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, FAU_GEN.1.1.c, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000463-GPOS-00207, SRG-OS-000465-GPOS-00209, SRG-OS-000463-VMM-001850, OL08-00-030316, SV-248767r779867_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/sbin/setsebool"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030316
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_setsebool
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/sbin/setsebool
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/setsebool -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/setsebool -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/setsebool
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/setsebool -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/setsebool -F auid>=1000 -F
              auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/setsebool
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030316
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_setsebool
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Any Attempts to Run seunshare [ref]
At a minimum, the audit system should collect any execution attempt of the `seunshare` command for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F path=/usr/sbin/seunshare -F auid>=1000 -F auid!=unset -F key=privileged
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file:
    -a always,exit -F path=/usr/sbin/seunshare -F auid>=1000 -F auid!=unset -F key=privileged
Rationale: | Misuse of privileged functions, either intentionally or unintentionally by authorized users, or by unauthorized external entities that have compromised system accounts, is a serious and ongoing concern and can have significant adverse impacts on organizations. Auditing the use of privileged functions is one way to detect such misuse and identify the risk from insider and advanced persistent threats. Privileged programs are subject to escalation-of-privilege attacks, which attempt to subvert their normal role of providing some necessary but limited capability. As such, motivation exists to monitor these programs for unusual activity.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_execution_seunshare
Identifiers and References | References: CCI-000172, AU-2(d), AU-12(c), AC-6(9), CM-6(a), FAU_GEN.1.1.c, SRG-OS-000463-VMM-001850
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    ACTION_ARCH_FILTERS="-a always,exit"
    OTHER_FILTERS="-F path=/usr/sbin/seunshare"
    AUID_FILTERS="-F auid>=1000 -F auid!=unset"
    SYSCALL=""
    KEY="privileged"
    SYSCALL_GROUPING=""
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_seunshare
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for /usr/sbin/seunshare
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/seunshare -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/privileged.rules
          set_fact: audit_file="/etc/audit/rules.d/privileged.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/seunshare -F auid>=1000
              -F auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/seunshare
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls: []
            syscall_grouping: []

        - name: Check existence of  in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit(( -S |,)\w+)*(( -S |,){{ item }})+(( -S |,)\w+)*
              -F path=/usr/sbin/seunshare -F auid>=1000 -F auid!=unset (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit)(?=.*(?:(?:-S |,)(?:{{ syscalls_found | join("|")
              }}))\b)((?:( -S |,)\w+)+)( -F path=/usr/sbin/seunshare -F auid>=1000 -F
              auid!=unset (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit{{ syscalls | join(',') }} -F path=/usr/sbin/seunshare
              -F auid>=1000 -F auid!=unset -F key=privileged
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - audit_rules_execution_seunshare
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
Group Record Information on Kernel Modules Loading and Unloading Group contains 1 rule
[ref] To capture kernel module loading and unloading events, use following lines, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
    -a always,exit -F arch=ARCH -S init_module,delete_module -F key=modules
Place to add the lines depends on a way `auditd` daemon is configured. If it is configured to use the `augenrules` program (the default), add the lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`. If the `auditd` daemon is configured to use the `auditctl` utility, add the lines to file `/etc/audit/audit.rules`.
#### Rule Ensure auditd Collects Information on Kernel Module Loading and Unloading [ref]
To capture kernel module loading and unloading events, use following lines, setting ARCH to either b32 for 32-bit system, or having two lines for both b32 and b64 in case your system is 64-bit:
    -a always,exit -F arch=ARCH -S init_module,finit_module,delete_module -F key=modules
The place to add the lines depends on a way `auditd` daemon is configured. If it is configured to use the `augenrules` program (the default), add the lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`. If the `auditd` daemon is configured to use the `auditctl` utility, add the lines to file `/etc/audit/audit.rules`.
Rationale: | The addition/removal of kernel modules can be used to alter the behavior of the kernel and potentially introduce malicious code into kernel space. It is important to have an audit trail of modules that have been introduced into the kernel.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_kernel_module_loading
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.7
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    # Note: 32-bit and 64-bit kernel syscall numbers not always line up =>
    #       it's required on a 64-bit system to check also for the presence
    #       of 32-bit's equivalent of the corresponding rule.
    #       (See `man 7 audit.rules` for details )
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            OTHER_FILTERS=""
            AUID_FILTERS=""
            SYSCALL="init_module finit_module delete_module"
            KEY="modules"
            SYSCALL_GROUPING="init_module finit_module delete_module"
            # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
            unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
            unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.7
        - audit_rules_kernel_module_loading
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Set architecture for audit tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.7
        - audit_rules_kernel_module_loading
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for kernel module loading for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - init_module
              - delete_module
              - finit_module
            syscall_grouping:
              - init_module
              - delete_module
              - finit_module

        - name: Check existence of init_module, delete_module, finit_module in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/modules.rules
          set_fact: audit_file="/etc/audit/rules.d/modules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=modules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - init_module
              - delete_module
              - finit_module
            syscall_grouping:
              - init_module
              - delete_module
              - finit_module

        - name: Check existence of init_module, delete_module, finit_module in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=modules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.7
        - audit_rules_kernel_module_loading
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Perform remediation of Audit rules for kernel module loading for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - init_module
              - delete_module
              - finit_module
            syscall_grouping:
              - init_module
              - delete_module
              - finit_module

        - name: Check existence of init_module, delete_module, finit_module in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/modules.rules
          set_fact: audit_file="/etc/audit/rules.d/modules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=modules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - init_module
              - delete_module
              - finit_module
            syscall_grouping:
              - init_module
              - delete_module
              - finit_module

        - name: Check existence of init_module, delete_module, finit_module in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=modules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.7
        - audit_rules_kernel_module_loading
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
Group Record Attempts to Alter Logon and Logout Events Group contains 3 rules
[ref] The audit system already collects login information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing logon events:
    -w /var/log/tallylog -p wa -k logins
    -w /var/run/faillock/ -p wa -k logins
    -w /var/log/lastlog -p wa -k logins
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for unattempted manual edits of files involved in storing logon events:
    -w /var/log/tallylog -p wa -k logins
    -w /var/run/faillock/ -p wa -k logins
    -w /var/log/lastlog -p wa -k logins
#### Rule Record Attempts to Alter Logon and Logout Events - faillock [ref]
The audit system already collects login information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing logon events:
    -w /var/run/faillock -p wa -k logins
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for unattempted manual edits of files involved in storing logon events:
    -w /var/run/faillock -p wa -k logins
Rationale: | Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_login_events_faillock
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218, SRG-OS-000473-VMM-001930, SRG-OS-000470-VMM-001900
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/run/faillock" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/run/faillock $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/run/faillock$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/run/faillock -p wa -k logins" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/run/faillock" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/logins.rules"
        # If the logins.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/run/faillock" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/run/faillock $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/run/faillock$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/run/faillock -p wa -k logins" >> "$audit_rules_file"
        fi
    done

    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:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other user/group modification audit rules
      find:
        paths: /etc/audit/rules.d
        recurse: false
        contains: -k logins$
        patterns: '*.rules'
      register: find_faillock
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules
        as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/logins.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_faillock.matched is defined and find_faillock.matched == 0
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_faillock.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_faillock.matched is defined and find_faillock.matched > 0
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the faillock rule in rules.d when on x86
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /var/run/faillock -p wa -k logins
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the faillock rule in /etc/audit/audit.rules
      lineinfile:
        line: -w /var/run/faillock -p wa -k logins
        state: present
        dest: /etc/audit/audit.rules
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_faillock
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
#### Rule Record Attempts to Alter Logon and Logout Events - lastlog [ref]
The audit system already collects login information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing logon events:
    -w /var/log/lastlog -p wa -k logins
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for unattempted manual edits of files involved in storing logon events:
    -w /var/log/lastlog -p wa -k logins
Rationale: | Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_login_events_lastlog
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.3, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000473-GPOS-00218, SRG-OS-000470-GPOS-00214, SRG-OS-000473-VMM-001930, SRG-OS-000470-VMM-001900, OL08-00-030600, SV-248802r779972_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/lastlog" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/logins.rules"
        # If the logins.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/log/lastlog" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/lastlog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/log/lastlog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/log/lastlog -p wa -k logins" >> "$audit_rules_file"
        fi
    done

    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:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other user/group modification audit rules
      find:
        paths: /etc/audit/rules.d
        recurse: false
        contains: -k logins$
        patterns: '*.rules'
      register: find_lastlog
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules
        as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/logins.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_lastlog.matched is defined and find_lastlog.matched == 0
      tags:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_lastlog.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_lastlog.matched is defined and find_lastlog.matched > 0
      tags:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the lastlog rule in rules.d when on x86
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /var/log/lastlog -p wa -k logins
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the lastlog rule in /etc/audit/audit.rules
      lineinfile:
        line: -w /var/log/lastlog -p wa -k logins
        state: present
        dest: /etc/audit/audit.rules
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030600
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_lastlog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
#### Rule Record Attempts to Alter Logon and Logout Events - tallylog [ref]
The audit system already collects login information for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d` in order to watch for attempted manual edits of files involved in storing logon events:
    -w /var/log/tallylog -p wa -k logins
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file in order to watch for unattempted manual edits of files involved in storing logon events:
    -w /var/log/tallylog -p wa -k logins
Rationale: | Manual editing of these files may indicate nefarious activity, such as an attacker attempting to remove evidence of an intrusion.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_login_events_tallylog
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000172, CCI-002884, CCI-000126, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.3, SRG-OS-000392-GPOS-00172, SRG-OS-000470-GPOS-00214, SRG-OS-000473-GPOS-00218, SRG-OS-000473-VMM-001930, SRG-OS-000470-VMM-001900
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/logins.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/var/log/tallylog" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/logins.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/logins.rules"
        # If the logins.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/var/log/tallylog" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/var/log/tallylog $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/var/log/tallylog$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /var/log/tallylog -p wa -k logins" >> "$audit_rules_file"
        fi
    done

    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:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other user/group modification audit rules
      find:
        paths: /etc/audit/rules.d
        recurse: false
        contains: -k logins$
        patterns: '*.rules'
      register: find_tallylog
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: If existing user/group modification ruleset not found, use /etc/audit/rules.d/logins.rules
        as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/logins.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_tallylog.matched is defined and find_tallylog.matched == 0
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_tallylog.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_tallylog.matched is defined and find_tallylog.matched > 0
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the tallylog rule in rules.d when on x86
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /var/log/tallylog -p wa -k logins
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy

    - name: Inserts/replaces the tallylog rule in /etc/audit/audit.rules
      lineinfile:
        line: -w /var/log/tallylog -p wa -k logins
        state: present
        dest: /etc/audit/audit.rules
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.3
        - audit_rules_login_events_tallylog
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
Group Records Events that Modify Date and Time Information Group contains 5 rules
[ref] Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time. All changes to the system time should be audited.
#### Rule Record attempts to alter time through adjtimex [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -a always,exit -F arch=b32 -S adjtimex -F key=audit_time_rules
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S adjtimex -F key=audit_time_rules
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale: | Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_time_adjtimex
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.4.2.b
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
        # Create expected audit group and audit rule form for particular system call & architecture
        if [ ${ARCH} = "b32" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
            # so append it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday stime"
            SYSCALL_GROUPING="adjtimex settimeofday stime"
        elif [ ${ARCH} = "b64" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
            # therefore don't add it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday"
            SYSCALL_GROUPING="adjtimex settimeofday"
        fi
        OTHER_FILTERS=""
        AUID_FILTERS=""
        KEY="audit_time_rules"
        # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'augenrules', then check if the audit rule is defined
        # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
        # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
        default_file="/etc/audit/rules.d/$KEY.rules"
        # As other_filters may include paths, lets use a different delimiter for it
        # The "F" script expression tells sed to print the filenames where the expressions matched
        readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
        # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
        if [ ${#files_to_inspect[@]} -eq "0" ]
        then
            file_to_inspect="/etc/audit/rules.d/$KEY.rules"
            files_to_inspect=("$file_to_inspect")
            if [ ! -e "$file_to_inspect" ]
            then
                touch "$file_to_inspect"
                chmod 0640 "$file_to_inspect"
            fi
        fi

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
        # file to the list of files to be inspected
        default_file="/etc/audit/audit.rules"
        files_to_inspect+=('/etc/audit/audit.rules' )

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_adjtimex
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set architecture for audit tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_adjtimex
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for adjtimex for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - adjtimex
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of adjtimex in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - adjtimex
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of adjtimex in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_adjtimex
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for adjtimex for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - adjtimex
            syscall_grouping:
              - adjtimex
              - settimeofday

        - name: Check existence of adjtimex in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - adjtimex
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of adjtimex in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_adjtimex
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Attempts to Alter Time Through clock_settime [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale: | Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_time_clock_settime
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.4.2.b
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
    	OTHER_FILTERS="-F a0=0x0"
    	AUID_FILTERS=""
    	SYSCALL="clock_settime"
    	KEY="time-change"
    	SYSCALL_GROUPING=""
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_clock_settime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set architecture for audit tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_clock_settime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for clock_settime for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - clock_settime
            syscall_grouping: []

        - name: Check existence of clock_settime in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/time-change.rules
          set_fact: audit_file="/etc/audit/rules.d/time-change.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F a0=0x0 -F
              key=time-change
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - clock_settime
            syscall_grouping: []

        - name: Check existence of clock_settime in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F a0=0x0 -F
              key=time-change
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_clock_settime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for clock_settime for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - clock_settime
            syscall_grouping: []

        - name: Check existence of clock_settime in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/time-change.rules
          set_fact: audit_file="/etc/audit/rules.d/time-change.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F a0=0x0 -F
              key=time-change
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - clock_settime
            syscall_grouping: []

        - name: Check existence of clock_settime in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* -F a0=0x0 (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( -F a0=0x0 (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F a0=0x0 -F
              key=time-change
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_clock_settime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record attempts to alter time through settimeofday [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -a always,exit -F arch=b32 -S settimeofday -F key=audit_time_rules
If the system is 64 bit then also add the following line:
    -a always,exit -F arch=b64 -S settimeofday -F key=audit_time_rules
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls:
    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale: | Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_time_settimeofday
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.4.2.b
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
        # Create expected audit group and audit rule form for particular system call & architecture
        if [ ${ARCH} = "b32" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
            # so append it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday stime"
            SYSCALL_GROUPING="adjtimex settimeofday stime"
        elif [ ${ARCH} = "b64" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
            # therefore don't add it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday"
            SYSCALL_GROUPING="adjtimex settimeofday"
        fi
        OTHER_FILTERS=""
        AUID_FILTERS=""
        KEY="audit_time_rules"
        # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'augenrules', then check if the audit rule is defined
        # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
        # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
        default_file="/etc/audit/rules.d/$KEY.rules"
        # As other_filters may include paths, lets use a different delimiter for it
        # The "F" script expression tells sed to print the filenames where the expressions matched
        readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
        # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
        if [ ${#files_to_inspect[@]} -eq "0" ]
        then
            file_to_inspect="/etc/audit/rules.d/$KEY.rules"
            files_to_inspect=("$file_to_inspect")
            if [ ! -e "$file_to_inspect" ]
            then
                touch "$file_to_inspect"
                chmod 0640 "$file_to_inspect"
            fi
        fi

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
        # file to the list of files to be inspected
        default_file="/etc/audit/audit.rules"
        files_to_inspect+=('/etc/audit/audit.rules' )

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_settimeofday
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set architecture for audit tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_settimeofday
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for settimeofday for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - settimeofday
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of settimeofday in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - settimeofday
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of settimeofday in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_settimeofday
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for settimeofday for x86_64 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - settimeofday
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of settimeofday in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - settimeofday
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of settimeofday in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_settimeofday
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Attempts to Alter Time Through stime [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d` for both 32 bit and 64 bit systems:
    -a always,exit -F arch=b32 -S stime -F key=audit_time_rules
Since the 64 bit version of the "stime" system call is not defined in the audit lookup table, the corresponding "-F arch=b64" form of this rule is not expected to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule form itself is sufficient for both 32 bit and 64 bit systems). If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file for both 32 bit and 64 bit systems:
    -a always,exit -F arch=b32 -S stime -F key=audit_time_rules
Since the 64 bit version of the "stime" system call is not defined in the audit lookup table, the corresponding "-F arch=b64" form of this rule is not expected to be defined on 64 bit systems (the aforementioned "-F arch=b32" stime rule form itself is sufficient for both 32 bit and 64 bit systems). The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined system calls:
    -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules
Rationale: | Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_time_stime
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.4.2.b
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
        # Create expected audit group and audit rule form for particular system call & architecture
        if [ ${ARCH} = "b32" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call is known at 32-bit arch (see e.g "$ ausyscall i386 stime" 's output)
            # so append it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday stime"
            SYSCALL_GROUPING="adjtimex settimeofday stime"
        elif [ ${ARCH} = "b64" ]
        then
            ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
            # stime system call isn't known at 64-bit arch (see "$ ausyscall x86_64 stime" 's output)
            # therefore don't add it to the list of time group system calls to be audited
            SYSCALL="adjtimex settimeofday"
            SYSCALL_GROUPING="adjtimex settimeofday"
        fi
        OTHER_FILTERS=""
        AUID_FILTERS=""
        KEY="audit_time_rules"
        # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'augenrules', then check if the audit rule is defined
        # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
        # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
        default_file="/etc/audit/rules.d/$KEY.rules"
        # As other_filters may include paths, lets use a different delimiter for it
        # The "F" script expression tells sed to print the filenames where the expressions matched
        readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
        # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
        if [ ${#files_to_inspect[@]} -eq "0" ]
        then
            file_to_inspect="/etc/audit/rules.d/$KEY.rules"
            files_to_inspect=("$file_to_inspect")
            if [ ! -e "$file_to_inspect" ]
            then
                touch "$file_to_inspect"
                chmod 0640 "$file_to_inspect"
            fi
        fi

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
        unset syscall_a
        unset syscall_grouping
        unset syscall_string
        unset syscall
        unset file_to_edit
        unset rule_to_edit
        unset rule_syscalls_to_edit
        unset other_string
        unset auid_string
        unset full_rule

        # Load macro arguments into arrays
        read -a syscall_a <<< $SYSCALL
        read -a syscall_grouping <<< $SYSCALL_GROUPING

        # Create a list of audit *.rules files that should be inspected for presence and correctness
        # of a particular audit rule. The scheme is as follows:
        #
        # -----------------------------------------------------------------------------------------
        #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
        # -----------------------------------------------------------------------------------------
        #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
        # -----------------------------------------------------------------------------------------
        #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
        #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
        # -----------------------------------------------------------------------------------------
        #
        files_to_inspect=()

        # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
        # file to the list of files to be inspected
        default_file="/etc/audit/audit.rules"
        files_to_inspect+=('/etc/audit/audit.rules' )

        # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
        skip=1

        for audit_file in "${files_to_inspect[@]}"
        do
            # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
            # i.e, collect rules that match:
            # * the action, list and arch, (2-nd argument)
            # * the other filters, (3-rd argument)
            # * the auid filters, (4-rd argument)
            readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

            candidate_rules=()
            # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
            for s_rule in "${similar_rules[@]}"
            do
                # Strip all the options and fields we know of,
                # than check if there was any field left over
                extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
                grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
            done

            if [[ ${#syscall_a[@]} -ge 1 ]]
            then
                # Check if the syscall we want is present in any of the similar existing rules
                for rule in "${candidate_rules[@]}"
                do
                    rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                    all_syscalls_found=0
                    for syscall in "${syscall_a[@]}"
                    do
                        grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                           # A syscall was not found in the candidate rule
                           all_syscalls_found=1
                           }
                    done
                    if [[ $all_syscalls_found -eq 0 ]]
                    then
                        # We found a rule with all the syscall(s) we want; skip rest of macro
                        skip=0
                        break
                    fi

                    # Check if this rule can be grouped with our target syscall and keep track of it
                    for syscall_g in "${syscall_grouping[@]}"
                    do
                        if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                        then
                            file_to_edit=${audit_file}
                            rule_to_edit=${rule}
                            rule_syscalls_to_edit=${rule_syscalls}
                        fi
                    done
                done
            else
                # If there is any candidate rule, it is compliant; skip rest of macro
                if [ "${#candidate_rules[@]}" -gt 0 ]
                then
                    skip=0
                fi
            fi

            if [ "$skip" -eq 0 ]; then
                break
            fi
        done

        if [ "$skip" -ne 0 ]; then
            # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
            # At this point we know if we need to either append the $full_rule or group
            # the syscall together with an exsiting rule

            # Append the full_rule if it cannot be grouped to any other rule
            if [ -z ${rule_to_edit+x} ]
            then
                # Build full_rule while avoid adding double spaces when other_filters is empty
                if [ "${#syscall_a[@]}" -gt 0 ]
                then
                    syscall_string=""
                    for syscall in "${syscall_a[@]}"
                    do
                        syscall_string+=" -S $syscall"
                    done
                fi
                other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
                auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
                full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
                echo "$full_rule" >> "$default_file"
                chmod o-rwx ${default_file}
            else
                # Check if the syscalls are declared as a comma separated list or
                # as multiple -S parameters
                if grep -q -- "," <<< "${rule_syscalls_to_edit}"
                then
                    delimiter=","
                else
                    delimiter=" -S "
                fi
                new_grouped_syscalls="${rule_syscalls_to_edit}"
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                       # A syscall was not found in the candidate rule
                       new_grouped_syscalls+="${delimiter}${syscall}"
                       }
                done

                # Group the syscall in the rule
                sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
            fi
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_stime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Perform remediation of Audit rules for stime syscall for x86 platform
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - stime
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of stime in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_time_rules.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_time_rules.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - stime
            syscall_grouping:
              - adjtimex
              - settimeofday
              - stime

        - name: Check existence of stime in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_time_rules
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_stime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Attempts to Alter the localtime File [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -w /etc/localtime -p wa -k audit_time_rules
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -w /etc/localtime -p wa -k audit_time_rules
The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport and should always be used.
Rationale: | Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_time_watch_localtime
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-001487, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.4.2.b
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/localtime" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/localtime $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/localtime$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/localtime -p wa -k audit_time_rules" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_time_rules.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/localtime" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_time_rules.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_time_rules.rules"
        # If the audit_time_rules.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/localtime" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/localtime $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/localtime$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/localtime -p wa -k audit_time_rules" >> "$audit_rules_file"
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/localtime already exists in /etc/audit/rules.d/
      find:
        paths: /etc/audit/rules.d
        contains: ^\s*-w\s+/etc/localtime\s+-p\s+wa(\s|$)+
        patterns: '*.rules'
      register: find_existing_watch_rules_d
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other rules with specified key audit_time_rules
      find:
        paths: /etc/audit/rules.d
        contains: ^.*(?:-F key=|-k\s+)audit_time_rules$
        patterns: '*.rules'
      register: find_watch_key
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/audit_time_rules.rules as the recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/audit_time_rules.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/localtime in /etc/audit/rules.d/
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /etc/localtime -p wa -k audit_time_rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/localtime already exists in /etc/audit/audit.rules
      find:
        paths: /etc/audit/
        contains: ^\s*-w\s+/etc/localtime\s+-p\s+wa(\s|$)+
        patterns: audit.rules
      register: find_existing_watch_audit_rules
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/localtime in /etc/audit/audit.rules
      lineinfile:
        line: -w /etc/localtime -p wa -k audit_time_rules
        state: present
        dest: /etc/audit/audit.rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.4.2.b
        - audit_rules_time_watch_localtime
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Events that Modify the System's Network Environment [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, setting ARCH to either b32 or b64 as appropriate for your system:
    -a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, setting ARCH to either b32 or b64 as appropriate for your system:
    -a always,exit -F arch=ARCH -S sethostname,setdomainname -F key=audit_rules_networkconfig_modification
    -w /etc/issue -p wa -k audit_rules_networkconfig_modification
    -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
    -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
    -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
Rationale: | The network environment should not be modified by anything other than administrator action. Any change to network parameters should be audited.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_networkconfig_modification
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.5.5
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # First perform the remediation of the syscall rule
    # Retrieve hardware architecture of the underlying system
    [ "$(getconf LONG_BIT)" = "32" ] && RULE_ARCHS=("b32") || RULE_ARCHS=("b32" "b64")

    for ARCH in "${RULE_ARCHS[@]}"
    do
    	ACTION_ARCH_FILTERS="-a always,exit -F arch=$ARCH"
    	OTHER_FILTERS=""
    	AUID_FILTERS=""
    	SYSCALL="sethostname setdomainname"
    	KEY="audit_rules_networkconfig_modification"
    	SYSCALL_GROUPING="sethostname setdomainname"
    	# Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'augenrules', then check if the audit rule is defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to the list for inspection
    # If rule isn't defined yet, add '/etc/audit/rules.d/$key.rules' to the list for inspection
    default_file="/etc/audit/rules.d/$KEY.rules"
    # As other_filters may include paths, lets use a different delimiter for it
    # The "F" script expression tells sed to print the filenames where the expressions matched
    readarray -t files_to_inspect < <(sed -s -n -e "/$ACTION_ARCH_FILTERS/!d" -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" -e "F" /etc/audit/rules.d/*.rules)
    # Case when particular rule isn't defined in /etc/audit/rules.d/*.rules yet
    if [ ${#files_to_inspect[@]} -eq "0" ]
    then
        file_to_inspect="/etc/audit/rules.d/$KEY.rules"
        files_to_inspect=("$file_to_inspect")
        if [ ! -e "$file_to_inspect" ]
        then
            touch "$file_to_inspect"
            chmod 0640 "$file_to_inspect"
        fi
    fi

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    	unset syscall_a
    unset syscall_grouping
    unset syscall_string
    unset syscall
    unset file_to_edit
    unset rule_to_edit
    unset rule_syscalls_to_edit
    unset other_string
    unset auid_string
    unset full_rule

    # Load macro arguments into arrays
    read -a syscall_a <<< $SYSCALL
    read -a syscall_grouping <<< $SYSCALL_GROUPING

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    #  Tool used to load audit rules | Rule already defined  |  Audit rules file to inspect    |
    # -----------------------------------------------------------------------------------------
    #        auditctl                |     Doesn't matter    |  /etc/audit/audit.rules         |
    # -----------------------------------------------------------------------------------------
    #        augenrules              |          Yes          |  /etc/audit/rules.d/*.rules     |
    #        augenrules              |          No           |  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    #
    files_to_inspect=()

    # If audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # file to the list of files to be inspected
    default_file="/etc/audit/audit.rules"
    files_to_inspect+=('/etc/audit/audit.rules' )

    # After converting to jinja, we cannot return; therefore we skip the rest of the macro if needed instead
    skip=1

    for audit_file in "${files_to_inspect[@]}"
    do
        # Filter existing $audit_file rules' definitions to select those that satisfy the rule pattern,
        # i.e, collect rules that match:
        # * the action, list and arch, (2-nd argument)
        # * the other filters, (3-rd argument)
        # * the auid filters, (4-rd argument)
        readarray -t similar_rules < <(sed -e "/$ACTION_ARCH_FILTERS/!d"  -e "\#$OTHER_FILTERS#!d" -e "/$AUID_FILTERS/!d" "$audit_file")

        candidate_rules=()
        # Filter out rules that have more fields then required. This will remove rules more specific than the required scope
        for s_rule in "${similar_rules[@]}"
        do
            # Strip all the options and fields we know of,
            # than check if there was any field left over
            extra_fields=$(sed -E -e "s/$ACTION_ARCH_FILTERS//"  -e "s#$OTHER_FILTERS##" -e "s/$AUID_FILTERS//" -e "s/((:?-S [[:alnum:],]+)+)//g" -e "s/-F key=\w+|-k \w+//"<<< "$s_rule")
            grep -q -- "-F" <<< "$extra_fields" || candidate_rules+=("$s_rule")
        done

        if [[ ${#syscall_a[@]} -ge 1 ]]
        then
            # Check if the syscall we want is present in any of the similar existing rules
            for rule in "${candidate_rules[@]}"
            do
                rule_syscalls=$(echo "$rule" | grep -o -P '(-S [\w,]+)+' | xargs)
                all_syscalls_found=0
                for syscall in "${syscall_a[@]}"
                do
                    grep -q -- "\b${syscall}\b" <<< "$rule_syscalls" || {
                       # A syscall was not found in the candidate rule
                       all_syscalls_found=1
                       }
                done
                if [[ $all_syscalls_found -eq 0 ]]
                then
                    # We found a rule with all the syscall(s) we want; skip rest of macro
                    skip=0
                    break
                fi

                # Check if this rule can be grouped with our target syscall and keep track of it
                for syscall_g in "${syscall_grouping[@]}"
                do
                    if grep -q -- "\b${syscall_g}\b" <<< "$rule_syscalls"
                    then
                        file_to_edit=${audit_file}
                        rule_to_edit=${rule}
                        rule_syscalls_to_edit=${rule_syscalls}
                    fi
                done
            done
        else
            # If there is any candidate rule, it is compliant; skip rest of macro
            if [ "${#candidate_rules[@]}" -gt 0 ]
            then
                skip=0
            fi
        fi

        if [ "$skip" -eq 0 ]; then
            break
        fi
    done

    if [ "$skip" -ne 0 ]; then
        # We checked all rules that matched the expected resemblance pattern (action, arch & auid)
        # At this point we know if we need to either append the $full_rule or group
        # the syscall together with an exsiting rule

        # Append the full_rule if it cannot be grouped to any other rule
        if [ -z ${rule_to_edit+x} ]
        then
            # Build full_rule while avoid adding double spaces when other_filters is empty
            if [ "${#syscall_a[@]}" -gt 0 ]
            then
                syscall_string=""
                for syscall in "${syscall_a[@]}"
                do
                    syscall_string+=" -S $syscall"
                done
            fi
            other_string=$([[ $OTHER_FILTERS ]] && echo " $OTHER_FILTERS") || /bin/true
            auid_string=$([[ $AUID_FILTERS ]] && echo " $AUID_FILTERS") || /bin/true
            full_rule="$ACTION_ARCH_FILTERS${syscall_string}${other_string}${auid_string} -F key=$KEY" || /bin/true
            echo "$full_rule" >> "$default_file"
            chmod o-rwx ${default_file}
        else
            # Check if the syscalls are declared as a comma separated list or
            # as multiple -S parameters
            if grep -q -- "," <<< "${rule_syscalls_to_edit}"
            then
                delimiter=","
            else
                delimiter=" -S "
            fi
            new_grouped_syscalls="${rule_syscalls_to_edit}"
            for syscall in "${syscall_a[@]}"
            do
                grep -q -- "\b${syscall}\b" <<< "${rule_syscalls_to_edit}" || {
                   # A syscall was not found in the candidate rule
                   new_grouped_syscalls+="${delimiter}${syscall}"
                   }
            done

            # Group the syscall in the rule
            sed -i -e "\#${rule_to_edit}#s#${rule_syscalls_to_edit}#${new_grouped_syscalls}#" "$file_to_edit"
        fi
    fi
    done

    # Then perform the remediations for the watch rules
    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/issue" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
        # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/issue" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/issue$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/issue -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/issue.net" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
        # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/issue.net" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/issue.net $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/issue.net$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/issue.net -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/hosts" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/hosts" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
        # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/hosts" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/hosts $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/hosts$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/hosts -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sysconfig/network" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_networkconfig_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
        # If the audit_rules_networkconfig_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sysconfig/network" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sysconfig/network $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sysconfig/network$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification" >> "$audit_rules_file"
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set architecture for audit tasks
      set_fact:
        audit_arch: b{{ ansible_architecture | regex_replace('.*(\d\d$)','\1') }}
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Remediate audit rules for network configuration for x86
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - sethostname
              - setdomainname
            syscall_grouping:
              - sethostname
              - setdomainname

        - name: Check existence of sethostname, setdomainname in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - sethostname
              - setdomainname
            syscall_grouping:
              - sethostname
              - setdomainname

        - name: Check existence of sethostname, setdomainname in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b32(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b32)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b32 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Remediate audit rules for network configuration for x86_64
      block:

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - sethostname
              - setdomainname
            syscall_grouping:
              - sethostname
              - setdomainname

        - name: Check existence of sethostname, setdomainname in /etc/audit/rules.d/
          find:
            paths: /etc/audit/rules.d
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: '*.rules'
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Reset syscalls found per file
          set_fact:
            syscalls_per_file: {}
            found_paths_dict: {}

        - name: Declare syscalls found per file
          set_fact: syscalls_per_file="{{ syscalls_per_file | combine( {item.files[0].path
            :[item.item] + syscalls_per_file.get(item.files[0].path, []) } ) }}"
          loop: '{{ find_command.results | selectattr(''matched'') | list }}'

        - name: Declare files where syscalls were found
          set_fact: found_paths="{{ find_command.results | map(attribute='files') | flatten
            | map(attribute='path') | list }}"

        - name: Count occurrences of syscalls in paths
          set_fact: found_paths_dict="{{ found_paths_dict | combine({ item:1+found_paths_dict.get(item,
            0) }) }}"
          loop: '{{ find_command.results | map(attribute=''files'') | flatten | map(attribute=''path'')
            | list }}'

        - name: Get path with most syscalls
          set_fact: audit_file="{{ (found_paths_dict | dict2items() | sort(attribute='value')
            | last).key }}"
          when: found_paths | length >= 1

        - name: No file with syscall found, set path to /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
          set_fact: audit_file="/etc/audit/rules.d/audit_rules_networkconfig_modification.rules"
          when: found_paths | length == 0

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_per_file[audit_file]
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0

        - name: Declare list of syscalls
          set_fact:
            syscalls:
              - sethostname
              - setdomainname
            syscall_grouping:
              - sethostname
              - setdomainname

        - name: Check existence of sethostname, setdomainname in /etc/audit/audit.rules
          find:
            paths: /etc/audit
            contains: -a always,exit -F arch=b64(( -S |,)\w+)*(( -S |,){{ item }})+((
              -S |,)\w+)* (-k\s+|-F\s+key=)\S+\s*$
            patterns: audit.rules
          register: find_command
          loop: '{{ (syscall_grouping + syscalls) | unique }}'

        - name: Set path to /etc/audit/audit.rules
          set_fact: audit_file="/etc/audit/audit.rules"

        - name: Declare found syscalls
          set_fact: syscalls_found="{{ find_command.results | selectattr('matched') |
            map(attribute='item') | list }}"

        - name: Declare missing syscalls
          set_fact: missing_syscalls="{{ syscalls | difference(syscalls_found) }}"

        - name: Replace the audit rule in {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            regexp: (-a always,exit -F arch=b64)(?=.*(?:(?:-S |,)(?:{{ syscalls_found
              | join("|") }}))\b)((?:( -S |,)\w+)+)( (?:-k |-F key=)\w+)
            line: \1\2\3{{ missing_syscalls | join("\3") }}\4
            backrefs: true
            state: present
          when: syscalls_found | length > 0 and missing_syscalls | length > 0

        - name: Add the audit rule to {{ audit_file }}
          lineinfile:
            path: '{{ audit_file }}'
            line: -a always,exit -F arch=b64 -S {{ syscalls | join(',') }} -F key=audit_rules_networkconfig_modification
            create: true
            mode: o-rwx
            state: present
          when: syscalls_found | length == 0
      when:
        - '"audit" in ansible_facts.packages'
        - audit_arch == "b64"
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/issue already exists in /etc/audit/rules.d/
      find:
        paths: /etc/audit/rules.d
        contains: ^\s*-w\s+/etc/issue\s+-p\s+wa(\s|$)+
        patterns: '*.rules'
      register: find_existing_watch_rules_d
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other rules with specified key audit_rules_networkconfig_modification
      find:
        paths: /etc/audit/rules.d
        contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
        patterns: '*.rules'
      register: find_watch_key
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules as the
        recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/issue in /etc/audit/rules.d/
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /etc/issue -p wa -k audit_rules_networkconfig_modification
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/issue already exists in /etc/audit/audit.rules
      find:
        paths: /etc/audit/
        contains: ^\s*-w\s+/etc/issue\s+-p\s+wa(\s|$)+
        patterns: audit.rules
      register: find_existing_watch_audit_rules
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/issue in /etc/audit/audit.rules
      lineinfile:
        line: -w /etc/issue -p wa -k audit_rules_networkconfig_modification
        state: present
        dest: /etc/audit/audit.rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/issue.net already exists in /etc/audit/rules.d/
      find:
        paths: /etc/audit/rules.d
        contains: ^\s*-w\s+/etc/issue.net\s+-p\s+wa(\s|$)+
        patterns: '*.rules'
      register: find_existing_watch_rules_d
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other rules with specified key audit_rules_networkconfig_modification
      find:
        paths: /etc/audit/rules.d
        contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
        patterns: '*.rules'
      register: find_watch_key
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules as the
        recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/issue.net in /etc/audit/rules.d/
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/issue.net already exists in /etc/audit/audit.rules
      find:
        paths: /etc/audit/
        contains: ^\s*-w\s+/etc/issue.net\s+-p\s+wa(\s|$)+
        patterns: audit.rules
      register: find_existing_watch_audit_rules
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/issue.net in /etc/audit/audit.rules
      lineinfile:
        line: -w /etc/issue.net -p wa -k audit_rules_networkconfig_modification
        state: present
        dest: /etc/audit/audit.rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/hosts already exists in /etc/audit/rules.d/
      find:
        paths: /etc/audit/rules.d
        contains: ^\s*-w\s+/etc/hosts\s+-p\s+wa(\s|$)+
        patterns: '*.rules'
      register: find_existing_watch_rules_d
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other rules with specified key audit_rules_networkconfig_modification
      find:
        paths: /etc/audit/rules.d
        contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
        patterns: '*.rules'
      register: find_watch_key
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules as the
        recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/hosts in /etc/audit/rules.d/
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/hosts already exists in /etc/audit/audit.rules
      find:
        paths: /etc/audit/
        contains: ^\s*-w\s+/etc/hosts\s+-p\s+wa(\s|$)+
        patterns: audit.rules
      register: find_existing_watch_audit_rules
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/hosts in /etc/audit/audit.rules
      lineinfile:
        line: -w /etc/hosts -p wa -k audit_rules_networkconfig_modification
        state: present
        dest: /etc/audit/audit.rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/sysconfig/network already exists in /etc/audit/rules.d/
      find:
        paths: /etc/audit/rules.d
        contains: ^\s*-w\s+/etc/sysconfig/network\s+-p\s+wa(\s|$)+
        patterns: '*.rules'
      register: find_existing_watch_rules_d
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for other rules with specified key audit_rules_networkconfig_modification
      find:
        paths: /etc/audit/rules.d
        contains: ^.*(?:-F key=|-k\s+)audit_rules_networkconfig_modification$
        patterns: '*.rules'
      register: find_watch_key
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/audit_rules_networkconfig_modification.rules as the
        recipient for the rule
      set_fact:
        all_files:
          - /etc/audit/rules.d/audit_rules_networkconfig_modification.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched == 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_files:
          - '{{ find_watch_key.files | map(attribute=''path'') | list | first }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_watch_key.matched is defined and find_watch_key.matched > 0 and find_existing_watch_rules_d.matched
          is defined and find_existing_watch_rules_d.matched == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/sysconfig/network in /etc/audit/rules.d/
      lineinfile:
        path: '{{ all_files[0] }}'
        line: -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_rules_d.matched is defined and find_existing_watch_rules_d.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Check if watch rule for /etc/sysconfig/network already exists in /etc/audit/audit.rules
      find:
        paths: /etc/audit/
        contains: ^\s*-w\s+/etc/sysconfig/network\s+-p\s+wa(\s|$)+
        patterns: audit.rules
      register: find_existing_watch_audit_rules
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Add watch rule for /etc/sysconfig/network in /etc/audit/audit.rules
      lineinfile:
        line: -w /etc/sysconfig/network -p wa -k audit_rules_networkconfig_modification
        state: present
        dest: /etc/audit/audit.rules
        create: true
        mode: '0640'
      when:
        - '"audit" in ansible_facts.packages'
        - find_existing_watch_audit_rules.matched is defined and find_existing_watch_audit_rules.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.5.5
        - audit_rules_networkconfig_modification
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Ensure auditd Collects System Administrator Actions [ref]
At a minimum, the audit system should collect administrator actions for all users and root. If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following line to a file with suffix `.rules` in the directory `/etc/audit/rules.d`:
    -w /etc/sudoers -p wa -k actions
    -w /etc/sudoers.d/ -p wa -k actions
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following line to `/etc/audit/audit.rules` file:
    -w /etc/sudoers -p wa -k actions
    -w /etc/sudoers.d/ -p wa -k actions
Rationale: | The actions taken by system administrators should be audited to keep a record of what was executed on the system, as well as, for accountability purposes.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_sysadmin_actions
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, 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 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.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, AC-2(7)(b), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1.1.c, Req-10.2.2, Req-10.2.5.b, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000304-GPOS-00121, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000470-GPOS-00214, SRG-OS-000471-GPOS-00215, SRG-OS-000239-GPOS-00089, SRG-OS-000240-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000304-GPOS-00121, SRG-OS-000466-GPOS-00210, SRG-OS-000476-GPOS-00221, SRG-OS-000462-VMM-001840, SRG-OS-000471-VMM-001910
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sudoers" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sudoers$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sudoers -p wa -k actions" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/actions.rules"
        # If the actions.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sudoers" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sudoers$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sudoers -p wa -k actions" >> "$audit_rules_file"
        fi
    done

    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sudoers.d/" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers.d/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sudoers.d/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/actions.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/sudoers.d/" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/actions.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/actions.rules"
        # If the actions.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/sudoers.d/" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/sudoers.d/ $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/sudoers.d/$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/sudoers.d/ -p wa -k actions" >> "$audit_rules_file"
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Search /etc/audit/rules.d for audit rule entries for sysadmin actions
      find:
        paths: /etc/audit/rules.d
        recurse: false
        contains: ^.*/etc/sudoers.*$
        patterns: '*.rules'
      register: find_audit_sysadmin_actions
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use /etc/audit/rules.d/actions.rules as the recipient for the rule
      set_fact:
        all_sysadmin_actions_files:
          - /etc/audit/rules.d/actions.rules
      when:
        - '"audit" in ansible_facts.packages'
        - find_audit_sysadmin_actions.matched is defined and find_audit_sysadmin_actions.matched
          == 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Use matched file as the recipient for the rule
      set_fact:
        all_sysadmin_actions_files:
          - '{{ find_audit_sysadmin_actions.files | map(attribute=''path'') | list | first
            }}'
      when:
        - '"audit" in ansible_facts.packages'
        - find_audit_sysadmin_actions.matched is defined and find_audit_sysadmin_actions.matched
          > 0
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Inserts/replaces audit rule for /etc/sudoers rule in rules.d
      lineinfile:
        path: '{{ all_sysadmin_actions_files[0] }}'
        line: -w /etc/sudoers -p wa -k actions
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Inserts/replaces audit rule for /etc/sudoers.d rule in rules.d
      lineinfile:
        path: '{{ all_sysadmin_actions_files[0] }}'
        line: -w /etc/sudoers.d/ -p wa -k actions
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Inserts/replaces audit rule for /etc/sudoers in audit.rules
      lineinfile:
        path: /etc/audit/audit.rules
        line: -w /etc/sudoers -p wa -k actions
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Inserts/replaces audit rule for /etc/sudoers.d in audit.rules
      lineinfile:
        path: /etc/audit/audit.rules
        line: -w /etc/sudoers.d/ -p wa -k actions
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - NIST-800-171-3.1.7
        - NIST-800-53-AC-2(7)(b)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-CM-6(a)
        - PCI-DSS-Req-10.2.2
        - PCI-DSS-Req-10.2.5.b
        - audit_rules_sysadmin_actions
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Record Events that Modify User/Group Information [ref]
If the `auditd` daemon is configured to use the `augenrules` program to read audit rules during daemon startup (the default), add the following lines to a file with suffix `.rules` in the directory `/etc/audit/rules.d`, in order to capture events that modify account changes:
    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
If the `auditd` daemon is configured to use the `auditctl` utility to read audit rules during daemon startup, add the following lines to `/etc/audit/audit.rules` file, in order to capture events that modify account changes:
    -w /etc/group -p wa -k audit_rules_usergroup_modification
    -w /etc/passwd -p wa -k audit_rules_usergroup_modification
    -w /etc/gshadow -p wa -k audit_rules_usergroup_modification
    -w /etc/shadow -p wa -k audit_rules_usergroup_modification
    -w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification
Warning: This rule checks for multiple syscalls related to account changes; it was written with DISA STIG in mind. Other policies should use a separate rule for each syscall that needs to be checked. For example: `audit_rules_usergroup_modification_group` `audit_rules_usergroup_modification_gshadow` `audit_rules_usergroup_modification_passwd`
Rationale: | In addition to auditing new user and group accounts, these watches will alert the system administrator(s) to any modifications. Any unexpected users, groups, or modifications should be investigated for legitimacy.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_audit_rules_usergroup_modification
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.03, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.1.7, CCI-000018, CCI-000130, CCI-000172, CCI-001403, CCI-002130, 4.2.3.10, 4.3.2.6.7, 4.3.3.2.2, 4.3.3.3.9, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.8, 4.3.3.6.6, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.1, SR 1.13, 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 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.1.2, A.6.2.1, A.6.2.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, 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, AC-2(4), AU-2(d), AU-12(c), AC-6(9), CM-6(a), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-1, PR.AC-3, PR.AC-4, PR.AC-6, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, Req-10.2.5, SRG-OS-000004-GPOS-00004, SRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000239-GPOS-00089, SRG-OS-000241-GPOS-00090, SRG-OS-000241-GPOS-00091, SRG-OS-000303-GPOS-00120, SRG-OS-000392-GPOS-00172, SRG-OS-000462-GPOS-00206, SRG-OS-000471-GPOS-00215, SRG-OS-000476-GPOS-00221
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    # Perform the remediation for both possible tools: 'auditctl' and 'augenrules'
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/group" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/group" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
        # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/group" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/group $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/group$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/group -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/passwd" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/passwd" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
        # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/passwd" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/passwd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/passwd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/passwd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/gshadow" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
        # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/gshadow" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/gshadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/gshadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/gshadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/shadow" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/shadow" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
        # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/shadow" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/shadow $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/shadow$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/shadow -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit tool is 'auditctl', then add '/etc/audit/audit.rules'
    # into the list of files to be inspected
    files_to_inspect+=('/etc/audit/audit.rules')

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done
    # Create a list of audit *.rules files that should be inspected for presence and correctness
    # of a particular audit rule. The scheme is as follows:
    #
    # -----------------------------------------------------------------------------------------
    # Tool used to load audit rules	| Rule already defined	|  Audit rules file to inspect	  |
    # -----------------------------------------------------------------------------------------
    #	auditctl		|     Doesn't matter	|  /etc/audit/audit.rules	  |
    # -----------------------------------------------------------------------------------------
    # 	augenrules		|          Yes		|  /etc/audit/rules.d/*.rules	  |
    # 	augenrules		|          No		|  /etc/audit/rules.d/$key.rules  |
    # -----------------------------------------------------------------------------------------
    files_to_inspect=()

    # If the audit is 'augenrules', then check if rule is already defined
    # If rule is defined, add '/etc/audit/rules.d/*.rules' to list of files for inspection.
    # If rule isn't defined, add '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' to list of files for inspection.
    readarray -t matches < <(grep -HP "[\s]*-w[\s]+/etc/security/opasswd" /etc/audit/rules.d/*.rules)

    # For each of the matched entries
    for match in "${matches[@]}"
    do
        # Extract filepath from the match
        rulesd_audit_file=$(echo $match | cut -f1 -d ':')
        # Append that path into list of files for inspection
        files_to_inspect+=("$rulesd_audit_file")
    done
    # Case when particular audit rule isn't defined yet
    if [ "${#files_to_inspect[@]}" -eq "0" ]
    then
        # Append '/etc/audit/rules.d/audit_rules_usergroup_modification.rules' into list of files for inspection
        key_rule_file="/etc/audit/rules.d/audit_rules_usergroup_modification.rules"
        # If the audit_rules_usergroup_modification.rules file doesn't exist yet, create it with correct permissions
        if [ ! -e "$key_rule_file" ]
        then
            touch "$key_rule_file"
            chmod 0640 "$key_rule_file"
        fi
        files_to_inspect+=("$key_rule_file")
    fi

    # Finally perform the inspection and possible subsequent audit rule
    # correction for each of the files previously identified for inspection
    for audit_rules_file in "${files_to_inspect[@]}"
    do
        # Check if audit watch file system object rule for given path already present
        if grep -q -P -- "[\s]*-w[\s]+/etc/security/opasswd" "$audit_rules_file"
        then
            # Rule is found => verify yet if existing rule definition contains
            # all of the required access type bits

            # Define BRE whitespace class shortcut
            sp="[[:space:]]"
            # Extract current permission access types (e.g. -p [r|w|x|a] values) from audit rule
            current_access_bits=$(sed -ne "s#$sp*-w$sp\+/etc/security/opasswd $sp\+-p$sp\+\([rxwa]\{1,4\}\).*#\1#p" "$audit_rules_file")
            # Split required access bits string into characters array
            # (to check bit's presence for one bit at a time)
            for access_bit in $(echo "wa" | grep -o .)
            do
                # For each from the required access bits (e.g. 'w', 'a') check
                # if they are already present in current access bits for rule.
                # If not, append that bit at the end
                if ! grep -q "$access_bit" <<< "$current_access_bits"
                then
                    # Concatenate the existing mask with the missing bit
                    current_access_bits="$current_access_bits$access_bit"
                fi
            done
            # Propagate the updated rule's access bits (original + the required
            # ones) back into the /etc/audit/audit.rules file for that rule
            sed -i "s#\($sp*-w$sp\+/etc/security/opasswd$sp\+-p$sp\+\)\([rxwa]\{1,4\}\)\(.*\)#\1$current_access_bits\3#" "$audit_rules_file"
        else
            # Rule isn't present yet. Append it at the end of $audit_rules_file file
            # with proper key

            echo "-w /etc/security/opasswd -p wa -k audit_rules_usergroup_modification" >> "$audit_rules_file"
        fi
    done

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Group Configure auditd Data Retention Group contains 6 rules
[ref] The audit system writes data to `/var/log/audit/audit.log`. By default, `auditd` rotates 5 logs by size (6MB), retaining a maximum of 30MB of data in total, and refuses to write entries when the disk is too full. This minimizes the risk of audit data filling its partition and impacting other services. This also minimizes the risk of the audit daemon temporarily disabling the system if it cannot write audit log (which it can be configured to do). For a busy system or a system which is thoroughly auditing system activity, the default settings for data retention may be insufficient. The log file size needed will depend heavily on what types of events are being audited. First configure auditing to log all the events of interest. Then monitor the log size manually for awhile to determine what file size will allow you to keep the required data for the correct time period. Using a dedicated partition for `/var/log/audit` prevents the `auditd` logs from disrupting system functionality if they fill, and, more importantly, prevents other activity in `/var` from filling the partition and stopping the audit trail. (The audit logs are size-limited and therefore unlikely to grow without bound unless configured to do so.) Some machines may have requirements that no actions occur which cannot be audited. If this is the case, then `auditd` can be configured to halt the machine if it runs out of space. Note: Since older logs are rotated, configuring `auditd` this way does not prevent older logs from being rotated away before they can be viewed. If your system is configured to halt when logging cannot be performed, make sure this can never happen under normal circumstances! Ensure that `/var/log/audit` is on its own partition, and that this partition is larger than the maximum amount of data `auditd` will retain normally.
#### Rule Configure auditd flush priority [ref]
The `auditd` service can be configured to synchronously write audit event data to disk. Add or correct the following line in `/etc/audit/auditd.conf` to ensure that audit event data is fully synchronized with the log files on the disk:
    flush = incremental_async
Rationale: | Audit data should be synchronously written to disk to ensure log integrity. These parameters assure that all audit event data is fully synchronized with the log files on the disk.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_data_retention_flush
Identifiers and References | References: 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, BAI03.05, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.3.1, CCI-001576, 164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, CIP-004-6 R2.2.3, CIP-004-6 R3.3, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3, CIP-007-3 R6.5, AU-11, CM-6(a), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.PT-1, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    var_auditd_flush='incremental_async'

    AUDITCONFIG=/etc/audit/auditd.conf

    # if flush is present, flush param edited to var_auditd_flush
    # else flush param is defined by var_auditd_flush
    #
    # the freq param is only used for values 'incremental' and 'incremental_async' and will be
    # commented out if flush != incremental or flush != incremental_async
    #
    # if flush == incremental or flush == incremental_async && freq param is not defined, it
    # will be defined as the package-default value of 20

    grep -q ^flush $AUDITCONFIG && \
      sed -i 's/^flush.*/flush = '"$var_auditd_flush"'/g' $AUDITCONFIG
    if ! [ $? -eq 0 ]; then
      echo "flush = $var_auditd_flush" >> $AUDITCONFIG
    fi

    if ! [ "$var_auditd_flush" == "incremental" ] && ! [ "$var_auditd_flush" == "incremental_async" ]; then
      sed -i 's/^freq/##freq/g' $AUDITCONFIG
    elif [ "$var_auditd_flush" == "incremental" ] || [ "$var_auditd_flush" == "incremental_async" ]; then
      grep -q freq $AUDITCONFIG && \
        sed -i 's/^#\+freq/freq/g' $AUDITCONFIG
      if ! [ $? -eq 0 ]; then
        echo "freq = 20" >> $AUDITCONFIG
      fi
    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - NIST-800-171-3.3.1
        - NIST-800-53-AU-11
        - NIST-800-53-CM-6(a)
        - auditd_data_retention_flush
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
    - name: XCCDF Value var_auditd_flush # promote to variable
      set_fact:
        var_auditd_flush: !!str incremental_async
      tags:
        - always

    - name: Configure auditd Flush Priority
      lineinfile:
        dest: /etc/audit/auditd.conf
        regexp: ^\s*flush\s*=\s*.*$
        line: flush = {{ var_auditd_flush }}
        state: present
        create: true
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-171-3.3.1
        - NIST-800-53-AU-11
        - NIST-800-53-CM-6(a)
        - auditd_data_retention_flush
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Set number of records to cause an explicit flush to audit logs [ref]
To configure Audit daemon to issue an explicit flush to disk command after writing 50 records, set `freq` to `50` in `/etc/audit/auditd.conf`.
Rationale: | If option `freq` isn't set to `50`, the flush to disk may happen after higher number of records, increasing the danger of audit loss.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_freq
Identifiers and References | References: CM-6, FAU_GEN.1, SRG-OS-000051-GPOS-00024
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

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

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "freq = 50" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - NIST-800-53-CM-6
        - auditd_freq
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set number of records to cause an explicit flush to audit logs
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*freq\s*=\s*
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*freq\s*=\s*
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: true
            regexp: (?i)^\s*freq\s*=\s*
            line: freq = 50
            state: present
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-53-CM-6
        - auditd_freq
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Include Local Events in Audit Logs [ref]
To configure Audit daemon to include local events in Audit logs, set `local_events` to `yes` in `/etc/audit/auditd.conf`. This is the default setting.
Rationale: | If option `local_events` isn't set to `yes` only events from network will be aggregated.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_local_events
Identifiers and References | References: CCI-000366, CM-6, FAU_GEN.1, SRG-OS-000062-GPOS-00031, SRG-OS-000480-GPOS-00227, OL08-00-030061, SV-248729r779753_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

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

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "local_events = yes" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030061
        - NIST-800-53-CM-6
        - auditd_local_events
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Include Local Events in Audit Logs
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*local_events\s*=\s*
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*local_events\s*=\s*
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: true
            regexp: (?i)^\s*local_events\s*=\s*
            line: local_events = yes
            state: present
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030061
        - NIST-800-53-CM-6
        - auditd_local_events
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Resolve information before writing to audit logs [ref]
To configure Audit daemon to resolve all uid, gid, syscall, architecture, and socket address information before writing the events to disk, set `log_format` to `ENRICHED` in `/etc/audit/auditd.conf`.
Rationale: | If option `log_format` isn't set to `ENRICHED`, the audit records will be stored in a format exactly as the kernel sends them.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_log_format
Identifiers and References | References: CCI-000366, CM-6, AU-3, FAU_GEN.1.2, SRG-OS-000255-GPOS-00096, SRG-OS-000480-GPOS-00227, OL08-00-030063, SV-248731r779759_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

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

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "log_format = ENRICHED" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030063
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6
        - auditd_log_format
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Resolve information before writing to audit logs
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*log_format\s*=\s*
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*log_format\s*=\s*
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: true
            regexp: (?i)^\s*log_format\s*=\s*
            line: log_format = ENRICHED
            state: present
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030063
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6
        - auditd_log_format
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Set hostname as computer node name in audit logs [ref]
To configure Audit daemon to use value returned by gethostname syscall as computer node name in the audit events, set `name_format` to `hostname` in `/etc/audit/auditd.conf`.
Rationale: | If option `name_format` is left at its default value of `none`, audit events from different computers may be hard to distinguish.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_name_format
Identifiers and References | References: CCI-001851, CM-6, AU-3, FAU_GEN.1.2, SRG-OS-000039-GPOS-00017, SRG-OS-000342-GPOS-00133, SRG-OS-000479-GPOS-00224, OL08-00-030062, SV-248730r779756_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

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

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "name_format = hostname" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - DISA-STIG-OL08-00-030062
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6
        - auditd_name_format
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set hostname as computer node name in audit logs
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*name_format\s*=\s*
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*name_format\s*=\s*
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: true
            regexp: (?i)^\s*name_format\s*=\s*
            line: name_format = hostname
            state: present
      when: '"audit" in ansible_facts.packages'
      tags:
        - DISA-STIG-OL08-00-030062
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6
        - auditd_name_format
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Write Audit Logs to the Disk [ref]
To configure Audit daemon to write Audit logs to the disk, set `write_logs` to `yes` in `/etc/audit/auditd.conf`. This is the default setting.
Rationale: | If `write_logs` isn't set to `yes`, the Audit logs will not be written to the disk.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_auditd_write_logs
Identifiers and References | References: CM-6, FAU_STG.1, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if rpm --quiet -q audit; then

    if [ -e "/etc/audit/auditd.conf" ] ; then

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

    cp "/etc/audit/auditd.conf" "/etc/audit/auditd.conf.bak"
    # Insert at the end of the file
    printf '%s\n' "write_logs = yes" >> "/etc/audit/auditd.conf"
    # Clean up after ourselves.
    rm "/etc/audit/auditd.conf.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - NIST-800-53-CM-6
        - auditd_write_logs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Write Audit Logs to the Disk
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*write_logs\s*=\s*
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: false
            regexp: (?i)^\s*write_logs\s*=\s*
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/audit/auditd.conf
          lineinfile:
            path: /etc/audit/auditd.conf
            create: true
            regexp: (?i)^\s*write_logs\s*=\s*
            line: write_logs = yes
            state: present
      when: '"audit" in ansible_facts.packages'
      tags:
        - NIST-800-53-CM-6
        - auditd_write_logs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Enable auditd Service [ref]
The `auditd` service is an essential userspace component of the Linux Auditing System, as it is responsible for writing audit records to disk. The `auditd` service can be enabled with the following command:
    $ sudo systemctl enable auditd.service
Rationale: | Without establishing what type of events occurred, it would be difficult to establish, correlate, and investigate the events leading up to an outage or attack. Ensuring the `auditd` service is active ensures audit records generated by the kernel are appropriately recorded. Additionally, a properly configured audit subsystem ensures that actions of individual system users can be uniquely traced to those users so they can be held accountable for their actions.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_auditd_enabled
Identifiers and References | References: 1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9, 5.4.1.1, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO12.06, APO13.01, BAI03.05, BAI08.02, DSS01.03, DSS01.04, DSS02.02, DSS02.04, DSS02.07, DSS03.01, DSS03.05, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, 3.3.1, 3.3.2, 3.3.6, CCI-000126, CCI-000130, CCI-000131, CCI-000132, CCI-000133, CCI-000134, CCI-000135, CCI-000154, CCI-000158, CCI-000366, CCI-001464, CCI-001487, CCI-001814, CCI-001876, CCI-002884, CCI-000169, 164.308(a)(1)(ii)(D), 164.308(a)(5)(ii)(C), 164.310(a)(2)(iv), 164.310(d)(2)(iii), 164.312(b), 4.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6, A.11.2.6, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.7, A.15.2.1, A.15.2.2, A.16.1.4, A.16.1.5, A.16.1.7, A.6.2.1, A.6.2.2, CIP-004-6 R3.3, CIP-007-3 R6.5, AC-2(g), AU-3, AU-10, AU-2(d), AU-12(c), AU-14(1), AC-6(9), CM-6(a), SI-4(23), DE.AE-3, DE.AE-5, DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.AC-3, PR.PT-1, PR.PT-4, RS.AN-1, RS.AN-4, FAU_GEN.1, Req-10.1, SRG-OS-000037-GPOS-00015, SRG-OS-000038-GPOS-00016, SRG-OS-000039-GPOS-00017, SRG-OS-000040-GPOS-00018, SRG-OS-000041-GPOS-00019, SRG-OS-000042-GPOS-00021, SRG-OS-000051-GPOS-00024, SRG-OS-000054-GPOS-00025, SRG-OS-000122-GPOS-00063, SRG-OS-000254-GPOS-00095, SRG-OS-000255-GPOS-00096, SRG-OS-000365-GPOS-00152, SRG-OS-000392-GPOS-00172, SRG-OS-000480-GPOS-00227, SRG-OS-000062-GPOS-00031, SRG-OS-000037-VMM-000150, SRG-OS-000063-VMM-000310, SRG-OS-000038-VMM-000160, SRG-OS-000039-VMM-000170, SRG-OS-000040-VMM-000180, SRG-OS-000041-VMM-000190, OL08-00-030181, SV-248520r779126_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { rpm --quiet -q audit; }; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'auditd.service'
    "$SYSTEMCTL_EXEC" start 'auditd.service'
    "$SYSTEMCTL_EXEC" enable 'auditd.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Gather the package facts
      package_facts:
        manager: auto
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030181
        - NIST-800-171-3.3.1
        - NIST-800-171-3.3.2
        - NIST-800-171-3.3.6
        - NIST-800-53-AC-2(g)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-10
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-14(1)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-4(23)
        - PCI-DSS-Req-10.1
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_auditd_enabled

    - name: Enable service auditd
      block:

        - name: Gather the package facts
          package_facts:
            manager: auto

        - name: Enable service auditd
          service:
            name: auditd
            enabled: 'yes'
            state: started
            masked: 'no'
          when:
            - '"audit" in ansible_facts.packages'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"audit" in ansible_facts.packages'
      tags:
        - CJIS-5.4.1.1
        - DISA-STIG-OL08-00-030181
        - NIST-800-171-3.3.1
        - NIST-800-171-3.3.2
        - NIST-800-171-3.3.6
        - NIST-800-53-AC-2(g)
        - NIST-800-53-AC-6(9)
        - NIST-800-53-AU-10
        - NIST-800-53-AU-12(c)
        - NIST-800-53-AU-14(1)
        - NIST-800-53-AU-2(d)
        - NIST-800-53-AU-3
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-4(23)
        - PCI-DSS-Req-10.1
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_auditd_enabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include enable_auditd

    class enable_auditd {
      service {'auditd':
        enable => true,
        ensure => 'running',
      }
    }
Remediation Kubernetes snippet ⇲
    ---
    apiVersion: machineconfiguration.openshift.io/v1
    kind: MachineConfig
    spec:
      config:
        ignition:
          version: 3.1.0
        systemd:
          units:
          - name: auditd.service
            enabled: true
Remediation script ⇲
    [customizations.services]
    enabled = ["auditd"]
Group Configure Syslog Group contains 2 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 Oracle Linux 8, 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.
#### Rule Ensure rsyslog is Installed [ref]
Rsyslog is installed by default. The `rsyslog` package can be installed with the following command:
     $ sudo yum install rsyslog
Rationale: | The rsyslog package provides the rsyslog daemon, which provides system logging services.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_rsyslog_installed
Identifiers and References | References: BP28(R5), NT28(R46), 1, 14, 15, 16, 3, 5, 6, APO11.04, BAI03.05, DSS05.04, DSS05.07, MEA02.01, CCI-001311, CCI-001312, CCI-000366, 164.312(a)(2)(ii), 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, CM-6(a), PR.PT-1, FTP_ITC_EXT.1.1, SRG-OS-000479-GPOS-00224, SRG-OS-000051-GPOS-00024, SRG-OS-000480-GPOS-00227, OL08-00-030670, SV-248812r780002_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Ensure rsyslog is installed
      package:
        name: rsyslog
        state: present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-030670
        - NIST-800-53-CM-6(a)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_rsyslog_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include install_rsyslog

    class install_rsyslog {
      package { 'rsyslog':
        ensure => 'installed',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    package --add=rsyslog
Remediation script ⇲
    [[packages]]
    name = "rsyslog"
    version = "*"
#### Rule Enable rsyslog Service [ref]
The `rsyslog` service provides syslog-style logging by default on Oracle Linux 8. The `rsyslog` service can be enabled with the following command:
    $ sudo systemctl enable rsyslog.service
Rationale: | The `rsyslog` service must be running in order to provide logging services, which are essential to system administration.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_rsyslog_enabled
Identifiers and References | References: BP28(R5), NT28(R46), 1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9, APO10.01, APO10.03, APO10.04, APO10.05, APO11.04, APO13.01, BAI03.05, BAI04.04, DSS01.03, DSS03.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, MEA01.01, MEA01.02, MEA01.03, MEA01.04, MEA01.05, MEA02.01, CCI-001311, CCI-001312, CCI-001557, CCI-001851, CCI-000366, 164.312(a)(2)(ii), 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 6.1, SR 6.2, SR 7.1, SR 7.2, A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.14.2.7, A.15.2.1, A.15.2.2, A.17.2.1, CM-6(a), AU-4(1), DE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.DS-4, PR.PT-1, SRG-OS-000480-GPOS-00227, OL08-00-010561, SV-248615r779411_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'rsyslog.service'
    "$SYSTEMCTL_EXEC" start 'rsyslog.service'
    "$SYSTEMCTL_EXEC" enable 'rsyslog.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Enable service rsyslog
      block:

        - name: Gather the package facts
          package_facts:
            manager: auto

        - name: Enable service rsyslog
          service:
            name: rsyslog
            enabled: 'yes'
            state: started
            masked: 'no'
          when:
            - '"rsyslog" in ansible_facts.packages'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010561
        - NIST-800-53-AU-4(1)
        - NIST-800-53-CM-6(a)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_rsyslog_enabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include enable_rsyslog

    class enable_rsyslog {
      service {'rsyslog':
        enable => true,
        ensure => 'running',
      }
    }
Remediation script ⇲
    [customizations.services]
    enabled = ["rsyslog"]
Group Network Configuration and Firewalls Group contains 2 groups and 3 rules
[ref] Most systems must be connected to a network of some sort, and this brings with it the substantial risk of network attack. This section discusses the security impact of decisions about networking which must be made when configuring a system. This section also discusses firewalls, network access controls, and other network security frameworks, which allow system-level rules to be written that can limit an attackers' ability to connect to your system. These rules can specify that network traffic should be allowed or denied from certain IP addresses, hosts, and networks. The rules can also specify which of the system's network services are available to particular hosts or networks.
Group firewalld Group contains 1 group and 2 rules
[ref] The dynamic firewall daemon `firewalld` provides a dynamically managed firewall with support for network “zones” to assign a level of trust to a network and its associated connections and interfaces. It has support for IPv4 and IPv6 firewall settings. It supports Ethernet bridges and has a separation of runtime and permanent configuration options. It also has an interface for services or applications to add firewall rules directly. A graphical configuration tool, `firewall-config`, is used to configure `firewalld`, which in turn uses `iptables` tool to communicate with `Netfilter` in the kernel which implements packet filtering. The firewall service provided by `firewalld` is dynamic rather than static because changes to the configuration can be made at anytime and are immediately implemented. There is no need to save or apply the changes. No unintended disruption of existing network connections occurs as no part of the firewall has to be reloaded.
Group Inspect and Activate Default firewalld Rules Group contains 2 rules
[ref] Firewalls can be used to separate networks into different zones based on the level of trust the user has decided to place on the devices and traffic within that network. `NetworkManager` informs firewalld to which zone an interface belongs. An interface's assigned zone can be changed by `NetworkManager` or via the `firewall-config` tool. The zone settings in `/etc/firewalld/` are a range of preset settings which can be quickly applied to a network interface. These are the zones provided by firewalld sorted according to the default trust level of the zones from untrusted to trusted: `drop` Any incoming network packets are dropped, there is no reply. Only outgoing network connections are possible. `block` Any incoming network connections are rejected with an `icmp-host-prohibited` message for IPv4 and `icmp6-adm-prohibited` for IPv6. Only network connections initiated from within the system are possible. `public` For use in public areas. You do not trust the other computers on the network to not harm your computer. Only selected incoming connections are accepted. `external` For use on external networks with masquerading enabled especially for routers. You do not trust the other computers on the network to not harm your computer. Only selected incoming connections are accepted. `dmz` For computers in your demilitarized zone that are publicly-accessible with limited access to your internal network. Only selected incoming connections are accepted. `work` For use in work areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted. `home` For use in home areas. You mostly trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted. `internal` For use on internal networks. You mostly trust the other computers on the networks to not harm your computer. Only selected incoming connections are accepted. `trusted` All network connections are accepted. It is possible to designate one of these zones to be the default zone. When interface connections are added to `NetworkManager`, they are assigned to the default zone. On installation, the default zone in firewalld is set to be the public zone. To find out all the settings of a zone, for example the `public zone,` enter the following command as root:
    # firewall-cmd --zone=public --list-all
Example output of this command might look like the following:
    # firewall-cmd --zone=public --list-all
    public
      interfaces:
      services: mdns dhcpv6-client ssh
      ports:
      forward-ports:
      icmp-blocks: source-quench
To view the network zones currently active, enter the following command as root:
    # firewall-cmd --get-service
The following listing displays the result of this command on common Oracle Linux 8 system:
    # firewall-cmd --get-service
    amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns ftp
    high-availability http https imaps ipp ipp-client ipsec kerberos kpasswd
    ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn
    pmcd pmproxy pmwebapi pmwebapis pop3s postgresql proxy-dhcp radius rpc-bind
    samba samba-client smtp ssh telnet tftp tftp-client transmission-client
    vnc-server wbem-https
Finally to view the network zones that will be active after the next firewalld service reload, enter the following command as root:
    # firewall-cmd --get-service --permanent
#### Rule Install firewalld Package [ref]
The `firewalld` package can be installed with the following command:
    $ sudo yum install firewalld
Rationale: | The firewalld package should be installed to provide access control methods.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_firewalld_installed
Identifiers and References | References: CCI-002314, CM-6(a), 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, OL08-00-040100, SV-248840r780086_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Ensure firewalld is installed
      package:
        name: firewalld
        state: present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040100
        - NIST-800-53-CM-6(a)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_firewalld_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include install_firewalld

    class install_firewalld {
      package { 'firewalld':
        ensure => 'installed',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    package --add=firewalld
Remediation script ⇲
    [[packages]]
    name = "firewalld"
    version = "*"
#### 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
Identifiers and References | References: 11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.3, 3.4.7, CCI-000366, CCI-000382, CCI-002314, 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, CIP-003-8 R4, CIP-003-8 R5, CIP-004-6 R3, AC-4, CM-7(b), CA-3(5), SC-7(21), CM-6(a), PR.IP-1, FMT_MOF_EXT.1, 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, OL08-00-040101, SV-248841r780089_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'firewalld.service'
    "$SYSTEMCTL_EXEC" start 'firewalld.service'
    "$SYSTEMCTL_EXEC" enable 'firewalld.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Enable service firewalld
      block:

        - name: Gather the package facts
          package_facts:
            manager: auto

        - name: Enable service firewalld
          service:
            name: firewalld
            enabled: 'yes'
            state: started
            masked: 'no'
          when:
            - '"firewalld" in ansible_facts.packages'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040101
        - 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)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_firewalld_enabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include enable_firewalld

    class enable_firewalld {
      service {'firewalld':
        enable => true,
        ensure => 'running',
      }
    }
Remediation script ⇲
    [customizations.services]
    enabled = ["firewalld"]
#### Rule Ensure System is Not Acting as a Network Sniffer [ref]
The system should not be acting as a network sniffer, which can capture all traffic on the network to which it is connected. Run the following to determine if any interface is running in promiscuous mode:
    $ ip link | grep PROMISC
Promiscuous mode of an interface can be disabled with the following command:
    $ sudo ip link set dev device_name multicast off promisc off
Rationale: | Network interfaces in promiscuous mode allow for the capture of all network traffic visible to the system. If unauthorized individuals can access these applications, it may allow them to collect information such as logon IDs, passwords, and key exchanges between systems. If the system is being used to perform a network troubleshooting function, the use of these tools must be documented with the Information Systems Security Manager (ISSM) and restricted to only authorized personnel.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_network_sniffer_disabled
Identifiers and References | References: 1, 11, 14, 3, 9, APO11.06, APO12.06, BAI03.10, BAI09.01, BAI09.02, BAI09.03, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.05, DSS04.05, DSS05.02, DSS05.05, DSS06.06, CCI-000366, 4.2.3.4, 4.3.3.3.7, 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.4, 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, SR 7.8, A.11.1.2, A.11.2.4, A.11.2.5, A.11.2.6, 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.16.1.6, A.8.1.1, A.8.1.2, A.9.1.2, CM-7(a), CM-7(b), CM-6(a), CM-7(2), MA-3, DE.DP-5, ID.AM-1, PR.IP-1, PR.MA-1, PR.PT-3, SRG-OS-000480-GPOS-00227, OL08-00-040330, SV-248899r780263_rule
Group File Permissions and Masks Group contains 5 groups and 18 rules
[ref] Traditional Unix security relies heavily on file and directory permissions to prevent unauthorized users from reading or modifying files to which they should not have access. Several of the commands in this section search filesystems for files or directories with certain characteristics, and are intended to be run on every local partition on a given system. When the variable PART appears in one of the commands below, it means that the command is intended to be run repeatedly, with the name of each local partition substituted for PART in turn. The following command prints a list of all xfs partitions on the local system, which is the default filesystem for Oracle Linux 8 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 8 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 File Permissions Within Some Important Directories Group contains 4 rules
[ref] Some directories contain files whose confidentiality or integrity is notably important and may also be susceptible to misconfiguration over time, particularly if unpackaged software is installed. As such, an argument exists to verify that files' permissions within these directories remain configured correctly and restrictively.
#### Rule Verify that System Executables Have Root Ownership [ref]
System executables are stored in the following directories by default:
    /bin
    /sbin
    /usr/bin
    /usr/libexec
    /usr/local/bin
    /usr/local/sbin
    /usr/sbin
All files in these directories should be owned by the `root` user. If any file FILE in these directories is found to be owned by a user other than root, correct its ownership with the following command:
    $ sudo chown root FILE
Rationale: | System binaries are executed by privileged users as well as system services, and restrictive permissions are necessary to ensure that their execution of these programs cannot be co-opted.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_ownership_binary_dirs
Identifiers and References | References: 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, OL08-00-010310, SV-248568r779270_rule
Remediation Shell script ⇲
    find /bin/ \
    /usr/bin/ \
    /usr/local/bin/ \
    /sbin/ \
    /usr/sbin/ \
    /usr/local/sbin/ \
    /usr/libexec \
    \! -user root -execdir chown root {} \;
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | medium
Strategy: | restrict
    - name: Read list of system executables without root ownership
      command: find /bin/ /usr/bin/ /usr/local/bin/ /sbin/ /usr/sbin/ /usr/local/sbin/
        /usr/libexec \! -user root
      register: no_root_system_executables
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010310
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - file_ownership_binary_dirs
        - medium_complexity
        - medium_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Set ownership to root of system executables
      file:
        path: '{{ item }}'
        owner: root
      with_items: '{{ no_root_system_executables.stdout_lines }}'
      when: no_root_system_executables.stdout_lines | length > 0
      tags:
        - DISA-STIG-OL08-00-010310
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - file_ownership_binary_dirs
        - medium_complexity
        - medium_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Verify that Shared Library Files Have Root Ownership [ref]
System-wide shared library files, which are linked to executables during process load time or run time, are stored in the following directories by default:
    /lib
    /lib64
    /usr/lib
    /usr/lib64
Kernel modules, which can be added to the kernel during runtime, are also stored in `/lib/modules`. All files in these directories should be owned by the `root` user. If the directory, or any file in these directories, is found to be owned by a user other than root correct its ownership with the following command:
    $ sudo chown root FILE
Rationale: | Files from shared library directories are loaded into the address space of processes (including privileged ones) or of the kernel itself at runtime. Proper ownership is necessary to protect the integrity of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_ownership_library_dirs
Identifiers and References | References: 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, OL08-00-010340, SV-248571r779279_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | configure
    find /lib/  -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;

    find /lib64/  -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;

    find /usr/lib/  -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;

    find /usr/lib64/  -type f ! -uid 0 -regex '^.*$' -exec chown 0 {} \;
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | configure
    - name: Find /lib/ file(s) matching ^.*$ recursively
      command: find -H /lib/  -type f ! -uid 0 -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Ensure owner on /lib/ file(s) matching ^.*$
      file:
        path: '{{ item }}'
        owner: '0'
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /lib64/ file(s) matching ^.*$ recursively
      command: find -H /lib64/  -type f ! -uid 0 -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Ensure owner on /lib64/ file(s) matching ^.*$
      file:
        path: '{{ item }}'
        owner: '0'
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /usr/lib/ file(s) matching ^.*$ recursively
      command: find -H /usr/lib/  -type f ! -uid 0 -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Ensure owner on /usr/lib/ file(s) matching ^.*$
      file:
        path: '{{ item }}'
        owner: '0'
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /usr/lib64/ file(s) matching ^.*$ recursively
      command: find -H /usr/lib64/  -type f ! -uid 0 -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Ensure owner on /usr/lib64/ file(s) matching ^.*$
      file:
        path: '{{ item }}'
        owner: '0'
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010340
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_ownership_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
#### Rule Verify that System Executables Have Restrictive Permissions [ref]
System executables are stored in the following directories by default:
    /bin
    /sbin
    /usr/bin
    /usr/libexec
    /usr/local/bin
    /usr/local/sbin
    /usr/sbin
All files in these directories should not be group-writable or world-writable. If any file FILE in these directories is found to be group-writable or world-writable, correct its permission with the following command:
    $ sudo chmod go-w FILE
Rationale: | System binaries are executed by privileged users, as well as system services, and restrictive permissions are necessary to ensure execution of these programs cannot be co-opted.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_binary_dirs
Identifiers and References | References: 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-5(6), CM-5(6).1, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, OL08-00-010300, SV-248567r779267_rule
Remediation Shell script ⇲
    DIRS="/bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin /usr/libexec"
    for dirPath in $DIRS; do
    	find "$dirPath" -perm /022 -exec chmod go-w '{}' \;
    done
Remediation Ansible snippet ⇲
Complexity: | medium
Disruption: | medium
Strategy: | restrict
    - name: Read list of world and group writable system executables
      command: find /bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin /usr/libexec
        -perm /022 -type f
      register: world_writable_library_files
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010300
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - file_permissions_binary_dirs
        - medium_complexity
        - medium_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: Remove world/group writability of system executables
      file:
        path: '{{ item }}'
        mode: go-w
      with_items: '{{ world_writable_library_files.stdout_lines }}'
      when: world_writable_library_files.stdout_lines | length > 0
      tags:
        - DISA-STIG-OL08-00-010300
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - file_permissions_binary_dirs
        - medium_complexity
        - medium_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Verify that Shared Library Files Have Restrictive Permissions [ref]
System-wide shared library files, which are linked to executables during process load time or run time, are stored in the following directories by default:
    /lib
    /lib64
    /usr/lib
    /usr/lib64
Kernel modules, which can be added to the kernel during runtime, are stored in `/lib/modules`. All files in these directories should not be group-writable or world-writable. If any file in these directories is found to be group-writable or world-writable, correct its permission with the following command:
    $ sudo chmod go-w FILE
Rationale: | Files from shared library directories are loaded into the address space of processes (including privileged ones) or of the kernel itself at runtime. Restrictive permissions are necessary to protect the integrity of the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_library_dirs
Identifiers and References | References: 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001499, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-6(a), CM-5(6), CM-5(6).1, AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000259-GPOS-00100, OL08-00-010330, SV-248570r779276_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | configure
    find -H /lib/  -perm /u+s,g+ws,o+wt -type f -regex '^.*$' -exec chmod u-s,g-ws,o-wt {} \;

    find -H /lib64/  -perm /u+s,g+ws,o+wt -type f -regex '^.*$' -exec chmod u-s,g-ws,o-wt {} \;

    find -H /usr/lib/  -perm /u+s,g+ws,o+wt -type f -regex '^.*$' -exec chmod u-s,g-ws,o-wt {} \;

    find -H /usr/lib64/  -perm /u+s,g+ws,o+wt -type f -regex '^.*$' -exec chmod u-s,g-ws,o-wt {} \;
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | configure
    - name: Find /lib/ file(s) recursively
      command: find -H /lib/  -perm /u+s,g+ws,o+wt -type f -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Set permissions for /lib/ file(s)
      file:
        path: '{{ item }}'
        mode: u-s,g-ws,o-wt
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /lib64/ file(s) recursively
      command: find -H /lib64/  -perm /u+s,g+ws,o+wt -type f -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Set permissions for /lib64/ file(s)
      file:
        path: '{{ item }}'
        mode: u-s,g-ws,o-wt
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /usr/lib/ file(s) recursively
      command: find -H /usr/lib/  -perm /u+s,g+ws,o+wt -type f -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Set permissions for /usr/lib/ file(s)
      file:
        path: '{{ item }}'
        mode: u-s,g-ws,o-wt
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Find /usr/lib64/ file(s) recursively
      command: find -H /usr/lib64/  -perm /u+s,g+ws,o+wt -type f -regex "^.*$"
      register: files_found
      changed_when: false
      failed_when: false
      check_mode: false
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed

    - name: Set permissions for /usr/lib64/ file(s)
      file:
        path: '{{ item }}'
        mode: u-s,g-ws,o-wt
        state: file
      with_items:
        - '{{ files_found.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010330
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-5(6)
        - NIST-800-53-CM-5(6).1
        - NIST-800-53-CM-6(a)
        - configure_strategy
        - file_permissions_library_dirs
        - 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
Rationale: | Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure. The only authorized public directories are those temporary directories supplied with the system, or those designed to be temporary file repositories. The setting is normally reserved for directories used by the system, by users for temporary file storage (such as `/tmp`), and for directories requiring global read/write access.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_dir_perms_world_writable_sticky_bits
Identifiers and References | References: BP28(R40), 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, CCI-001090, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5, SRG-OS-000138-GPOS-00069, OL08-00-010190, SV-248551r779219_rule
Remediation Shell script ⇲
    df --local -P | awk '{if (NR!=1) print $6}' \
    | xargs -I '{}' find '{}' -xdev -type d \
    \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null \
    | xargs chmod a+t
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Get all world-writable directories with no sticky bits set
      shell: |
        set -o pipefail
        df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null
      register: dir_output
      tags:
        - DISA-STIG-OL08-00-010190
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-6(a)
        - dir_perms_world_writable_sticky_bits
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy

    - name: ensure sticky bit is set
      file:
        path: '{{ item }}'
        mode: a+t
      with_items:
        - '{{ dir_output.stdout_lines }}'
      tags:
        - DISA-STIG-OL08-00-010190
        - NIST-800-53-AC-6(1)
        - NIST-800-53-CM-6(a)
        - dir_perms_world_writable_sticky_bits
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
#### Rule Ensure All SGID Executables Are Authorized [ref]
The SGID (set group id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SGID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SGID files. This configuration check considers authorized SGID files which were installed via RPM. It is assumed that when an individual has sudo access to install an RPM and all packages are signed with an organizationally-recognized GPG key, the software should be considered an approved package on the system. Any SGID file not deployed through an RPM will be flagged for further review.
Rationale: | Executable files with the SGID permission run with the privileges of the owner of the file. SGID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_sgid
Identifiers and References | References: BP28(R37), BP28(R38), 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 4.3.3.7.3, SR 2.1, SR 5.2, 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, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5
#### Rule Ensure All SUID Executables Are Authorized [ref]
The SUID (set user id) bit should be set only on files that were installed via authorized means. A straightforward means of identifying unauthorized SUID files is determine if any were not installed as part of an RPM package, which is cryptographically verified. Investigate the origin of any unpackaged SUID files. This configuration check considers authorized SUID files which were installed via RPM. It is assumed that when an individual has sudo access to install an RPM and all packages are signed with an organizationally-recognized GPG key, the software should be considered an approved package on the system. Any SUID file not deployed through an RPM will be flagged for further review.
Rationale: | Executable files with the SUID permission run with the privileges of the owner of the file. SUID files of uncertain provenance could allow for unprivileged users to elevate privileges. The presence of these files should be strictly controlled on the system.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_suid
Identifiers and References | References: BP28(R37), BP28(R38), 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 4.3.3.7.3, SR 2.1, SR 5.2, 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, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5
#### 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`.
Rationale: | Data in world-writable files can be modified by any user on the system. In almost all circumstances, files can be configured using a combination of user and group permissions to support whatever legitimate access is needed without the risk caused by world-writable files.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_file_permissions_unauthorized_world_writable
Identifiers and References | References: BP28(R40), 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, CM-6(a), AC-6(1), PR.AC-4, PR.DS-5
Remediation Shell script ⇲
    find / -xdev -type f -perm -002 -exec chmod o-w {} \;
Group Restrict Partition Mount Options Group contains 3 rules
[ref] System partitions can be mounted with certain options that limit what files on those partitions can do. These options are set in the `/etc/fstab` configuration file, and can be used to make certain types of malicious behavior more difficult.
#### Rule Add nodev Option to /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
Identifiers and References | References: 11, 13, 14, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06, CCI-001764, 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, 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7, PR.IP-1, PR.PT-2, PR.PT-3, SRG-OS-000368-GPOS-00154, OL08-00-040120, SV-248844r780098_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    function perform_remediation {

        mount_point_match_regexp="$(printf "[[:space:]]%s[[:space:]]" /dev/shm)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if [ "$(grep -c "$mount_point_match_regexp" /etc/fstab)" -eq 0 ]; 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+=","
            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 -c "nodev")" -eq 0 ]; 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"
            else
                mount --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
Strategy: | configure
    - name: 'Add nodev Option to /dev/shm: Check information associated to mountpoint'
      command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040120
        - 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() | list | lower }}'
        - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - device_name.stdout is defined and device_name.stdout_lines is defined
        - (device_name.stdout | length > 0)
      tags:
        - DISA-STIG-OL08-00-040120
        - 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:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - ("" | length == 0)
        - (device_name.stdout | length == 0)
      tags:
        - DISA-STIG-OL08-00-040120
        - 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~'',nodev''
          }) }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - mount_info is defined and "nodev" not in mount_info.options
      tags:
        - DISA-STIG-OL08-00-040120
        - 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'
      mount:
        path: /dev/shm
        src: '{{ mount_info.source }}'
        opts: '{{ mount_info.options }}'
        state: mounted
        fstype: '{{ mount_info.fstype }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - (device_name.stdout is defined and (device_name.stdout | length > 0)) or (""
          | length == 0)
      tags:
        - DISA-STIG-OL08-00-040120
        - 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
Identifiers and References | References: 11, 13, 14, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06, CCI-001764, 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, 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7, PR.IP-1, PR.PT-2, PR.PT-3, SRG-OS-000368-GPOS-00154, OL08-00-040122, SV-248846r780104_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    function perform_remediation {

        mount_point_match_regexp="$(printf "[[:space:]]%s[[:space:]]" /dev/shm)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if [ "$(grep -c "$mount_point_match_regexp" /etc/fstab)" -eq 0 ]; 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+=","
            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 -c "noexec")" -eq 0 ]; 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"
            else
                mount --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
Strategy: | configure
    - name: 'Add noexec Option to /dev/shm: Check information associated to mountpoint'
      command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040122
        - 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() | list | lower }}'
        - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - device_name.stdout is defined and device_name.stdout_lines is defined
        - (device_name.stdout | length > 0)
      tags:
        - DISA-STIG-OL08-00-040122
        - 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:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - ("" | length == 0)
        - (device_name.stdout | length == 0)
      tags:
        - DISA-STIG-OL08-00-040122
        - 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~'',noexec''
          }) }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - mount_info is defined and "noexec" not in mount_info.options
      tags:
        - DISA-STIG-OL08-00-040122
        - 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'
      mount:
        path: /dev/shm
        src: '{{ mount_info.source }}'
        opts: '{{ mount_info.options }}'
        state: mounted
        fstype: '{{ mount_info.fstype }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - (device_name.stdout is defined and (device_name.stdout | length > 0)) or (""
          | length == 0)
      tags:
        - DISA-STIG-OL08-00-040122
        - 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
Identifiers and References | References: 11, 13, 14, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06, CCI-001764, 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, 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7, PR.IP-1, PR.PT-2, PR.PT-3, SRG-OS-000368-GPOS-00154, OL08-00-040121, SV-248845r780101_rule
Remediation Shell script ⇲
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    function perform_remediation {

        mount_point_match_regexp="$(printf "[[:space:]]%s[[:space:]]" /dev/shm)"

        # If the mount point is not in /etc/fstab, get previous mount options from /etc/mtab
        if [ "$(grep -c "$mount_point_match_regexp" /etc/fstab)" -eq 0 ]; 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+=","
            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 -c "nosuid")" -eq 0 ]; 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"
            else
                mount --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
Strategy: | configure
    - name: 'Add nosuid Option to /dev/shm: Check information associated to mountpoint'
      command: findmnt  '/dev/shm'
      register: device_name
      failed_when: device_name.rc > 1
      changed_when: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040121
        - 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() | list | lower }}'
        - '{{ device_name.stdout_lines[1].split() | list }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - device_name.stdout is defined and device_name.stdout_lines is defined
        - (device_name.stdout | length > 0)
      tags:
        - DISA-STIG-OL08-00-040121
        - 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:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - ("" | length == 0)
        - (device_name.stdout | length == 0)
      tags:
        - DISA-STIG-OL08-00-040121
        - 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~'',nosuid''
          }) }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - mount_info is defined and "nosuid" not in mount_info.options
      tags:
        - DISA-STIG-OL08-00-040121
        - 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'
      mount:
        path: /dev/shm
        src: '{{ mount_info.source }}'
        opts: '{{ mount_info.options }}'
        state: mounted
        fstype: '{{ mount_info.fstype }}'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - (device_name.stdout is defined and (device_name.stdout | length > 0)) or (""
          | length == 0)
      tags:
        - DISA-STIG-OL08-00-040121
        - 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
Group Restrict Programs from Dangerous Execution Patterns Group contains 1 group and 7 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 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 that can contain functions pointers. If a write vulnereability occurs in the kernel allowing a write access to any of this structure, the kernel can be compromise. This option disallow any program withtout the CAP_SYSLOG capability from getting the kernel pointers addresses, replacing them with 0.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_kptr_restrict
Identifiers and References | References: BP28(R23), CCI-002824, CCI-000366, 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, SC-30, SC-30(2), SC-30(5), CM-6(a), SRG-OS-000132-GPOS-00067, SRG-OS-000433-GPOS-00192, SRG-OS-000480-GPOS-00227, OL08-00-040283, SV-248891r780239_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    # Comment out any occurrences of kernel.kptr_restrict from /etc/sysctl.d/*.conf files
    for f in /etc/sysctl.d/*.conf ; do
      matching_list=$(grep -P '^(?!#).*[\s]+kernel.kptr_restrict.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          # comment out "kernel.kptr_restrict" matches to preserve user data
          sed -i "s/^${entry}$/# &/g" $f
        done <<< "$matching_list"
      fi
    done
    sysctl_kernel_kptr_restrict_value='1'

    #
    # Set runtime for kernel.kptr_restrict
    #
    /sbin/sysctl -q -n -w kernel.kptr_restrict="$sysctl_kernel_kptr_restrict_value"

    #
    # If kernel.kptr_restrict present in /etc/sysctl.conf, change value to appropriate value
    #	else, add "kernel.kptr_restrict = value" to /etc/sysctl.conf
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^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\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.kptr_restrict\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.kptr_restrict.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040283
        - 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: Comment out any occurrences of kernel.kptr_restrict from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.kptr_restrict
        replace: '#kernel.kptr_restrict'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040283
        - 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: Ensure sysctl kernel.kptr_restrict is set
      sysctl:
        name: kernel.kptr_restrict
        value: '{{ sysctl_kernel_kptr_restrict_value }}'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040283
        - 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
Identifiers and References | References: BP28(R23), 3.1.7, CCI-000366, CCI-002824, 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), 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, SC-30, SC-30(2), CM-6(a), SRG-OS-000433-GPOS-00193, SRG-OS-000480-GPOS-00227, OL08-00-010430, SV-248594r779348_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    #
    # Set runtime for kernel.randomize_va_space
    #
    /sbin/sysctl -q -n -w kernel.randomize_va_space="2"

    #
    # 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
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^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\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.randomize_va_space\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.randomize_va_space.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010430
        - NIST-800-171-3.1.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SC-30
        - NIST-800-53-SC-30(2)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_randomize_va_space

    - name: Comment out any occurrences of kernel.randomize_va_space from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.randomize_va_space
        replace: '#kernel.randomize_va_space'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010430
        - NIST-800-171-3.1.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SC-30
        - NIST-800-53-SC-30(2)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_randomize_va_space

    - name: Ensure sysctl kernel.randomize_va_space is set to 2
      sysctl:
        name: kernel.randomize_va_space
        value: '2'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010430
        - NIST-800-171-3.1.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SC-30
        - NIST-800-53-SC-30(2)
        - 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
Identifiers and References | References: BP28(R23), 3.1.5, CCI-001314, 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), SI-11(a), SI-11(b), SRG-OS-000132-GPOS-00067, SRG-OS-000138-GPOS-00069, OL08-00-010375, SV-248579r779303_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    #
    # Set runtime for kernel.dmesg_restrict
    #
    /sbin/sysctl -q -n -w kernel.dmesg_restrict="1"

    #
    # If kernel.dmesg_restrict present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.dmesg_restrict = 1" to /etc/sysctl.conf
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^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\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.dmesg_restrict\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.dmesg_restrict.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010375
        - 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: Comment out any occurrences of kernel.dmesg_restrict from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.dmesg_restrict
        replace: '#kernel.dmesg_restrict'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010375
        - 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: Ensure sysctl kernel.dmesg_restrict is set to 1
      sysctl:
        name: kernel.dmesg_restrict
        value: '1'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010375
        - NIST-800-171-3.1.5
        - NIST-800-53-SI-11(a)
        - NIST-800-53-SI-11(b)
        - disable_strategy
        - low_complexity
        - low_severity
        - medium_disruption
        - reboot_required
        - sysctl_kernel_dmesg_restrict
#### Rule Disable Kernel Image Loading [ref]
To set the runtime status of the `kernel.kexec_load_disabled` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.kexec_load_disabled=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.kexec_load_disabled = 1
Rationale: | Disabling kexec_load allows greater control of the kernel memory. It makes it impossible to load another kernel image after it has been disabled.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_kexec_load_disabled
Identifiers and References | References: CCI-001749, CM-6, SRG-OS-000480-GPOS-00227, SRG-OS-000366-GPOS-00153, OL08-00-010372, SV-248576r779294_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    #
    # Set runtime for kernel.kexec_load_disabled
    #
    /sbin/sysctl -q -n -w kernel.kexec_load_disabled="1"

    #
    # If kernel.kexec_load_disabled present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.kexec_load_disabled = 1" to /etc/sysctl.conf
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^kernel.kexec_load_disabled")

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^kernel.kexec_load_disabled\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.kexec_load_disabled\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.kexec_load_disabled.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010372
        - NIST-800-53-CM-6
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_kexec_load_disabled

    - name: Comment out any occurrences of kernel.kexec_load_disabled from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.kexec_load_disabled
        replace: '#kernel.kexec_load_disabled'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010372
        - NIST-800-53-CM-6
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_kexec_load_disabled

    - name: Ensure sysctl kernel.kexec_load_disabled is set to 1
      sysctl:
        name: kernel.kexec_load_disabled
        value: '1'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010372
        - NIST-800-53-CM-6
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_kexec_load_disabled
#### Rule Disable Access to Network bpf() Syscall From Unprivileged Processes [ref]
To set the runtime status of the `kernel.unprivileged_bpf_disabled` kernel parameter, run the following command:
    $ sudo sysctl -w kernel.unprivileged_bpf_disabled=1
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    kernel.unprivileged_bpf_disabled = 1
Rationale: | Loading and accessing the packet filters programs and maps using the bpf() syscall has the potential of revealing sensitive information about the kernel state.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_kernel_unprivileged_bpf_disabled
Identifiers and References | References: CCI-000366, AC-6, SC-7(10), FMT_SMF_EXT.1, SRG-OS-000132-GPOS-00067, SRG-OS-000480-GPOS-00227, OL08-00-040281, SV-248889r780233_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    #
    # Set runtime for kernel.unprivileged_bpf_disabled
    #
    /sbin/sysctl -q -n -w kernel.unprivileged_bpf_disabled="1"

    #
    # If kernel.unprivileged_bpf_disabled present in /etc/sysctl.conf, change value to "1"
    #	else, add "kernel.unprivileged_bpf_disabled = 1" to /etc/sysctl.conf
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^kernel.unprivileged_bpf_disabled")

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

    # If the key exists, change it. Otherwise, add it to the config_file.
    # We search for the key string followed by a word boundary (matched by \>),
    # so if we search for 'setting', 'setting2' won't match.
    if LC_ALL=C grep -q -m 1 -i -e "^kernel.unprivileged_bpf_disabled\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.unprivileged_bpf_disabled\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.unprivileged_bpf_disabled.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040281
        - NIST-800-53-AC-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_unprivileged_bpf_disabled

    - name: Comment out any occurrences of kernel.unprivileged_bpf_disabled from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.unprivileged_bpf_disabled
        replace: '#kernel.unprivileged_bpf_disabled'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040281
        - NIST-800-53-AC-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_unprivileged_bpf_disabled

    - name: Ensure sysctl kernel.unprivileged_bpf_disabled is set to 1
      sysctl:
        name: kernel.unprivileged_bpf_disabled
        value: '1'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040281
        - NIST-800-53-AC-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_unprivileged_bpf_disabled
#### 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
Identifiers and References | References: BP28(R25), CCI-000366, SC-7(10), SRG-OS-000132-GPOS-00067, SRG-OS-000480-GPOS-00227, OL08-00-040282, SV-248890r780236_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    # Comment out any occurrences of kernel.yama.ptrace_scope from /etc/sysctl.d/*.conf files
    for f in /etc/sysctl.d/*.conf ; do
      matching_list=$(grep -P '^(?!#).*[\s]+kernel.yama.ptrace_scope.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          # comment out "kernel.yama.ptrace_scope" matches to preserve user data
          sed -i "s/^${entry}$/# &/g" $f
        done <<< "$matching_list"
      fi
    done

    #
    # Set runtime for kernel.yama.ptrace_scope
    #
    /sbin/sysctl -q -n -w kernel.yama.ptrace_scope="1"

    #
    # 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
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^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\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^kernel.yama.ptrace_scope\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*kernel.yama.ptrace_scope.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040282
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_yama_ptrace_scope

    - name: Comment out any occurrences of kernel.yama.ptrace_scope from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*kernel.yama.ptrace_scope
        replace: '#kernel.yama.ptrace_scope'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040282
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_yama_ptrace_scope

    - name: Ensure sysctl kernel.yama.ptrace_scope is set to 1
      sysctl:
        name: kernel.yama.ptrace_scope
        value: '1'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040282
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_kernel_yama_ptrace_scope
#### Rule Harden the operation of the BPF just-in-time compiler [ref]
To set the runtime status of the `net.core.bpf_jit_harden` kernel parameter, run the following command:
    $ sudo sysctl -w net.core.bpf_jit_harden=2
To make sure that the setting is persistent, add the following line to a file in the directory `/etc/sysctl.d`:
    net.core.bpf_jit_harden = 2
Rationale: | When hardened, the extended Berkeley Packet Filter just-in-time compiler will randomize any kernel addresses in the BPF programs and maps, and will not expose the JIT addresses in `/proc/kallsyms`.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sysctl_net_core_bpf_jit_harden
Identifiers and References | References: CCI-000366, CM-6, SC-7(10), FMT_SMF_EXT.1, SRG-OS-000480-GPOS-00227, OL08-00-040286, SV-248894r780248_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | medium
Reboot: | true
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    # Comment out any occurrences of net.core.bpf_jit_harden from /etc/sysctl.d/*.conf files
    for f in /etc/sysctl.d/*.conf ; do
      matching_list=$(grep -P '^(?!#).*[\s]+net.core.bpf_jit_harden.*$' $f | uniq )
      if ! test -z "$matching_list"; then
        while IFS= read -r entry; do
          # comment out "net.core.bpf_jit_harden" matches to preserve user data
          sed -i "s/^${entry}$/# &/g" $f
        done <<< "$matching_list"
      fi
    done

    #
    # Set runtime for net.core.bpf_jit_harden
    #
    /sbin/sysctl -q -n -w net.core.bpf_jit_harden="2"

    #
    # If net.core.bpf_jit_harden present in /etc/sysctl.conf, change value to "2"
    #	else, add "net.core.bpf_jit_harden = 2" to /etc/sysctl.conf
    #
    # Test if the config_file is a symbolic link. If so, use --follow-symlinks with sed.
    # Otherwise, regular sed command will do.
    sed_command=('sed' '-i')
    if test -L "/etc/sysctl.conf"; then
        sed_command+=('--follow-symlinks')
    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' <<< "^net.core.bpf_jit_harden")

    # 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 "^net.core.bpf_jit_harden\\>" "/etc/sysctl.conf"; then
        "${sed_command[@]}" "s/^net.core.bpf_jit_harden\\>.*/$formatted_output/gi" "/etc/sysctl.conf"
    else
        # \n is precaution for case where file ends without trailing newline

        printf '%s\n' "$formatted_output" >> "/etc/sysctl.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: List /etc/sysctl.d/*.conf files
      find:
        paths: /etc/sysctl.d/
        contains: ^[\s]*net.core.bpf_jit_harden.*$
        patterns: '*.conf'
      register: find_sysctl_d
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040286
        - NIST-800-53-CM-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_net_core_bpf_jit_harden

    - name: Comment out any occurrences of net.core.bpf_jit_harden from /etc/sysctl.d/*.conf
        files
      replace:
        path: '{{ item }}'
        regexp: ^[\s]*net.core.bpf_jit_harden
        replace: '#net.core.bpf_jit_harden'
      loop: '{{ find_sysctl_d.files }}'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040286
        - NIST-800-53-CM-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_net_core_bpf_jit_harden

    - name: Ensure sysctl net.core.bpf_jit_harden is set to 2
      sysctl:
        name: net.core.bpf_jit_harden
        value: '2'
        state: present
        reload: true
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040286
        - NIST-800-53-CM-6
        - NIST-800-53-SC-7(10)
        - disable_strategy
        - low_complexity
        - medium_disruption
        - medium_severity
        - reboot_required
        - sysctl_net_core_bpf_jit_harden
Group SELinux Group contains 2 rules
[ref] SELinux is a feature of the Linux kernel which can be used to guard against misconfigured or compromised programs. SELinux enforces the idea that programs should be limited in what files they can access and what actions they can take. The default SELinux policy, as configured on Oracle Linux 8, has been sufficiently developed and debugged that it should be usable on almost any system with minimal configuration and a small amount of system administrator training. This policy prevents system services - including most of the common network-visible services such as mail servers, FTP servers, and DNS servers - from accessing files which those services have no valid reason to access. This action alone prevents a huge amount of possible damage from network attacks against services, from trojaned software, and so forth. This guide recommends that SELinux be enabled using the default (targeted) policy on every Oracle Linux 8 system, unless that system has unusual requirements which make a stronger policy appropriate.
#### 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
Identifiers and References | References: BP28(R66), 1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9, 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, 3.1.2, 3.7.2, CCI-002165, CCI-002696, 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), 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, 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, 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, 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, AC-3, AC-3(3)(a), AU-9, SC-7(21), 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, SRG-OS-000445-GPOS-00199, SRG-OS-000445-VMM-001780, OL08-00-010450, SV-248596r779354_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; 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: | true
Strategy: | restrict
    - 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
          lineinfile:
            path: /etc/selinux/config
            create: false
            regexp: ^SELINUXTYPE=
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/selinux/config
          lineinfile:
            path: /etc/selinux/config
            create: false
            regexp: ^SELINUXTYPE=
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/selinux/config
          lineinfile:
            path: /etc/selinux/config
            create: true
            regexp: ^SELINUXTYPE=
            line: SELINUXTYPE={{ var_selinux_policy_name }}
            state: present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010450
        - 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)
        - low_complexity
        - low_disruption
        - medium_severity
        - reboot_required
        - restrict_strategy
        - selinux_policytype
#### Rule Ensure SELinux State is Enforcing [ref]
The SELinux state should be set to `enforcing` at system boot time. In the file `/etc/selinux/config`, add or correct the following line to configure the system to boot into enforcing mode:
    SELINUX=enforcing
Rationale: | Setting the SELinux state to enforcing ensures SELinux is able to confine potentially compromised processes to the security policy, which is designed to prevent them from causing damage to the system or further elevating their privileges.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_selinux_state
Identifiers and References | References: BP28(R4), BP28(R66), 1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 5, 6, 8, 9, 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, 3.1.2, 3.7.2, CCI-002165, CCI-002696, 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), 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, 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, 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, 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, AC-3, AC-3(3)(a), AU-9, SC-7(21), 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, SRG-OS-000445-GPOS-00199, SRG-OS-000445-VMM-001780, OL08-00-010170, SV-248548r779210_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Reboot: | true
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    var_selinux_state='enforcing'

    if [ -e "/etc/selinux/config" ] ; then

        LC_ALL=C sed -i "/^SELINUX=/Id" "/etc/selinux/config"
    else
        touch "/etc/selinux/config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/selinux/config"

    cp "/etc/selinux/config" "/etc/selinux/config.bak"
    # Insert at the end of the file
    printf '%s\n' "SELINUX=$var_selinux_state" >> "/etc/selinux/config"
    # Clean up after ourselves.
    rm "/etc/selinux/config.bak"

    fixfiles onboot
    fixfiles -f relabel

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: XCCDF Value var_selinux_state # promote to variable
      set_fact:
        var_selinux_state: !!str enforcing
      tags:
        - always

    - name: Ensure SELinux State is Enforcing
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/selinux/config
            create: false
            regexp: ^SELINUX=
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/selinux/config
          lineinfile:
            path: /etc/selinux/config
            create: false
            regexp: ^SELINUX=
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/selinux/config
          lineinfile:
            path: /etc/selinux/config
            create: true
            regexp: ^SELINUX=
            line: SELINUX={{ var_selinux_state }}
            state: present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010170
        - 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)
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - selinux_state
Group Services Group contains 15 groups and 26 rules
[ref] The best protection against vulnerable software is running less software. This section describes how to review the software which Oracle Linux 8 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Oracle Linux 8 system and provides guidance about which ones can be safely disabled. Oracle Linux 8 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Oracle Linux 8 systems, it is highly recommended to select the minimal packages and then build up the system from there.
Group Avahi Server Group contains 1 group and 1 rule
[ref] The Avahi daemon implements the DNS Service Discovery and Multicast DNS protocols, which provide service and host discovery on a network. It allows a system to automatically identify resources on the network, such as printers or web servers. This capability is also known as mDNSresponder and is a major part of Zeroconf networking.
Group Disable Avahi Server if Possible Group contains 1 rule
[ref] Because the Avahi daemon service keeps an open network port, it is subject to network attacks. Disabling it can reduce the system's vulnerability to such attacks.
#### Rule Disable Avahi Server Software [ref]
The `avahi-daemon` service can be disabled with the following command:
    $ sudo systemctl mask --now avahi-daemon.service
Rationale: | Because the Avahi daemon service keeps an open network port, it is subject to network attacks. Its functionality is convenient but is only appropriate if the local network can be trusted.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_avahi-daemon_disabled
Identifiers and References | References: 11, 14, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS06.06, CCI-000366, 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), PR.IP-1, PR.PT-3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" stop 'avahi-daemon.service'
    "$SYSTEMCTL_EXEC" disable 'avahi-daemon.service'
    "$SYSTEMCTL_EXEC" mask 'avahi-daemon.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" list-unit-files | grep -q '^avahi-daemon.socket'; then
        "$SYSTEMCTL_EXEC" stop 'avahi-daemon.socket'
        "$SYSTEMCTL_EXEC" mask 'avahi-daemon.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 'avahi-daemon.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Disable service avahi-daemon
      block:

        - name: Disable service avahi-daemon
          systemd:
            name: avahi-daemon.service
            enabled: 'no'
            state: stopped
            masked: 'yes'
          ignore_errors: 'yes'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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
        - medium_severity
        - no_reboot_needed
        - service_avahi-daemon_disabled

    - name: Unit Socket Exists - avahi-daemon.socket
      command: systemctl list-unit-files avahi-daemon.socket
      args:
        warn: false
      register: socket_file_exists
      changed_when: false
      ignore_errors: true
      check_mode: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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
        - medium_severity
        - no_reboot_needed
        - service_avahi-daemon_disabled

    - name: Disable socket avahi-daemon
      systemd:
        name: avahi-daemon.socket
        enabled: 'no'
        state: stopped
        masked: 'yes'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"avahi-daemon.socket" in socket_file_exists.stdout_lines[1]'
      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
        - medium_severity
        - no_reboot_needed
        - service_avahi-daemon_disabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include disable_avahi-daemon

    class disable_avahi-daemon {
      service {'avahi-daemon':
        enable => false,
        ensure => 'stopped',
      }
    }
Remediation script ⇲
    [customizations.services]
    disabled = ["avahi-daemon"]
Group Application Whitelisting Daemon Group contains 2 rules
[ref] Fapolicyd (File Access Policy Daemon) implements application whitelisting to decide file access rights. Applications that are known via a reputation source are allowed access while unknown applications are not. The daemon makes use of the kernel's `fanotify` interface to determine file access rights.
#### Rule Install fapolicyd Package [ref]
The `fapolicyd` package can be installed with the following command:
    $ sudo yum install fapolicyd
Rationale: | `fapolicyd` (File Access Policy Daemon) implements application whitelisting to decide file access rights.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_fapolicyd_installed
Identifiers and References | References: CCI-001764, CM-6(a), SI-4(22), SRG-OS-000370-GPOS-00155, SRG-OS-000368-GPOS-00154, OL08-00-040135, SV-248859r780143_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Ensure fapolicyd is installed
      package:
        name: fapolicyd
        state: present
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040135
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-4(22)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_fapolicyd_installed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include install_fapolicyd

    class install_fapolicyd {
      package { 'fapolicyd':
        ensure => 'installed',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    package --add=fapolicyd
Remediation script ⇲
    [[packages]]
    name = "fapolicyd"
    version = "*"
#### Rule Enable the File Access Policy Service [ref]
The File Access Policy service should be enabled. The `fapolicyd` service can be enabled with the following command:
    $ sudo systemctl enable fapolicyd.service
Rationale: | The `fapolicyd` service (File Access Policy Daemon) implements application whitelisting to decide file access rights.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_fapolicyd_enabled
Identifiers and References | References: CCI-001764, CM-6(a), SI-4(22), FMT_SMF_EXT.1, SRG-OS-000370-GPOS-00155, SRG-OS-000368-GPOS-00154, OL08-00-040136, SV-248860r780146_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" unmask 'fapolicyd.service'
    "$SYSTEMCTL_EXEC" start 'fapolicyd.service'
    "$SYSTEMCTL_EXEC" enable 'fapolicyd.service'

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    - name: Enable service fapolicyd
      block:

        - name: Gather the package facts
          package_facts:
            manager: auto

        - name: Enable service fapolicyd
          service:
            name: fapolicyd
            enabled: 'yes'
            state: started
            masked: 'no'
          when:
            - '"fapolicyd" in ansible_facts.packages'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-040136
        - NIST-800-53-CM-6(a)
        - NIST-800-53-SI-4(22)
        - enable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_fapolicyd_enabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include enable_fapolicyd

    class enable_fapolicyd {
      service {'fapolicyd':
        enable => true,
        ensure => 'running',
      }
    }
Remediation script ⇲
    [customizations.services]
    enabled = ["fapolicyd"]
Group Obsolete Services Group contains 5 groups and 10 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 Oracle Linux 8 by default. Organizations which are running these services should switch to more secure equivalents as soon as possible. If it remains absolutely necessary to run one of these services for legacy reasons, care should be taken to restrict the service as much as possible, for instance by configuring host firewall software such as `iptables` to restrict access to the vulnerable service to only those remote hosts which have a known need to use it.
Group Xinetd Group contains 2 rules
[ref] The `xinetd` service acts as a dedicated listener for some network services (mostly, obsolete ones) and can be used to provide access controls and perform some logging. It has been largely obsoleted by other features, and it is not installed by default. The older Inetd service is not even available as part of Oracle Linux 8.
#### Rule Uninstall xinetd Package [ref]
The `xinetd` package can be removed with the following command:
    $ sudo yum erase xinetd
Rationale: | Removing the `xinetd` package decreases the risk of the xinetd service's accidental (or intentional) activation.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_xinetd_removed
Identifiers and References | References: BP28(R1), 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000305, 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), 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

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

    if rpm -q --quiet "xinetd" ; then

        yum remove -y "xinetd"

    fi

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure xinetd is removed
      package:
        name: xinetd
        state: absent
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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_xinetd_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_xinetd

    class remove_xinetd {
      package { 'xinetd':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=xinetd
#### Rule Disable xinetd Service [ref]
The `xinetd` service can be disabled with the following command:
    $ sudo systemctl mask --now xinetd.service
Rationale: | The xinetd service provides a dedicated listener service for some programs, which is no longer necessary for commonly-used network services. Disabling it ensures that these uncommon services are not running, and also prevents attacks against xinetd itself.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_xinetd_disabled
Identifiers and References | References: 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, 3.4.7, CCI-000305, 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), 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" stop 'xinetd.service'
    "$SYSTEMCTL_EXEC" disable 'xinetd.service'
    "$SYSTEMCTL_EXEC" mask 'xinetd.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" list-unit-files | grep -q '^xinetd.socket'; then
        "$SYSTEMCTL_EXEC" stop 'xinetd.socket'
        "$SYSTEMCTL_EXEC" mask 'xinetd.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 'xinetd.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Disable service xinetd
      block:

        - name: Disable service xinetd
          systemd:
            name: xinetd.service
            enabled: 'no'
            state: stopped
            masked: 'yes'
          ignore_errors: 'yes'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - disable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_xinetd_disabled

    - name: Unit Socket Exists - xinetd.socket
      command: systemctl list-unit-files xinetd.socket
      args:
        warn: false
      register: socket_file_exists
      changed_when: false
      ignore_errors: true
      check_mode: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - disable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_xinetd_disabled

    - name: Disable socket xinetd
      systemd:
        name: xinetd.socket
        enabled: 'no'
        state: stopped
        masked: 'yes'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"xinetd.socket" in socket_file_exists.stdout_lines[1]'
      tags:
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - disable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - service_xinetd_disabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include disable_xinetd

    class disable_xinetd {
      service {'xinetd':
        enable => false,
        ensure => 'stopped',
      }
    }
Remediation script ⇲
    [customizations.services]
    disabled = ["xinetd"]
Group NIS Group contains 1 rule
[ref] The Network Information Service (NIS), also known as 'Yellow Pages' (YP), and its successor NIS+ have been made obsolete by Kerberos, LDAP, and other modern centralized authentication services. NIS should not be used because it suffers from security problems inherent in its design, such as inadequate protection of important authentication information.
#### Rule Remove NIS Client [ref]
The Network Information Service (NIS), formerly known as Yellow Pages, is a client-server directory service protocol used to distribute system configuration files. The NIS client (`ypbind`) was used to bind a system to an NIS server and receive the distributed configuration files.
Rationale: | The NIS service is inherently an insecure system that has been vulnerable to DOS attacks, buffer overflows and has poor authentication for querying NIS maps. NIS generally has been replaced by such protocols as Lightweight Directory Access Protocol (LDAP). It is recommended that the service be removed.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_ypbind_removed
Identifiers and References | References: BP28(R1), 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)
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove ypbind
    #	   from the system, and may remove any packages
    #	   that depend on ypbind. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "ypbind" ; then

        yum remove -y "ypbind"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure ypbind is removed
      package:
        name: ypbind
        state: absent
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - package_ypbind_removed
        - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_ypbind

    class remove_ypbind {
      package { 'ypbind':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=ypbind
Group Rlogin, Rsh, and Rexec Group contains 2 rules
[ref] The Berkeley r-commands are legacy services which allow cleartext remote access and have an insecure trust model.
#### Rule Uninstall rsh-server Package [ref]
The `rsh-server` package can be removed with the following command:
    $ sudo yum erase rsh-server
Rationale: | The `rsh-server` service provides unencrypted remote access service which does not provide for the confidentiality and integrity of user passwords or the remote session and has very weak authentication. If a privileged user were to login using this service, the privileged user password could be compromised. The `rsh-server` package provides several obsolete and insecure network services. Removing it decreases the risk of those services' accidental (or intentional) activation.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_rsh-server_removed
Identifiers and References | References: BP28(R1), 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000381, 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), 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), IA-5(1)(c), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, SRG-OS-000095-GPOS-00049, OL08-00-040010, SV-248827r780047_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove rsh-server
    #	   from the system, and may remove any packages
    #	   that depend on rsh-server. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "rsh-server" ; then

        yum remove -y "rsh-server"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure rsh-server is removed
      package:
        name: rsh-server
        state: absent
      tags:
        - DISA-STIG-OL08-00-040010
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - NIST-800-53-IA-5(1)(c)
        - disable_strategy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - package_rsh-server_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_rsh-server

    class remove_rsh-server {
      package { 'rsh-server':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=rsh-server
#### Rule Uninstall rsh Package [ref]
The `rsh` package contains the client commands for the rsh services
Rationale: | These legacy clients contain numerous security exposures and have been replaced with the more secure SSH package. Even if the server is removed, it is best to ensure the clients are also removed to prevent users from inadvertently attempting to use these commands and therefore exposing their credentials. Note that removing the `rsh` package removes the clients for `rsh`,`rcp`, and `rlogin`.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_package_rsh_removed
Identifiers and References | References: BP28(R1), 3.1.13, 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), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove rsh
    #	   from the system, and may remove any packages
    #	   that depend on rsh. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "rsh" ; then

        yum remove -y "rsh"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure rsh is removed
      package:
        name: rsh
        state: absent
      tags:
        - NIST-800-171-3.1.13
        - disable_strategy
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - package_rsh_removed
        - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_rsh

    class remove_rsh {
      package { 'rsh':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=rsh
Group Chat/Messaging Services Group contains 2 rules
[ref] The talk software makes it possible for users to send and receive messages across systems through a terminal session.
#### Rule Uninstall talk-server Package [ref]
The `talk-server` package can be removed with the following command:
     $ sudo yum erase talk-server
Rationale: | The talk software presents a security risk as it uses unencrypted protocols for communications. Removing the `talk-server` package decreases the risk of the accidental (or intentional) activation of talk services.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_talk-server_removed
Identifiers and References | References: BP28(R1), 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)
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove talk-server
    #	   from the system, and may remove any packages
    #	   that depend on talk-server. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "talk-server" ; then

        yum remove -y "talk-server"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure talk-server is removed
      package:
        name: talk-server
        state: absent
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_talk-server_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_talk-server

    class remove_talk-server {
      package { 'talk-server':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=talk-server
#### Rule Uninstall talk Package [ref]
The `talk` package contains the client program for the Internet talk protocol, which allows the user to chat with other users on different systems. Talk is a communication program which copies lines from one terminal to the terminal of another user. The `talk` package can be removed with the following command:
    $ sudo yum erase talk
Rationale: | The talk software presents a security risk as it uses unencrypted protocols for communications. Removing the `talk` package decreases the risk of the accidental (or intentional) activation of talk client program.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_package_talk_removed
Identifiers and References | References: BP28(R1), 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)
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove talk
    #	   from the system, and may remove any packages
    #	   that depend on talk. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "talk" ; then

        yum remove -y "talk"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure talk is removed
      package:
        name: talk
        state: absent
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - package_talk_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_talk

    class remove_talk {
      package { 'talk':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=talk
Group Telnet Group contains 3 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 yum erase 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 unsecure. They increase the risk to the platform by providing additional attack vectors. The telnet service provides an unencrypted remote access service which does not provide for the confidentiality and integrity of user passwords or the remote session. If a privileged user were to login using this service, the privileged user password could be compromised. Removing the `telnet-server` package decreases the risk of the telnet service's accidental (or intentional) activation.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_package_telnet-server_removed
Identifiers and References | References: BP28(R1), 11, 12, 14, 15, 3, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06, CCI-000381, 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), 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, 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, 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, CM-7(a), CM-7(b), CM-6(a), PR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4, SRG-OS-000095-GPOS-00049, OL08-00-040000, SV-248823r780035_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
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

        yum remove -y "telnet-server"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure telnet-server is removed
      package:
        name: telnet-server
        state: absent
      tags:
        - DISA-STIG-OL08-00-040000
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - disable_strategy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - package_telnet-server_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_telnet-server

    class remove_telnet-server {
      package { 'telnet-server':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=telnet-server
#### 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 Oracle Linux 8.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_telnet_removed
Identifiers and References | References: BP28(R1), 3.1.13, 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), A.8.2.3, A.13.1.1, A.13.2.1, A.13.2.3, A.14.1.2, A.14.1.3
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
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

        yum remove -y "telnet"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure telnet is removed
      package:
        name: telnet
        state: absent
      tags:
        - NIST-800-171-3.1.13
        - disable_strategy
        - low_complexity
        - low_disruption
        - low_severity
        - no_reboot_needed
        - package_telnet_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_telnet

    class remove_telnet {
      package { 'telnet':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=telnet
#### Rule Disable telnet Service [ref]
The `telnet` service configuration file `/etc/xinetd.d/telnet` is not created automatically. If it was created manually, check the `/etc/xinetd.d/telnet` file and ensure that `disable = no` is changed to read `disable = yes` as follows below:
    # description: The telnet server serves telnet sessions; it uses \\
    #       unencrypted username/password pairs for authentication.
    service telnet
    {
            flags           = REUSE
            socket_type     = stream

            wait            = no
            user            = root
            server          = /usr/sbin/in.telnetd
            log_on_failure  += USERID
            disable         = yes
    }
If the `/etc/xinetd.d/telnet` file does not exist, make sure that the activation of the `telnet` service on system boot is disabled via the following command: The `rexec` socket can be disabled with the following command:
    $ sudo systemctl mask --now rexec.socket
Rationale: | The telnet protocol uses unencrypted network communication, which means that data from the login session, including passwords and all other information transmitted during the session, can be stolen by eavesdroppers on the network. The telnet protocol is also subject to man-in-the-middle attacks.
Severity: | high
Rule ID: | xccdf_org.ssgproject.content_rule_service_telnet_disabled
Identifiers and References | References: 1, 11, 12, 14, 15, 16, 3, 5, 8, 9, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10, 3.1.13, 3.4.7, 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), 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, 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, 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.18.1.4, A.6.2.1, A.6.2.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.2, A.9.4.3, CM-7(a), CM-7(b), CM-6(a), IA-5(1)(c), PR.AC-1, PR.AC-3, PR.AC-6, PR.AC-7, PR.IP-1, PR.PT-3, PR.PT-4
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" stop 'telnet.service'
    "$SYSTEMCTL_EXEC" disable 'telnet.service'
    "$SYSTEMCTL_EXEC" mask 'telnet.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" list-unit-files | grep -q '^telnet.socket'; then
        "$SYSTEMCTL_EXEC" stop 'telnet.socket'
        "$SYSTEMCTL_EXEC" mask 'telnet.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 'telnet.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Disable service telnet
      block:

        - name: Disable service telnet
          systemd:
            name: telnet.service
            enabled: 'no'
            state: stopped
            masked: 'yes'
          ignore_errors: 'yes'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - NIST-800-171-3.1.13
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - NIST-800-53-IA-5(1)(c)
        - disable_strategy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_telnet_disabled

    - name: Unit Socket Exists - telnet.socket
      command: systemctl list-unit-files telnet.socket
      args:
        warn: false
      register: socket_file_exists
      changed_when: false
      ignore_errors: true
      check_mode: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - NIST-800-171-3.1.13
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - NIST-800-53-IA-5(1)(c)
        - disable_strategy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_telnet_disabled

    - name: Disable socket telnet
      systemd:
        name: telnet.socket
        enabled: 'no'
        state: stopped
        masked: 'yes'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"telnet.socket" in socket_file_exists.stdout_lines[1]'
      tags:
        - NIST-800-171-3.1.13
        - NIST-800-171-3.4.7
        - NIST-800-53-CM-6(a)
        - NIST-800-53-CM-7(a)
        - NIST-800-53-CM-7(b)
        - NIST-800-53-IA-5(1)(c)
        - disable_strategy
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_telnet_disabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include disable_telnet

    class disable_telnet {
      service {'telnet':
        enable => false,
        ensure => 'stopped',
      }
    }
Remediation script ⇲
    [customizations.services]
    disabled = ["telnet"]
Group Proxy Server Group contains 1 group and 2 rules
[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 2 rules
[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 yum erase 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
Identifiers and References |
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
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

        yum remove -y "squid"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure squid is removed
      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
Strategy: | disable
    include remove_squid

    class remove_squid {
      package { 'squid':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=squid
#### Rule Disable Squid [ref]
The `squid` service can be disabled with the following command:
    $ sudo systemctl mask --now squid.service
Rationale: | Running proxy server software provides a network-based avenue of attack, and should be removed if not needed.
Severity: | unknown
Rule ID: | xccdf_org.ssgproject.content_rule_service_squid_disabled
Identifiers and References |
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" stop 'squid.service'
    "$SYSTEMCTL_EXEC" disable 'squid.service'
    "$SYSTEMCTL_EXEC" mask 'squid.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" list-unit-files | grep -q '^squid.socket'; then
        "$SYSTEMCTL_EXEC" stop 'squid.socket'
        "$SYSTEMCTL_EXEC" mask 'squid.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 'squid.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Disable service squid
      block:

        - name: Disable service squid
          systemd:
            name: squid.service
            enabled: 'no'
            state: stopped
            masked: 'yes'
          ignore_errors: 'yes'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_squid_disabled
        - unknown_severity

    - name: Unit Socket Exists - squid.socket
      command: systemctl list-unit-files squid.socket
      args:
        warn: false
      register: socket_file_exists
      changed_when: false
      ignore_errors: true
      check_mode: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_squid_disabled
        - unknown_severity

    - name: Disable socket squid
      systemd:
        name: squid.socket
        enabled: 'no'
        state: stopped
        masked: 'yes'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"squid.socket" in socket_file_exists.stdout_lines[1]'
      tags:
        - disable_strategy
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - service_squid_disabled
        - unknown_severity
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include disable_squid

    class disable_squid {
      service {'squid':
        enable => false,
        ensure => 'stopped',
      }
    }
Remediation script ⇲
    [customizations.services]
    disabled = ["squid"]
Group Network Routing Group contains 1 group and 2 rules
[ref] A router is a very desirable target for a potential adversary because they fulfill a variety of infrastructure networking roles such as access to network segments, gateways to other networks, filtering, etc. Therefore, if one is required, the system acting as a router should be dedicated to that purpose alone and be stored in a physically secure location. The system's default routing software is Quagga, and provided in an RPM package of the same name.
Group Disable Quagga if Possible Group contains 2 rules
[ref] If Quagga was installed and activated, but the system does not need to act as a router, then it should be disabled and removed.
#### Rule Uninstall quagga Package [ref]
The `quagga` package can be removed with the following command:
     $ sudo yum erase quagga
Rationale: | Routing software is typically used on routers to exchange network topology information with other routers. If routing software is used when not required, system network information may be unnecessarily transmitted across the network. If there is no need to make the router software available, removing it provides a safeguard against its activation.
Severity: | low
Rule ID: | xccdf_org.ssgproject.content_rule_package_quagga_removed
Identifiers and References | References: 12, 15, 8, APO13.01, DSS05.02, CCI-000366, 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, A.13.1.1, A.13.2.1, A.14.1.3, CM-7(a), CM-7(b), CM-6(a), PR.PT-4, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # CAUTION: This remediation script will remove quagga
    #	   from the system, and may remove any packages
    #	   that depend on quagga. Execute this
    #	   remediation AFTER testing on a non-production
    #	   system!

    if rpm -q --quiet "quagga" ; then

        yum remove -y "quagga"

    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Ensure quagga is removed
      package:
        name: quagga
        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_quagga_removed
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    include remove_quagga

    class remove_quagga {
      package { 'quagga':
        ensure => 'purged',
      }
    }
Remediation Anaconda snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    package --remove=quagga
#### Rule Disable Quagga Service [ref]
The `zebra` service can be disabled with the following command:
    $ sudo systemctl mask --now zebra.service
Rationale: | Routing protocol daemons are typically used on routers to exchange network topology information with other routers. If routing daemons are used when not required, system network information may be unnecessarily transmitted across the network.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_service_zebra_disabled
Identifiers and References | References: 12, 15, 8, APO13.01, DSS05.02, CCI-000366, 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), 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, A.13.1.1, A.13.2.1, A.14.1.3, CM-7(a), CM-7(b), CM-6(a), PR.PT-4, SRG-OS-000480-GPOS-00227
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    SYSTEMCTL_EXEC='/usr/bin/systemctl'
    "$SYSTEMCTL_EXEC" stop 'zebra.service'
    "$SYSTEMCTL_EXEC" disable 'zebra.service'
    "$SYSTEMCTL_EXEC" mask 'zebra.service'
    # Disable socket activation if we have a unit file for it
    if "$SYSTEMCTL_EXEC" list-unit-files | grep -q '^zebra.socket'; then
        "$SYSTEMCTL_EXEC" stop 'zebra.socket'
        "$SYSTEMCTL_EXEC" mask 'zebra.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 'zebra.service' || true

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | disable
    - name: Disable service zebra
      block:

        - name: Disable service zebra
          systemd:
            name: zebra.service
            enabled: 'no'
            state: stopped
            masked: 'yes'
          ignore_errors: 'yes'
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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
        - medium_severity
        - no_reboot_needed
        - service_zebra_disabled

    - name: Unit Socket Exists - zebra.socket
      command: systemctl list-unit-files zebra.socket
      args:
        warn: false
      register: socket_file_exists
      changed_when: false
      ignore_errors: true
      check_mode: false
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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
        - medium_severity
        - no_reboot_needed
        - service_zebra_disabled

    - name: Disable socket zebra
      systemd:
        name: zebra.socket
        enabled: 'no'
        state: stopped
        masked: 'yes'
      when:
        - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
        - '"zebra.socket" in socket_file_exists.stdout_lines[1]'
      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
        - medium_severity
        - no_reboot_needed
        - service_zebra_disabled
Remediation Puppet snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | enable
    include disable_zebra

    class disable_zebra {
      service {'zebra':
        enable => false,
        ensure => 'stopped',
      }
    }
Remediation script ⇲
    [customizations.services]
    disabled = ["zebra"]
Group SSH Server Group contains 1 group and 9 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 9 rules
[ref] If the system needs to act as an SSH server, then certain changes should be made to the OpenSSH daemon configuration file `/etc/ssh/sshd_config`. The following recommendations can be applied to this file. See the `sshd_config(5)` man page for more detailed information.
#### Rule Disable 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`:
    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
Identifiers and References | References: NT007(R17), 11, 12, 13, 14, 15, 16, 18, 3, 5, 9, 5.5.6, APO01.06, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.03, DSS06.06, 3.1.1, 3.1.5, CCI-000366, CCI-000766, 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), 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, 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, 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, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.AC-4, PR.AC-6, PR.DS-5, PR.IP-1, PR.PT-3, FIA_UAU.1, SRG-OS-000106-GPOS-00053, SRG-OS-000480-GPOS-00229, SRG-OS-000480-GPOS-00227, SRG-OS-000480-VMM-002000, OL08-00-020330, SV-248714r779708_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*PermitEmptyPasswords\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "PermitEmptyPasswords no" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "PermitEmptyPasswords no" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Disable SSH Access via Empty Passwords
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitEmptyPasswords\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitEmptyPasswords\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*PermitEmptyPasswords\s+
            line: PermitEmptyPasswords no
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - CJIS-5.5.6
        - DISA-STIG-OL08-00-020330
        - 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)
        - high_severity
        - low_complexity
        - low_disruption
        - no_reboot_needed
        - restrict_strategy
        - sshd_disable_empty_passwords
#### Rule Disable GSSAPI Authentication [ref]
Unless needed, SSH should not permit extraneous or unnecessary authentication mechanisms like GSSAPI. The default SSH configuration disallows authentications based on GSSAPI. The appropriate configuration is used if no value is set for `GSSAPIAuthentication`. To explicitly disable GSSAPI authentication, add or correct the following line in `/etc/ssh/sshd_config`:
    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
Identifiers and References | References: 11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.12, CCI-000318, CCI-000368, CCI-001812, CCI-001813, CCI-001814, CCI-000366, 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), 4.3.4.3.2, 4.3.4.3.3, SR 7.6, 0418, 1055, 1402, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, CM-7(a), CM-7(b), CM-6(a), AC-17(a), PR.IP-1, FTP_ITC_EXT.1, SRG-OS-000364-GPOS-00151, SRG-OS-000480-GPOS-00227, SRG-OS-000480-VMM-002000, OL08-00-010522, SV-248607r779387_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*GSSAPIAuthentication\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "GSSAPIAuthentication no" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "GSSAPIAuthentication no" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Disable GSSAPI Authentication
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*GSSAPIAuthentication\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*GSSAPIAuthentication\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*GSSAPIAuthentication\s+
            line: GSSAPIAuthentication no
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010522
        - 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`:
    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
Identifiers and References | References: 11, 12, 14, 15, 16, 18, 3, 5, 9, 5.5.6, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06, 3.1.12, CCI-000366, 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, 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, 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, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.AC-4, PR.AC-6, PR.IP-1, PR.PT-3, FIA_UAU.1, SRG-OS-000480-GPOS-00227, SRG-OS-000107-VMM-000530
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*IgnoreRhosts\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "IgnoreRhosts yes" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "IgnoreRhosts yes" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Disable SSH Support for .rhosts Files
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*IgnoreRhosts\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*IgnoreRhosts\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*IgnoreRhosts\s+
            line: IgnoreRhosts yes
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      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)
        - 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`:
    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
Identifiers and References | References: BP28(R19), NT007(R21), 1, 11, 12, 13, 14, 15, 16, 18, 3, 5, 5.5.6, APO01.06, DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.06, DSS06.10, 3.1.1, 3.1.5, CCI-000366, CCI-000770, 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), 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, 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, 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, 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, AC-6(2), AC-17(a), IA-2, IA-2(5), CM-7(a), CM-7(b), CM-6(a), PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5, PR.PT-3, FIA_UAU.1, SRG-OS-000109-GPOS-00056, SRG-OS-000480-GPOS-00227, SRG-OS-000480-VMM-002000, OL08-00-010550, SV-248613r779405_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "PermitRootLogin no" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "PermitRootLogin no" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Disable SSH Root Login
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitRootLogin\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitRootLogin\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*PermitRootLogin\s+
            line: PermitRootLogin no
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - CJIS-5.5.6
        - DISA-STIG-OL08-00-010550
        - 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)
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sshd_disable_root_login
#### Rule Disable SSH Support for User Known Hosts [ref]
SSH can allow system users to connect to systems if a cache of the remote systems public keys is available. This should be disabled. To ensure this behavior is disabled, add or correct the following line in `/etc/ssh/sshd_config`:
    IgnoreUserKnownHosts yes
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: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_disable_user_known_hosts
Identifiers and References | References: 11, 3, 9, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.12, CCI-000366, 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), 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.IP-1, FIA_UAU.1, SRG-OS-000480-GPOS-00227, OL08-00-010520, SV-248605r779381_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*IgnoreUserKnownHosts\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "IgnoreUserKnownHosts yes" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "IgnoreUserKnownHosts yes" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Disable SSH Support for User Known Hosts
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*IgnoreUserKnownHosts\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*IgnoreUserKnownHosts\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*IgnoreUserKnownHosts\s+
            line: IgnoreUserKnownHosts yes
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010520
        - 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_user_known_hosts
#### 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`:
    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
Identifiers and References | References: 11, 3, 9, 5.5.6, BAI10.01, BAI10.02, BAI10.03, BAI10.05, 3.1.12, CCI-000366, 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), 4.3.4.3.2, 4.3.4.3.3, SR 7.6, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, AC-17(a), CM-7(a), CM-7(b), CM-6(a), PR.IP-1, SRG-OS-000480-GPOS-00229, SRG-OS-000480-VMM-002000, OL08-00-010830, SV-248650r779516_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*PermitUserEnvironment\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "PermitUserEnvironment no" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "PermitUserEnvironment no" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Do Not Allow SSH Environment Options
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitUserEnvironment\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PermitUserEnvironment\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*PermitUserEnvironment\s+
            line: PermitUserEnvironment no
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - CJIS-5.5.6
        - DISA-STIG-OL08-00-010830
        - 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_do_not_permit_user_env
#### Rule Enable Use of Strict Mode Checking [ref]
SSHs `StrictModes` option checks file and ownership permissions in the user's home directory `.ssh` folder before accepting login. If world- writable permissions are found, logon is rejected. The default SSH configuration has `StrictModes` enabled. The appropriate configuration is used if no value is set for `StrictModes`. To explicitly enable `StrictModes` in SSH, add or correct the following line in `/etc/ssh/sshd_config`:
    StrictModes yes
Rationale: | If other users have access to modify user-specific SSH configuration files, they may be able to log into the system as another user.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_enable_strictmodes
Identifiers and References | References: 12, 13, 14, 15, 16, 18, 3, 5, APO01.06, DSS05.04, DSS05.07, DSS06.02, 3.1.12, CCI-000366, 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), 4.3.3.7.3, SR 2.1, SR 5.2, 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, 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, AC-6, AC-17(a), CM-6(a), PR.AC-4, PR.DS-5, SRG-OS-000480-GPOS-00227, SRG-OS-000480-VMM-002000, OL08-00-010500, SV-248603r779375_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*StrictModes\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "StrictModes yes" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "StrictModes yes" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Enable Use of Strict Mode Checking
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*StrictModes\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*StrictModes\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*StrictModes\s+
            line: StrictModes yes
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-010500
        - NIST-800-171-3.1.12
        - NIST-800-53-AC-17(a)
        - NIST-800-53-AC-6
        - NIST-800-53-CM-6(a)
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sshd_enable_strictmodes
#### Rule Enable SSH Print Last Log [ref]
Ensure that SSH will display the date and time of the last successful account logon. The default SSH configuration enables print of the date and time of the last login. The appropriate configuration is used if no value is set for `PrintLastLog`. To explicitly enable LastLog in SSH, add or correct the following line in `/etc/ssh/sshd_config`:
    PrintLastLog yes
Rationale: | Providing users feedback on when account accesses last occurred facilitates user recognition and reporting of unauthorized account use.
Severity: | medium
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_print_last_log
Identifiers and References | References: 1, 12, 15, 16, DSS05.04, DSS05.10, DSS06.10, CCI-000366, 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, SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9, A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3, AC-9, AC-17(a), CM-6(a), PR.AC-7, SRG-OS-000480-GPOS-00227, OL08-00-020350, SV-248718r779720_rule
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*PrintLastLog\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "PrintLastLog yes" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "PrintLastLog yes" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Enable SSH Print Last Log
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PrintLastLog\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*PrintLastLog\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*PrintLastLog\s+
            line: PrintLastLog yes
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - DISA-STIG-OL08-00-020350
        - NIST-800-53-AC-17(a)
        - NIST-800-53-AC-9
        - NIST-800-53-CM-6(a)
        - low_complexity
        - low_disruption
        - medium_severity
        - no_reboot_needed
        - restrict_strategy
        - sshd_print_last_log
#### Rule Set LogLevel to INFO [ref]
The INFO parameter specifices that record login and logout activity will be logged. The default SSH configuration sets the log level to INFO. The appropriate configuration is used if no value is set for `LogLevel`. To explicitly specify the log level in SSH, add or correct the following line in `/etc/ssh/sshd_config`:
    LogLevel INFO
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` 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: | low
Rule ID: | xccdf_org.ssgproject.content_rule_sshd_set_loglevel_info
Identifiers and References | References: AC-17(a), CM-6(a)
Remediation Shell script ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    # Remediation is applicable only in certain platforms
    if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; then

    if [ -e "/etc/ssh/sshd_config" ] ; then

        LC_ALL=C sed -i "/^\s*LogLevel\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert before the line matching the regex '^Match'.
    line_number="$(LC_ALL=C grep -n "^Match" "/etc/ssh/sshd_config.bak" | LC_ALL=C sed 's/:.*//g')"
    if [ -z "$line_number" ]; then
        # There was no match of '^Match', insert at
        # the end of the file.
        printf '%s\n' "LogLevel INFO" >> "/etc/ssh/sshd_config"
    else
        head -n "$(( line_number - 1 ))" "/etc/ssh/sshd_config.bak" > "/etc/ssh/sshd_config"
        printf '%s\n' "LogLevel INFO" >> "/etc/ssh/sshd_config"
        tail -n "+$(( line_number ))" "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    fi
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"

    else
        >&2 echo 'Remediation is not applicable, nothing was done'
    fi
Remediation Ansible snippet ⇲
Complexity: | low
Disruption: | low
Strategy: | restrict
    - name: Set LogLevel to INFO
      block:

        - name: Check for duplicate values
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*LogLevel\s+
            state: absent
          check_mode: true
          changed_when: false
          register: dupes

        - name: Deduplicate values from /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: false
            regexp: (?i)^\s*LogLevel\s+
            state: absent
          when: dupes.found is defined and dupes.found > 1

        - name: Insert correct line to /etc/ssh/sshd_config
          lineinfile:
            path: /etc/ssh/sshd_config
            create: true
            regexp: (?i)^\s*LogLevel\s+
            line: LogLevel INFO
            state: present
            insertbefore: ^[#\s]*Match
            validate: /usr/sbin/sshd -t -f %s
      when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
      tags:
        - NIST-800-53-AC-17(a)
        - NIST-800-53-CM-6(a)
        - low_complexity
        - low_disruption
        - low_severity
        - no_reboot_needed
        - restrict_strategy
        - sshd_set_loglevel_info

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.