Guide to the Secure Configuration of Red Hat Enterprise Linux 7

with profile Standard System Security Profile for Red Hat Enterprise Linux 7
This profile contains rules to ensure standard security baseline of a Red Hat Enterprise Linux 7 system. Regardless of your system's workload all of these checks should pass.
This guide presents a catalog of security-relevant configuration settings for Red Hat Enterprise Linux 7. 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 TitleStandard System Security Profile for Red Hat Enterprise Linux 7
Profile IDxccdf_org.ssgproject.content_profile_standard

CPE Platforms

  • cpe:/o:redhat:enterprise_linux:7::client
  • cpe:/o:redhat:enterprise_linux:7::computenode
  • cpe:/o:redhat:enterprise_linux:7::server
  • cpe:/o:redhat:enterprise_linux:7::workstation
  • cpe:/o:redhat:enterprise_linux:7

Revision History

Current version: 0.1.74

  • draft (as of 2024-06-20)

Table of Contents

  1. System Settings
    1. Installing and Maintaining Software
    2. Account and Access Control
    3. GRUB2 bootloader configuration
    4. Configure Syslog
    5. File Permissions and Masks
  2. Services
    1. Base Services
    2. Cron and At Daemons
  3. System Accounting with auditd
    1. Configure auditd Rules for Comprehensive Auditing

Checklist

Group   Guide to the Secure Configuration of Red Hat Enterprise Linux 7   Group contains 32 groups and 74 rules
Group   System Settings   Group contains 20 groups and 40 rules
[ref]   Contains rules that check correct system settings.
Group   Installing and Maintaining Software   Group contains 5 groups and 7 rules
[ref]   The following sections contain information on security-relevant choices during the initial operating system installation process and the setup of software updates.
Group   System and Software Integrity   Group contains 2 groups and 2 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 2 rules
[ref]   Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes.

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

Rule   Verify File Hashes with RPM   [ref]

Without cryptographic integrity protections, system executables and files can be altered by unauthorized users without detection. The RPM package management system can check the hashes of installed software packages, including many that are important to system security. To verify that the cryptographic hash of system files and commands matches vendor values, run the following command to list which files on the system have hashes that differ from what is expected by the RPM database:
$ rpm -Va --noconfig | grep '^..5'
If the file was not expected to change, investigate the cause of the change using audit logs or other means. The package can then be reinstalled to restore the file. Run the following command to determine which package owns the file:
$ rpm -qf FILENAME
          
The package can be reinstalled from a 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
          
Warning:  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of packages present on the system. It is not a problem in most cases, but especially systems with a large number of installed packages can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
The hashes of important files like system executables should match the information given by the RPM database. Executables with erroneous hashes could be a sign of nefarious activity on the system.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_rpm_verify_hashes
Identifiers:

CCE-27157-7

References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.3.8, 3.4.1
disaCCI-000366, CCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
pcidssReq-11.5
os-srgSRG-OS-000480-GPOS-00227
stigidRHEL-07-010020
pcidss411.5.2
stigrefSV-214799r854001_rule

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

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

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

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

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


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

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

    
    yum reinstall -y $packages_to_reinstall
    
fi

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.
Warning:  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of packages present on the system. It is not a problem in most cases, but especially systems with a large number of installed packages can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
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:

CCE-27209-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 5, 6, 9
cjis5.10.4.1
cobit5APO01.06, APO11.04, BAI03.05, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.04, DSS05.07, DSS06.02, MEA02.01
cui3.3.8, 3.4.1
disaCCI-001493, CCI-001494, CCI-001495, CCI-001496
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.3.7.3, 4.3.4.3.2, 4.3.4.3.3, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.1, SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 5.2, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.12.1.2, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.5.1, A.12.6.2, A.12.7.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2
nistCM-6(d), CM-6(c), SI-7, SI-7(1), SI-7(6), AU-9(3), CM-6(a)
nist-csfPR.AC-4, PR.DS-5, PR.IP-1, PR.PT-1
pcidssReq-11.5
os-srgSRG-OS-000256-GPOS-00097, SRG-OS-000257-GPOS-00098, SRG-OS-000258-GPOS-00099, SRG-OS-000278-GPOS-00108
stigidRHEL-07-010010
pcidss411.5.2
stigrefSV-204392r880752_rule

Complexity:high
Disruption:medium
Reboot:false
Strategy:restrict
- name: Read list of files with incorrect permissions
  command: rpm -Va --nodeps --nosignature --nofiledigest --nosize --nomtime --nordev
    --nocaps --nolinkto --nouser --nogroup
  register: files_with_incorrect_permissions
  failed_when: files_with_incorrect_permissions.rc > 1
  changed_when: false
  check_mode: false
  tags:
  - CCE-27209-6
  - CJIS-5.10.4.1
  - DISA-STIG-RHEL-07-010010
  - 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
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_permissions

- name: Create list of packages
  command: rpm -qf "{{ item }}"
  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:
  - CCE-27209-6
  - CJIS-5.10.4.1
  - DISA-STIG-RHEL-07-010010
  - 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
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_permissions

- name: Correct file permissions with RPM
  command: rpm --setperms '{{ item }}'
  with_items: '{{ list_of_packages.results | map(attribute=''stdout_lines'') | list
    | unique }}'
  when: (files_with_incorrect_permissions.stdout_lines | length > 0)
  tags:
  - CCE-27209-6
  - CJIS-5.10.4.1
  - DISA-STIG-RHEL-07-010010
  - 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
  - PCI-DSSv4-11.5.2
  - high_complexity
  - high_severity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy
  - rpm_verify_permissions

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

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

# Create a list of files on the system having permissions different from what
# is expected by the RPM database
readarray -t FILES_WITH_INCORRECT_PERMS < <(rpm -Va --nofiledigest | awk '{ if (substr($0,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
Group   Disk Partitioning   Group contains 2 rules
[ref]   To ensure separation and protection of data, there are top-level system directories which should be placed on their own physical partition or logical volume. The installer's default partitioning scheme creates separate logical volumes for /, /boot, and swap.
  • If starting with any of the default layouts, check the box to \"Review and modify partitioning.\" This allows for the easy creation of additional logical volumes inside the volume group already created, though it may require making /'s logical volume smaller to create space. In general, using logical volumes is preferable to using partitions because they can be more easily adjusted later.
  • If creating a custom layout, create the partitions mentioned in the previous paragraph (which the installer will require anyway), as well as separate ones described in the following sections.
If a system has already been installed, and the default partitioning scheme was used, it is possible but nontrivial to modify it to create separate logical volumes for the directories listed above. The Logical Volume Manager (LVM) makes this possible. See the LVM HOWTO at http://tldp.org/HOWTO/LVM-HOWTO/ for more detailed information on LVM.

Rule   Ensure /var/log Located On Separate Partition   [ref]

System logs are stored in the /var/log directory. Ensure that /var/log has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale:
Placing /var/log in its own partition enables better separation between log files and other files in /var/.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_var_log
Identifiers:

CCE-82034-0

References:
cis-csc1, 12, 14, 15, 16, 3, 5, 6, 8
cobit5APO11.04, APO13.01, BAI03.05, DSS05.02, DSS05.04, DSS05.07, MEA02.01
disaCCI-000366
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.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
nerc-cipCIP-007-3 R6.5
nistCM-6(a), AU-4, SC-5(2)
nist-csfPR.PT-1, PR.PT-4
os-srgSRG-OS-000480-GPOS-00227
anssiR28
cis1.1.2.6.1


[[customizations.filesystem]]
mountpoint = "/var/log"
size = 5368709120

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

part /var/log

Rule   Ensure /var/log/audit Located On Separate Partition   [ref]

Audit logs are stored in the /var/log/audit directory. Ensure that /var/log/audit has its own partition or logical volume at installation time, or migrate it using LVM. Make absolutely certain that it is large enough to store all audit logs that will be created by the auditing daemon.
Rationale:
Placing /var/log/audit in its own partition enables better separation between audit files and other files, and helps ensure that auditing cannot be halted due to the partition running out of space.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_var_log_audit
Identifiers:

CCE-82035-7

References:
cis-csc1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 8
cobit5APO11.04, APO13.01, BAI03.05, BAI04.04, DSS05.02, DSS05.04, DSS05.07, MEA02.01
disaCCI-000366, CCI-001849
hipaa164.312(a)(2)(ii)
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.2, SR 7.6
iso27001-2013A.12.1.3, A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1, A.13.1.1, A.13.2.1, A.14.1.3, A.17.2.1
nerc-cipCIP-007-3 R6.5
nistCM-6(a), AU-4, SC-5(2)
nist-csfPR.DS-4, PR.PT-1, PR.PT-4
osppFMT_SMF_EXT.1
os-srgSRG-OS-000341-GPOS-00132, SRG-OS-000480-GPOS-00227
app-srg-ctrSRG-APP-000357-CTR-000800
stigidRHEL-07-021330
anssiR71
cis1.1.2.7.1
stigrefSV-204495r603261_rule


[[customizations.filesystem]]
mountpoint = "/var/log/audit"
size = 10737418240

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

part /var/log/audit
Group   Updating Software   Group contains 3 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.

Red Hat Enterprise Linux 7 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   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:

CCE-26989-4

References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
disaCCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a), SA-12, SA-12(10), CM-11(a), CM-11(b)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
stigidRHEL-07-020050
anssiR59
cis1.2.2
pcidss46.3.3
stigrefSV-204447r877463_rule

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

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

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

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

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

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

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

Rule   Ensure Red Hat GPG Key Installed   [ref]

To ensure the system can cryptographically verify base software packages come from Red Hat (and to connect to the Red Hat Network to receive them), the Red Hat GPG key must properly be installed. To install the Red Hat GPG key, run:
$ sudo subscription-manager register
If the system is not connected to the Internet or an RHN Satellite, then install the Red Hat GPG key from trusted media such as the Red Hat installation CD-ROM or DVD. Assuming the disc is mounted in /media/cdrom, use the following command as the root user to import it into the keyring:
$ sudo rpm --import /media/cdrom/RPM-GPG-KEY
Alternatively, the key may be pre-loaded during the RHEL installation. In such cases, the key can be installed by running the following command:
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
Rationale:
Changes to software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor. The Red Hat GPG key is necessary to cryptographically verify packages are from Red Hat.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_redhat_gpgkey_installed
Identifiers:

CCE-26957-1

References:
cis-csc11, 2, 3, 9
cjis5.10.4.1
cobit5APO01.06, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS06.02
cui3.4.8
disaCCI-001749
hipaa164.308(a)(1)(ii)(D), 164.312(b), 164.312(c)(1), 164.312(c)(2), 164.312(e)(2)(i)
isa-62443-20094.3.4.3.2, 4.3.4.3.3, 4.3.4.4.4
isa-62443-2013SR 3.1, SR 3.3, SR 3.4, SR 3.8, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4
nerc-cipCIP-003-8 R4.2, CIP-003-8 R6, CIP-007-3 R4, CIP-007-3 R4.1, CIP-007-3 R4.2, CIP-007-3 R5.1
nistCM-5(3), SI-7, SC-12, SC-12(3), CM-6(a)
nist-csfPR.DS-6, PR.DS-8, PR.IP-1
osppFPT_TUD_EXT.1, FPT_TUD_EXT.2
pcidssReq-6.2
os-srgSRG-OS-000366-GPOS-00153
stigidRHEL-07-010019
anssiR59
pcidss46.3.3
stigrefSV-256968r902687_rule

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

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

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

- name: Set Fact - Valid fingerprints
  set_fact:
    gpg_valid_fingerprints:
    - 567E347AD0044ADE55BA8A5F199E2F91FD431D51
    - 43A6E49C4A38F4BE9ABF2A5345689C882FA658E0
  tags:
  - CCE-26957-1
  - CJIS-5.10.4.1
  - DISA-STIG-RHEL-07-010019
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3.3
  - ensure_redhat_gpgkey_installed
  - high_severity
  - medium_complexity
  - medium_disruption
  - no_reboot_needed
  - restrict_strategy

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

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

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

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

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

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

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

Rule   Ensure Software Patches Installed   [ref]

If the system is joined to the Red Hat Network, a Red Hat Satellite Server, 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 Red Hat Network and installed using rpm.

NOTE: U.S. Defense systems are required to be patched within 30 days or sooner as local policy dictates.
Warning:  Red Hat Enterprise Linux 7 does not have a corresponding OVAL CVE Feed. Therefore, this will result in a "not checked" result during a scan.
Rationale:
Installing software updates is a fundamental mitigation against the exploitation of publicly-known vulnerabilities. If the most recent security patches and updates are not installed, unauthorized users may take advantage of weaknesses in the unpatched software. The lack of prompt attention to patching could result in a system compromise.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_security_patches_up_to_date
Identifiers:

CCE-26895-3

References:
cis-csc18, 20, 4
cjis5.10.4.1
cobit5APO12.01, APO12.02, APO12.03, APO12.04, BAI03.10, DSS05.01, DSS05.02
disaCCI-000366, CCI-001227
isa-62443-20094.2.3, 4.2.3.12, 4.2.3.7, 4.2.3.9
iso27001-2013A.12.6.1, A.14.2.3, A.16.1.3, A.18.2.2, A.18.2.3
nistSI-2(5), SI-2(c), CM-6(a)
nist-csfID.RA-1, PR.IP-12
osppFMT_MOF_EXT.1
pcidssReq-6.2
os-srgSRG-OS-000480-GPOS-00227
stigidRHEL-07-020260
anssiR61
pcidss46.3.3
stigrefSV-204459r603261_rule
Group   Account and Access Control   Group contains 4 groups and 3 rules
[ref]   In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under Red Hat Enterprise Linux 7.
Group   Protect Accounts by Restricting Password-Based Login   Group contains 1 group and 2 rules
[ref]   Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the /etc/passwd and /etc/shadow files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.
Group   Verify Proper Storage and Existence of Password Hashes   Group contains 2 rules
[ref]   By default, password hashes for local accounts are stored in the second field (colon-separated) in /etc/shadow. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as /etc/passwd, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.

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

Add a group to the system for each GID referenced without a corresponding group.
Rationale:
If a user is assigned the Group Identifier (GID) of a group not existing on the system, and a group with the Group Identifier (GID) is subsequently created, the user may have unintended rights to any files associated with the group.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_gid_passwd_group_same
Identifiers:

CCE-27503-2

References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
disaCCI-000764
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1
iso27001-2013A.18.1.4, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-2, CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.5.a
os-srgSRG-OS-000104-GPOS-00051
stigidRHEL-07-020300
cis6.2.3
pcidss48.2.2
stigrefSV-204461r603261_rule

Rule   Prevent Login to Accounts With Empty Password   [ref]

If an account is configured for password authentication but does not have an assigned password, it may be possible to log into the account without authentication. Remove any instances of the nullok in /etc/pam.d/system-auth and /etc/pam.d/password-auth to prevent logins with empty passwords.
Warning:  If the system relies on authselect tool to manage PAM settings, the remediation will also use authselect tool. However, if any manual modification was made in PAM files, the authselect integrity check will fail and the remediation will be aborted in order to preserve intentional changes. In this case, an informative message will be shown in the remediation report. Note that this rule is not applicable for systems running within a container. Having user with empty password within a container is not considered a risk, because it should not be possible to directly login into a container anyway.
Rationale:
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_no_empty_passwords
Identifiers:

CCE-27286-4

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
disaCCI-000366
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.18.1.4, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistIA-5(1)(a), IA-5(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
osppFIA_UAU.1
pcidssReq-8.2.3
os-srgSRG-OS-000480-GPOS-00227
stigidRHEL-07-010290
cis4.4.2.4.1
pcidss48.3.1
stigrefSV-204424r880839_rule

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

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

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

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

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

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

  - name: Prevent Login to Accounts With Empty Password - Ensure authselect changes
      are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_enable_feature_cmd is not skipped
    - result_authselect_enable_feature_cmd is success
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - result_authselect_present.stat.exists
  tags:
  - CCE-27286-4
  - CJIS-5.5.2
  - DISA-STIG-RHEL-07-010290
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3.1
  - configure_strategy
  - high_severity
  - low_complexity
  - medium_disruption
  - no_empty_passwords
  - no_reboot_needed

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

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

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

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

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

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Secure Session Configuration Files for Login Accounts   Group contains 1 group and 1 rule
[ref]   When a user logs into a Unix account, the system configures the user's session by reading a number of files. Many of these files are located in the user's home directory, and may have weak permissions as a result of user error or misconfiguration. If an attacker can modify or even read certain types of account configuration information, they can often gain full access to the affected user's account. Therefore, it is important to test and correct configuration file permissions for interactive accounts, particularly those of privileged users such as root or system administrators.
Group   Ensure that No Dangerous Directories Exist in Root's Path   Group contains 1 rule
[ref]   The active path of the root account can be obtained by starting a new root shell and running:
# echo $PATH
This will produce a colon-separated list of directories in the path.

Certain path elements could be considered dangerous, as they could lead to root executing unknown or untrusted programs, which could contain malicious code. Since root may sometimes work inside untrusted directories, the . character, which represents the current directory, should never be in the root path, nor should any directory which can be written to by an unprivileged or semi-privileged (system) user.

It is a good practice for administrators to always execute privileged commands by typing the full path to the command.

Rule   Ensure that Root's Path Does Not Include World or Group-Writable Directories   [ref]

For each element in root's path, run:
# ls -ld DIR
         
and ensure that write permissions are disabled for group and other.
Rationale:
Such entries increase the risk that root could execute code provided by unprivileged users, and potentially malicious code.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_root_path_dirs_no_write
Identifiers:

CCE-80200-9

References:
cis-csc11, 3, 9
cobit5BAI10.01, BAI10.02, BAI10.03, BAI10.05
disaCCI-000366
isa-62443-20094.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 7.6
iso27001-2013A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4
nistCM-6(a), CM-6(a)
nist-csfPR.IP-1
cis6.2.8

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Get root paths which are not symbolic links
  stat:
    path: '{{ item }}'
  changed_when: false
  failed_when: false
  register: root_paths
  with_items: '{{ ansible_env.PATH.split('':'') }}'
  tags:
  - CCE-80200-9
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - accounts_root_path_dirs_no_write
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Disable writability to root directories
  file:
    path: '{{ item.item }}'
    mode: g-w,o-w
  with_items: '{{ root_paths.results }}'
  when:
  - root_paths.results is defined
  - item.stat.exists
  - not item.stat.islnk
  tags:
  - CCE-80200-9
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - accounts_root_path_dirs_no_write
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
Group   GRUB2 bootloader configuration   Group contains 2 groups and 12 rules
[ref]   During the boot process, the boot loader is responsible for starting the execution of the kernel and passing options to it. The boot loader allows for the selection of different kernels - possibly on different partitions or media. The default Red Hat Enterprise Linux 7 boot loader for x86 systems is called GRUB2. Options it can pass to the kernel include single-user mode, which provides root access without any authentication, and the ability to disable SELinux. To prevent local users from modifying the boot parameters and endangering security, protect the boot loader configuration with a password and ensure its configuration file's permissions are set properly.
Group   Non-UEFI GRUB2 bootloader configuration   Group contains 6 rules
[ref]   Non-UEFI GRUB2 bootloader configuration

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

The file /boot/grub2/grub.cfg should be group-owned by the root group to prevent destruction or modification of the file. To properly set the group owner of /boot/grub2/grub.cfg, run the command:
$ sudo chgrp root /boot/grub2/grub.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_grub2_cfg
Identifiers:

CCE-82023-3

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

chgrp 0 /boot/grub2/grub.cfg

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

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

The file /boot/grub2/user.cfg should be group-owned by the root group to prevent reading or modification of the file. To properly set the group owner of /boot/grub2/user.cfg, run the command:
$ sudo chgrp root /boot/grub2/user.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway. Non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_user_cfg
Identifiers:

CCE-86008-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

chgrp 0 /boot/grub2/user.cfg

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

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

The file /boot/grub2/grub.cfg should be owned by the root user to prevent destruction or modification of the file. To properly set the owner of /boot/grub2/grub.cfg, run the command:
$ sudo chown root /boot/grub2/grub.cfg 
Rationale:
Only root should be able to modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_grub2_cfg
Identifiers:

CCE-82026-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
os-srgSRG-OS-000480-GPOS-00227
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

chown 0 /boot/grub2/grub.cfg

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

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

The file /boot/grub2/user.cfg should be owned by the root user to prevent reading or modification of the file. To properly set the owner of /boot/grub2/user.cfg, run the command:
$ sudo chown root /boot/grub2/user.cfg 
Rationale:
Only root should be able to modify important boot parameters. Also, non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_user_cfg
Identifiers:

CCE-86014-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

chown 0 /boot/grub2/user.cfg

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

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

File permissions for /boot/grub2/grub.cfg should be set to 600. To properly set the permissions of /boot/grub2/grub.cfg, run the command:
$ sudo chmod 600 /boot/grub2/grub.cfg
Rationale:
Proper permissions ensure that only the root user can modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_grub2_cfg
Identifiers:

CCE-82039-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

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

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

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

File permissions for /boot/grub2/user.cfg should be set to 600. To properly set the permissions of /boot/grub2/user.cfg, run the command:
$ sudo chmod 600 /boot/grub2/user.cfg
Rationale:
Proper permissions ensure that only the root user can read or modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_user_cfg
Identifiers:

CCE-86023-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
hipaa164.308(a)(1)(ii)(B), 164.308(a)(7)(i), 164.308(a)(7)(ii)(A), 164.310(a)(1), 164.310(a)(2)(i), 164.310(a)(2)(ii), 164.310(a)(2)(iii), 164.310(b), 164.310(c), 164.310(d)(1), 164.310(d)(2)(iii)
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.3.2
pcidss42.2.6

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

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

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

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

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

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   UEFI GRUB2 bootloader configuration   Group contains 6 rules
[ref]   UEFI GRUB2 bootloader configuration
Warning:  UEFI generally uses vfat file systems, which does not support Unix-style permissions managed by chmod command. In this case, in order to change file permissions for files within /boot/efi it is necessary to update the mount options in /etc/fstab file and reboot the system.

Rule   Verify the UEFI Boot Loader grub.cfg Group Ownership   [ref]

The file /boot/efi/EFI/redhat/grub.cfg should be group-owned by the root group to prevent destruction or modification of the file. To properly set the group owner of /boot/efi/EFI/redhat/grub.cfg, run the command:
$ sudo chgrp root /boot/efi/EFI/redhat/grub.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_efi_grub2_cfg
Identifiers:

CCE-83430-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.3.2

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

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

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

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

chgrp 0 /boot/efi/EFI/redhat/grub.cfg

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

Rule   Verify /boot/efi/EFI/redhat/user.cfg Group Ownership   [ref]

The file /boot/efi/EFI/redhat/user.cfg should be group-owned by the root group to prevent reading or modification of the file. To properly set the group owner of /boot/efi/EFI/redhat/user.cfg, run the command:
$ sudo chgrp root /boot/efi/EFI/redhat/user.cfg
Rationale:
The root group is a highly-privileged group. Furthermore, the group-owner of this file should not have any access privileges anyway. Non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_efi_user_cfg
Identifiers:

CCE-86011-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.3.2

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

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

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

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

chgrp 0 /boot/efi/EFI/redhat/user.cfg

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

Rule   Verify the UEFI Boot Loader grub.cfg User Ownership   [ref]

The file /boot/efi/EFI/redhat/grub.cfg should be owned by the root user to prevent destruction or modification of the file. To properly set the owner of /boot/efi/EFI/redhat/grub.cfg, run the command:
$ sudo chown root /boot/efi/EFI/redhat/grub.cfg 
Rationale:
Only root should be able to modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_efi_grub2_cfg
Identifiers:

CCE-83429-1

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.3.2

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

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

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

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

chown 0 /boot/efi/EFI/redhat/grub.cfg

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

Rule   Verify /boot/efi/EFI/redhat/user.cfg User Ownership   [ref]

The file /boot/efi/EFI/redhat/user.cfg should be owned by the root user to prevent reading or modification of the file. To properly set the owner of /boot/efi/EFI/redhat/user.cfg, run the command:
$ sudo chown root /boot/efi/EFI/redhat/user.cfg 
Rationale:
Only root should be able to modify important boot parameters. Also, non-root users who read the boot parameters may be able to identify weaknesses in security upon boot and be able to exploit them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_efi_user_cfg
Identifiers:

CCE-86019-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-7.1
anssiR29
cis1.3.2

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

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

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

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

chown 0 /boot/efi/EFI/redhat/user.cfg

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

Rule   Verify the UEFI Boot Loader grub.cfg Permissions   [ref]

File permissions for /boot/efi/EFI/redhat/grub.cfg should be set to 700. To properly set the permissions of /boot/efi/EFI/redhat/grub.cfg, run the command:
$ sudo chmod 700 /boot/efi/EFI/redhat/grub.cfg
Rationale:
Proper permissions ensure that only the root user can modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_efi_grub2_cfg
Identifiers:

CCE-83431-7

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.3.2

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

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

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

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

chmod u-s,g-xwrs,o-xwrt /boot/efi/EFI/redhat/grub.cfg

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

Rule   Verify /boot/efi/EFI/redhat/user.cfg Permissions   [ref]

File permissions for /boot/efi/EFI/redhat/user.cfg should be set to 600. To properly set the permissions of /boot/efi/EFI/redhat/user.cfg, run the command:
$ sudo chmod 600 /boot/efi/EFI/redhat/user.cfg
Rationale:
Proper permissions ensure that only the root user can read or modify important boot parameters.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_efi_user_cfg
Identifiers:

CCE-86026-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
cui3.4.5
disaCCI-000225
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR29
cis1.3.2

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

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

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

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

chmod u-s,g-xwrs,o-xwrt /boot/efi/EFI/redhat/user.cfg

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
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 Red Hat Enterprise Linux 7, 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:

CCE-80187-8

References:
cis-csc1, 14, 15, 16, 3, 5, 6
cobit5APO11.04, BAI03.05, DSS05.04, DSS05.07, MEA02.01
disaCCI-001311, CCI-001312, CCI-000366
hipaa164.312(a)(2)(ii)
isa-62443-20094.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 2.10, SR 2.11, SR 2.12, SR 2.8, SR 2.9
iso27001-2013A.12.4.1, A.12.4.2, A.12.4.3, A.12.4.4, A.12.7.1
nistCM-6(a)
nist-csfPR.PT-1
osppFTP_ITC_EXT.1.1
os-srgSRG-OS-000479-GPOS-00224, SRG-OS-000051-GPOS-00024, SRG-OS-000480-GPOS-00227
cis5.1.1.1


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

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

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

package --add=rsyslog

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

class install_rsyslog {
  package { 'rsyslog':
    ensure => 'installed',
  }
}

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

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

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

Rule   Enable rsyslog Service   [ref]

The rsyslog service provides syslog-style logging by default on Red Hat Enterprise Linux 7. 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:

CCE-80188-6

References:
cis-csc1, 12, 13, 14, 15, 16, 2, 3, 5, 6, 7, 8, 9
cobit5APO10.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
disaCCI-001311, CCI-001312, CCI-001557, CCI-001851, CCI-000366
hipaa164.312(a)(2)(ii)
isa-62443-20094.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.4.4.7, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 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
iso27001-2013A.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
nistCM-6(a), AU-4(1)
nist-csfDE.CM-1, DE.CM-3, DE.CM-7, ID.SC-4, PR.DS-4, PR.PT-1
os-srgSRG-OS-000480-GPOS-00227
cis5.1.1.2


[customizations.services]
enabled = ["rsyslog"]

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Enable rsyslog Service - Enable service rsyslog
  block:

  - name: Gather the package facts
    package_facts:
      manager: auto

  - name: Enable rsyslog Service - Enable Service rsyslog
    ansible.builtin.systemd:
      name: rsyslog
      enabled: true
      state: started
      masked: false
    when:
    - '"rsyslog" in ansible_facts.packages'
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80188-6
  - 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

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

class enable_rsyslog {
  service {'rsyslog':
    enable => true,
    ensure => 'running',
  }
}

Complexity:low
Disruption:low
Reboot:false
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
Group   File Permissions and Masks   Group contains 4 groups and 16 rules
[ref]   Traditional Unix security relies heavily on file and directory permissions to prevent unauthorized users from reading or modifying files to which they should not have access.

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

The following command prints a list of all xfs partitions on the local system, which is the default filesystem for Red Hat Enterprise Linux 7 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 13 rules
[ref]   Permissions for many files on a system must be set restrictively to ensure sensitive information is properly protected. This section discusses important permission restrictions which can be verified to ensure that no harmful discrepancies have arisen.
Group   Verify Permissions on Files with Local Account Information and Credentials   Group contains 9 rules

Rule   Verify Group Who Owns group File   [ref]

To properly set the group owner of /etc/group, run the command:
$ sudo chgrp root /etc/group
Rationale:
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_group
Identifiers:

CCE-82037-3

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.3
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/group
  stat:
    path: /etc/group
  register: file_exists
  tags:
  - CCE-82037-3
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/group
  file:
    path: /etc/group
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82037-3
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chgrp 0 /etc/group

Rule   Verify Group Who Owns passwd File   [ref]

To properly set the group owner of /etc/passwd, run the command:
$ sudo chgrp root /etc/passwd
Rationale:
The /etc/passwd file contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_passwd
Identifiers:

CCE-26639-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.1
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/passwd
  stat:
    path: /etc/passwd
  register: file_exists
  tags:
  - CCE-26639-5
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/passwd
  file:
    path: /etc/passwd
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-26639-5
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chgrp 0 /etc/passwd

Rule   Verify Group Who Owns shadow File   [ref]

To properly set the group owner of /etc/shadow, run the command:
$ sudo chgrp root /etc/shadow
Rationale:
The /etc/shadow file stores password hashes. Protection of this file is critical for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_shadow
Identifiers:

CCE-82051-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.5
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/shadow
  stat:
    path: /etc/shadow
  register: file_exists
  tags:
  - CCE-82051-4
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/shadow
  file:
    path: /etc/shadow
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82051-4
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_groupowner_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chgrp 0 /etc/shadow

Rule   Verify User Who Owns group File   [ref]

To properly set the owner of /etc/group, run the command:
$ sudo chown root /etc/group 
Rationale:
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_group
Identifiers:

CCE-82031-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.3
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/group
  stat:
    path: /etc/group
  register: file_exists
  tags:
  - CCE-82031-6
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/group
  file:
    path: /etc/group
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82031-6
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chown 0 /etc/group

Rule   Verify User Who Owns passwd File   [ref]

To properly set the owner of /etc/passwd, run the command:
$ sudo chown root /etc/passwd 
Rationale:
The /etc/passwd file contains information about the users that are configured on the system. Protection of this file is critical for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_passwd
Identifiers:

CCE-82052-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.1
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/passwd
  stat:
    path: /etc/passwd
  register: file_exists
  tags:
  - CCE-82052-2
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/passwd
  file:
    path: /etc/passwd
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82052-2
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chown 0 /etc/passwd

Rule   Verify User Who Owns shadow File   [ref]

To properly set the owner of /etc/shadow, run the command:
$ sudo chown root /etc/shadow 
Rationale:
The /etc/shadow file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_shadow
Identifiers:

CCE-82022-5

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.5
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/shadow
  stat:
    path: /etc/shadow
  register: file_exists
  tags:
  - CCE-82022-5
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/shadow
  file:
    path: /etc/shadow
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82022-5
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_owner_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chown 0 /etc/shadow

Rule   Verify Permissions on group File   [ref]

To properly set the permissions of /etc/group, run the command:
$ sudo chmod 0644 /etc/group
Rationale:
The /etc/group file contains information regarding groups that are configured on the system. Protection of this file is important for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_group
Identifiers:

CCE-82032-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.3
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/group
  stat:
    path: /etc/group
  register: file_exists
  tags:
  - CCE-82032-4
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/group
  file:
    path: /etc/group
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82032-4
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_group
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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





chmod u-xs,g-xws,o-xwt /etc/group

Rule   Verify Permissions on passwd File   [ref]

To properly set the permissions of /etc/passwd, run the command:
$ sudo chmod 0644 /etc/passwd
Rationale:
If the /etc/passwd file is writable by a group-owner or the world the risk of its compromise is increased. The file contains the list of accounts on the system and associated information, and protection of this file is critical for system security.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_passwd
Identifiers:

CCE-82029-0

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.1
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/passwd
  stat:
    path: /etc/passwd
  register: file_exists
  tags:
  - CCE-82029-0
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/passwd
  file:
    path: /etc/passwd
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82029-0
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_passwd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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





chmod u-xs,g-xws,o-xwt /etc/passwd

Rule   Verify Permissions on shadow File   [ref]

To properly set the permissions of /etc/shadow, run the command:
$ sudo chmod 0000 /etc/shadow
Rationale:
The /etc/shadow file contains the list of local system accounts and stores password hashes. Protection of this file is critical for system security. Failure to give ownership of this file to root provides the designated owner with access to sensitive information which could weaken the system security posture.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_shadow
Identifiers:

CCE-82042-3

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cjis5.5.2.2
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-002223
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
pcidssReq-8.7.c
os-srgSRG-OS-000480-GPOS-00227
anssiR50
cis6.1.5
pcidss42.2.6

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/shadow
  stat:
    path: /etc/shadow
  register: file_exists
  tags:
  - CCE-82042-3
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xwrs,g-xwrs,o-xwrt on /etc/shadow
  file:
    path: /etc/shadow
    mode: u-xwrs,g-xwrs,o-xwrt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-82042-3
  - CJIS-5.5.2.2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-8.7.c
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_etc_shadow
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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





chmod u-xwrs,g-xwrs,o-xwrt /etc/shadow

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

When the so-called 'sticky bit' is set on a directory, only the owner of a given file may remove that file from the directory. Without the sticky bit, any user with write access to a directory may remove any file in the directory. Setting the sticky bit prevents users from removing each other's files. In cases where there is no reason for a directory to be world-writable, a better solution is to remove that permission rather than to set the sticky bit. However, if a directory is used by a particular application, consult that application's documentation instead of blindly changing modes.
To set the sticky bit on a world-writable directory DIR, run the following command:
$ sudo chmod +t DIR
        
Warning:  This rule can take a long time to perform the check and might consume a considerable amount of resources depending on the number of directories present on the system. It is not a problem in most cases, but especially systems with a large number of directories can be affected. See https://access.redhat.com/articles/6999111.
Rationale:
Failing to set the sticky bit on public directories allows unauthorized users to delete files in the directory structure.

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

CCE-80130-8

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-001090
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
os-srgSRG-OS-000138-GPOS-00069
anssiR54
cis6.1.11
pcidss42.2.6

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

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

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

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

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

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

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

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

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

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

Rule   Ensure All SGID Executables Are Authorized   [ref]

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

CCE-80132-4

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR56

Rule   Ensure All SUID Executables Are Authorized   [ref]

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

CCE-80133-2

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR56

Rule   Ensure No World-Writable Files Exist   [ref]

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

CCE-80131-6

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.13.1.1, A.13.1.3, A.13.2.1, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.6.1.2, A.7.1.1, A.7.1.2, A.7.3.1, A.8.2.2, A.8.2.3, A.9.1.1, A.9.1.2, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-6(a), AC-6(1)
nist-csfPR.AC-4, PR.DS-5
anssiR54
cis6.1.11
pcidss42.2.6

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

FILTER_NODEV=$(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,)
PARTITIONS=$(findmnt -n -l -k -it $FILTER_NODEV | awk '{ print $1 }')
for PARTITION in $PARTITIONS; do
  find "${PARTITION}" -xdev -type f -perm -002 -exec chmod o-w {} \; 2>/dev/null
done

# Ensure /tmp is also fixed whem tmpfs is used.
if grep "^tmpfs /tmp" /proc/mounts; then
  find /tmp -xdev -type f -perm -002 -exec chmod o-w {} \; 2>/dev/null
fi
Group   Restrict Dynamic Mounting and Unmounting of Filesystems   Group contains 1 rule
[ref]   Linux includes a number of facilities for the automated addition and removal of filesystems on a running system. These facilities may be necessary in many environments, but this capability also carries some risk -- whether direct risk from allowing users to introduce arbitrary filesystems, or risk that software flaws in the automated mount facility itself could allow an attacker to compromise the system.

This command can be used to list the types of filesystems that are available to the currently executing kernel:
$ find /lib/modules/`uname -r`/kernel/fs -type f -name '*.ko'
If these filesystems are not required then they can be explicitly disabled in a configuratio file in /etc/modprobe.d.

Rule   Disable the Automounter   [ref]

The autofs daemon mounts and unmounts filesystems, such as user home directories shared via NFS, on demand. In addition, autofs can be used to handle removable media, and the default configuration provides the cdrom device as /misc/cd. However, this method of providing access to removable media is not common, so autofs can almost always be disabled if NFS is not in use. Even if NFS is required, it may be possible to configure filesystem mounts statically by editing /etc/fstab rather than relying on the automounter.

The autofs service can be disabled with the following command:
$ sudo systemctl mask --now autofs.service
Rationale:
Disabling the automounter permits the administrator to statically control filesystem mounting through /etc/fstab.

Additionally, automatically mounting filesystems permits easy introduction of unknown devices, thereby facilitating malicious activity.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_service_autofs_disabled
Identifiers:

CCE-27498-5

References:
cis-csc1, 12, 15, 16, 5
cobit5APO13.01, DSS01.04, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.4.6
disaCCI-000366, CCI-000778, CCI-001958
hipaa164.308(a)(3)(i), 164.308(a)(3)(ii)(A), 164.310(d)(1), 164.310(d)(2), 164.312(a)(1), 164.312(a)(2)(iv), 164.312(b)
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.2, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.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.6
iso27001-2013A.11.2.6, A.13.1.1, A.13.2.1, A.18.1.4, A.6.2.1, A.6.2.2, A.7.1.1, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-7(a), CM-7(b), CM-6(a), MP-7
nist-csfPR.AC-1, PR.AC-3, PR.AC-6, PR.AC-7
os-srgSRG-OS-000114-GPOS-00059, SRG-OS-000378-GPOS-00163, SRG-OS-000480-GPOS-00227
stigidRHEL-07-020110
cis2.2.1
stigrefSV-204451r853893_rule


[customizations.services]
masked = ["autofs"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27498-5
  - DISA-STIG-RHEL-07-020110
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_autofs_disabled

- name: Disable the Automounter - Collect systemd Services Present in the System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ( ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman",
    "container"] and "autofs" in ansible_facts.packages )
  tags:
  - CCE-27498-5
  - DISA-STIG-RHEL-07-020110
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_autofs_disabled

- name: Disable the Automounter - Ensure autofs.service is Masked
  ansible.builtin.systemd:
    name: autofs.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ( ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
    and "autofs" in ansible_facts.packages )
  - service_exists.stdout_lines is search("autofs.service", multiline=True)
  tags:
  - CCE-27498-5
  - DISA-STIG-RHEL-07-020110
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_autofs_disabled

- name: Unit Socket Exists - autofs.socket
  ansible.builtin.command: systemctl -q list-unit-files autofs.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ( ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman",
    "container"] and "autofs" in ansible_facts.packages )
  tags:
  - CCE-27498-5
  - DISA-STIG-RHEL-07-020110
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_autofs_disabled

- name: Disable the Automounter - Disable Socket autofs
  ansible.builtin.systemd:
    name: autofs.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ( ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
    and "autofs" in ansible_facts.packages )
  - socket_file_exists.stdout_lines is search("autofs.socket", multiline=True)
  tags:
  - CCE-27498-5
  - DISA-STIG-RHEL-07-020110
  - NIST-800-171-3.4.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-MP-7
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_autofs_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: autofs.service
        enabled: false
        mask: true
      - name: autofs.socket
        enabled: false
        mask: true

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

class disable_autofs {
  service {'autofs':
    enable => false,
    ensure => 'stopped',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
# Remediation is applicable only in certain platforms
if ( [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q autofs ); then

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'autofs.service'
"$SYSTEMCTL_EXEC" disable 'autofs.service'
"$SYSTEMCTL_EXEC" mask 'autofs.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files autofs.socket; then
    "$SYSTEMCTL_EXEC" stop 'autofs.socket'
    "$SYSTEMCTL_EXEC" mask 'autofs.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 'autofs.service' || true

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Restrict Partition Mount Options   Group contains 2 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:

CCE-80152-2

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
disaCCI-001764
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
stigidRHEL-07-021024
cis1.1.2.2.2
stigrefSV-204486r853900_rule

Complexity:low
Disruption:high
Reboot:false
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:
  - CCE-80152-2
  - DISA-STIG-RHEL-07-021024
  - 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:
  - CCE-80152-2
  - DISA-STIG-RHEL-07-021024
  - 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 is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length == 0)
  tags:
  - CCE-80152-2
  - DISA-STIG-RHEL-07-021024
  - 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:
  - CCE-80152-2
  - DISA-STIG-RHEL-07-021024
  - 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"]
  - mount_info is defined
  - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
    length == 0)
  tags:
  - CCE-80152-2
  - DISA-STIG-RHEL-07-021024
  - 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

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

function perform_remediation {
    


    mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

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


    if mkdir -p "/dev/shm"; then
        if mountpoint -q "/dev/shm"; then
            mount -o remount --target "/dev/shm"
        fi
    fi
}

perform_remediation

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

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:

CCE-80154-8

References:
cis-csc11, 13, 14, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS05.02, DSS05.05, DSS05.06, DSS06.06
disaCCI-001764
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 7.6
iso27001-2013A.11.2.9, A.12.1.2, A.12.5.1, A.12.6.2, A.14.2.2, A.14.2.3, A.14.2.4, A.8.2.1, A.8.2.2, A.8.2.3, A.8.3.1, A.8.3.3, A.9.1.2
nerc-cipCIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.3, CIP-007-3 R2.1, CIP-007-3 R2.2, CIP-007-3 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.1, CIP-007-3 R5.1.2
nistCM-7(a), CM-7(b), CM-6(a), AC-6, AC-6(1), MP-7
nist-csfPR.IP-1, PR.PT-2, PR.PT-3
os-srgSRG-OS-000368-GPOS-00154
stigidRHEL-07-021024
cis1.1.2.2.3
stigrefSV-204486r853900_rule

Complexity:low
Disruption:high
Reboot:false
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:
  - CCE-80154-8
  - DISA-STIG-RHEL-07-021024
  - 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:
  - CCE-80154-8
  - DISA-STIG-RHEL-07-021024
  - 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 is defined and device_name.stdout_lines is defined
  - (device_name.stdout | length == 0)
  tags:
  - CCE-80154-8
  - DISA-STIG-RHEL-07-021024
  - 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:
  - CCE-80154-8
  - DISA-STIG-RHEL-07-021024
  - 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"]
  - mount_info is defined
  - (device_name.stdout is defined and (device_name.stdout | length > 0)) or ("" |
    length == 0)
  tags:
  - CCE-80154-8
  - DISA-STIG-RHEL-07-021024
  - 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

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

function perform_remediation {
    


    mount_point_match_regexp="$(printf "^[[:space:]]*[^#].*[[:space:]]%s[[:space:]]" /dev/shm)"

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


    if mkdir -p "/dev/shm"; then
        if mountpoint -q "/dev/shm"; then
            mount -o remount --target "/dev/shm"
        fi
    fi
}

perform_remediation

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Services   Group contains 2 groups and 6 rules
[ref]   The best protection against vulnerable software is running less software. This section describes how to review the software which Red Hat Enterprise Linux 7 installs on a system and disable software which is not needed. It then enumerates the software packages installed on a default Red Hat Enterprise Linux 7 system and provides guidance about which ones can be safely disabled.

Red Hat Enterprise Linux 7 provides a convenient minimal install option that essentially installs the bare necessities for a functional system. When building Red Hat Enterprise Linux 7 systems, it is highly recommended to select the minimal packages and then build up the system from there.
Group   Base Services   Group contains 5 rules
[ref]   This section addresses the base services that are installed on a Red Hat Enterprise Linux 7 default installation which are not covered in other sections. Some of these services listen on the network and should be treated with particular discretion. Other services are local system utilities that may or may not be extraneous. In general, system services should be disabled if not required.

Rule   Disable Automatic Bug Reporting Tool (abrtd)   [ref]

The Automatic Bug Reporting Tool (abrtd) daemon collects and reports crash data when an application crash is detected. Using a variety of plugins, abrtd can email crash reports to system administrators, log crash reports to files, or forward crash reports to a centralized issue tracking system such as RHTSupport. The abrtd service can be disabled with the following command:
$ sudo systemctl mask --now abrtd.service
Rationale:
Mishandling crash data could expose sensitive information about vulnerabilities in software executing on the system, as well as sensitive information from within a process's address space or registers.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_service_abrtd_disabled
Identifiers:

CCE-82027-4

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


[customizations.services]
masked = ["abrtd"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable Automatic Bug Reporting Tool (abrtd) - Collect systemd Services Present
    in the System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-82027-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_abrtd_disabled

- name: Disable Automatic Bug Reporting Tool (abrtd) - Ensure abrtd.service is Masked
  ansible.builtin.systemd:
    name: abrtd.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("abrtd.service", multiline=True)
  tags:
  - CCE-82027-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_abrtd_disabled

- name: Unit Socket Exists - abrtd.socket
  ansible.builtin.command: systemctl -q list-unit-files abrtd.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-82027-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_abrtd_disabled

- name: Disable Automatic Bug Reporting Tool (abrtd) - Disable Socket abrtd
  ansible.builtin.systemd:
    name: abrtd.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("abrtd.socket", multiline=True)
  tags:
  - CCE-82027-4
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - disable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - service_abrtd_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: abrtd.service
        enabled: false
        mask: true
      - name: abrtd.socket
        enabled: false
        mask: true

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

class disable_abrtd {
  service {'abrtd':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'abrtd.service'
"$SYSTEMCTL_EXEC" disable 'abrtd.service'
"$SYSTEMCTL_EXEC" mask 'abrtd.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files abrtd.socket; then
    "$SYSTEMCTL_EXEC" stop 'abrtd.socket'
    "$SYSTEMCTL_EXEC" mask 'abrtd.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 'abrtd.service' || true

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

Rule   Disable ntpdate Service (ntpdate)   [ref]

The ntpdate service sets the local hardware clock by polling NTP servers when the system boots. It synchronizes to the NTP servers listed in /etc/ntp/step-tickers or /etc/ntp.conf and then sets the local hardware clock to the newly synchronized system time. The ntpdate service can be disabled with the following command:
$ sudo systemctl mask --now ntpdate.service
Rationale:
The ntpdate service may only be suitable for systems which are rebooted frequently enough that clock drift does not cause problems between reboots. In any event, the functionality of the ntpdate service is now available in the ntpd program and should be considered deprecated.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_service_ntpdate_disabled
Identifiers:

CCE-80262-9

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
disaCCI-000382
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4


[customizations.services]
masked = ["ntpdate"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable ntpdate Service (ntpdate) - Collect systemd Services Present in the
    System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80262-9
  - 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
  - service_ntpdate_disabled

- name: Disable ntpdate Service (ntpdate) - Ensure ntpdate.service is Masked
  ansible.builtin.systemd:
    name: ntpdate.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("ntpdate.service", multiline=True)
  tags:
  - CCE-80262-9
  - 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
  - service_ntpdate_disabled

- name: Unit Socket Exists - ntpdate.socket
  ansible.builtin.command: systemctl -q list-unit-files ntpdate.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80262-9
  - 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
  - service_ntpdate_disabled

- name: Disable ntpdate Service (ntpdate) - Disable Socket ntpdate
  ansible.builtin.systemd:
    name: ntpdate.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("ntpdate.socket", multiline=True)
  tags:
  - CCE-80262-9
  - 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
  - service_ntpdate_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: ntpdate.service
        enabled: false
        mask: true
      - name: ntpdate.socket
        enabled: false
        mask: true

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

class disable_ntpdate {
  service {'ntpdate':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'ntpdate.service'
"$SYSTEMCTL_EXEC" disable 'ntpdate.service'
"$SYSTEMCTL_EXEC" mask 'ntpdate.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files ntpdate.socket; then
    "$SYSTEMCTL_EXEC" stop 'ntpdate.socket'
    "$SYSTEMCTL_EXEC" mask 'ntpdate.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 'ntpdate.service' || true

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

Rule   Disable Odd Job Daemon (oddjobd)   [ref]

The oddjobd service exists to provide an interface and access control mechanism through which specified privileged tasks can run tasks for unprivileged client applications. Communication with oddjobd through the system message bus. The oddjobd service can be disabled with the following command:
$ sudo systemctl mask --now oddjobd.service
Rationale:
The oddjobd service may provide necessary functionality in some environments, and can be disabled if it is not needed. Execution of tasks by privileged programs, on behalf of unprivileged ones, has traditionally been a source of privilege escalation security issues.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_service_oddjobd_disabled
Identifiers:

CCE-80263-7

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


[customizations.services]
masked = ["oddjobd"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable Odd Job Daemon (oddjobd) - Collect systemd Services Present in the
    System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80263-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_oddjobd_disabled

- name: Disable Odd Job Daemon (oddjobd) - Ensure oddjobd.service is Masked
  ansible.builtin.systemd:
    name: oddjobd.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("oddjobd.service", multiline=True)
  tags:
  - CCE-80263-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_oddjobd_disabled

- name: Unit Socket Exists - oddjobd.socket
  ansible.builtin.command: systemctl -q list-unit-files oddjobd.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80263-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_oddjobd_disabled

- name: Disable Odd Job Daemon (oddjobd) - Disable Socket oddjobd
  ansible.builtin.systemd:
    name: oddjobd.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("oddjobd.socket", multiline=True)
  tags:
  - CCE-80263-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_oddjobd_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: oddjobd.service
        enabled: false
        mask: true
      - name: oddjobd.socket
        enabled: false
        mask: true

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

class disable_oddjobd {
  service {'oddjobd':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'oddjobd.service'
"$SYSTEMCTL_EXEC" disable 'oddjobd.service'
"$SYSTEMCTL_EXEC" mask 'oddjobd.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files oddjobd.socket; then
    "$SYSTEMCTL_EXEC" stop 'oddjobd.socket'
    "$SYSTEMCTL_EXEC" mask 'oddjobd.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 'oddjobd.service' || true

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

Rule   Disable Apache Qpid (qpidd)   [ref]

The qpidd service provides high speed, secure, guaranteed delivery services. It is an implementation of the Advanced Message Queuing Protocol. By default the qpidd service will bind to port 5672 and listen for connection attempts. The qpidd service can be disabled with the following command:
$ sudo systemctl mask --now qpidd.service
Rationale:
The qpidd service is automatically installed when the base package selection is selected during installation. The qpidd service listens for network connections, which increases the attack surface of the system. If the system is not intended to receive AMQP traffic, then the qpidd service is not needed and should be disabled or removed.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_service_qpidd_disabled
Identifiers:

CCE-80266-0

References:
cis-csc11, 12, 14, 15, 3, 8, 9
cobit5APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS05.02, DSS05.03, DSS05.05, DSS06.06
disaCCI-000382
isa-62443-20094.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.11.2.6, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.2.1, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.2.1, A.6.2.2, A.9.1.2
nistCM-7(a), CM-7(b), CM-6(a)
nist-csfPR.AC-3, PR.IP-1, PR.PT-3, PR.PT-4


[customizations.services]
masked = ["qpidd"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable Apache Qpid (qpidd) - Collect systemd Services Present in the System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80266-0
  - 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
  - service_qpidd_disabled

- name: Disable Apache Qpid (qpidd) - Ensure qpidd.service is Masked
  ansible.builtin.systemd:
    name: qpidd.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("qpidd.service", multiline=True)
  tags:
  - CCE-80266-0
  - 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
  - service_qpidd_disabled

- name: Unit Socket Exists - qpidd.socket
  ansible.builtin.command: systemctl -q list-unit-files qpidd.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80266-0
  - 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
  - service_qpidd_disabled

- name: Disable Apache Qpid (qpidd) - Disable Socket qpidd
  ansible.builtin.systemd:
    name: qpidd.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("qpidd.socket", multiline=True)
  tags:
  - CCE-80266-0
  - 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
  - service_qpidd_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: qpidd.service
        enabled: false
        mask: true
      - name: qpidd.socket
        enabled: false
        mask: true

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

class disable_qpidd {
  service {'qpidd':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'qpidd.service'
"$SYSTEMCTL_EXEC" disable 'qpidd.service'
"$SYSTEMCTL_EXEC" mask 'qpidd.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files qpidd.socket; then
    "$SYSTEMCTL_EXEC" stop 'qpidd.socket'
    "$SYSTEMCTL_EXEC" mask 'qpidd.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 'qpidd.service' || true

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

Rule   Disable Network Router Discovery Daemon (rdisc)   [ref]

The rdisc service implements the client side of the ICMP Internet Router Discovery Protocol (IRDP), which allows discovery of routers on the local subnet. If a router is discovered then the local routing table is updated with a corresponding default route. By default this daemon is disabled. The rdisc service can be disabled with the following command:
$ sudo systemctl mask --now rdisc.service
Rationale:
General-purpose systems typically have their network and routing information configured statically by a system administrator. Workstations or some special-purpose systems often use DHCP (instead of IRDP) to retrieve dynamic network configuration information.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_service_rdisc_disabled
Identifiers:

CCE-80268-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 18, 3, 4, 6, 8, 9
cobit5APO01.06, APO13.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.04, DSS01.05, DSS03.01, DSS05.02, DSS05.03, DSS05.04, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-000382
isa-62443-20094.2.3.4, 4.3.3.4, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4, 4.3.4.3.2, 4.3.4.3.3, 4.4.3.3
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 7.1, SR 7.6
iso27001-2013A.10.1.1, A.11.1.4, A.11.1.5, A.11.2.1, A.11.2.6, A.12.1.1, A.12.1.2, A.12.5.1, A.12.6.2, A.13.1.1, A.13.1.2, A.13.1.3, A.13.2.1, A.13.2.2, A.13.2.3, A.13.2.4, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.6.1.2, A.6.2.1, A.6.2.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
nistAC-4, CM-7(a), CM-7(b), CM-6(a)
nist-csfDE.AE-1, ID.AM-3, PR.AC-3, PR.AC-5, PR.DS-5, PR.IP-1, PR.PT-3, PR.PT-4


[customizations.services]
masked = ["rdisc"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable Network Router Discovery Daemon (rdisc) - Collect systemd Services
    Present in the System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80268-6
  - NIST-800-53-AC-4
  - 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_rdisc_disabled

- name: Disable Network Router Discovery Daemon (rdisc) - Ensure rdisc.service is
    Masked
  ansible.builtin.systemd:
    name: rdisc.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("rdisc.service", multiline=True)
  tags:
  - CCE-80268-6
  - NIST-800-53-AC-4
  - 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_rdisc_disabled

- name: Unit Socket Exists - rdisc.socket
  ansible.builtin.command: systemctl -q list-unit-files rdisc.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80268-6
  - NIST-800-53-AC-4
  - 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_rdisc_disabled

- name: Disable Network Router Discovery Daemon (rdisc) - Disable Socket rdisc
  ansible.builtin.systemd:
    name: rdisc.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("rdisc.socket", multiline=True)
  tags:
  - CCE-80268-6
  - NIST-800-53-AC-4
  - 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_rdisc_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: rdisc.service
        enabled: false
        mask: true
      - name: rdisc.socket
        enabled: false
        mask: true

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

class disable_rdisc {
  service {'rdisc':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'rdisc.service'
"$SYSTEMCTL_EXEC" disable 'rdisc.service'
"$SYSTEMCTL_EXEC" mask 'rdisc.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files rdisc.socket; then
    "$SYSTEMCTL_EXEC" stop 'rdisc.socket'
    "$SYSTEMCTL_EXEC" mask 'rdisc.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 'rdisc.service' || true

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Cron and At Daemons   Group contains 1 rule
[ref]   The cron and at services are used to allow commands to be executed at a later time. The cron service is required by almost all systems to perform necessary maintenance tasks, while at may or may not be required on a given system. Both daemons should be configured defensively.

Rule   Disable At Service (atd)   [ref]

The at and batch commands can be used to schedule tasks that are meant to be executed only once. This allows delayed execution in a manner similar to cron, except that it is not recurring. The daemon atd keeps track of tasks scheduled via at and batch, and executes them at the specified time. The atd service can be disabled with the following command:
$ sudo systemctl mask --now atd.service
Rationale:
The atd service could be used by an unsophisticated insider to carry out activities outside of a normal login session, which could complicate accountability. Furthermore, the need to schedule tasks with at or batch is not common.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_service_atd_disabled
Identifiers:

CCE-80345-2

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


[customizations.services]
masked = ["atd"]

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Disable At Service (atd) - Collect systemd Services Present in the System
  ansible.builtin.command: systemctl -q list-unit-files --type service
  register: service_exists
  changed_when: false
  failed_when: service_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80345-2
  - 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_atd_disabled

- name: Disable At Service (atd) - Ensure atd.service is Masked
  ansible.builtin.systemd:
    name: atd.service
    state: stopped
    enabled: false
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - service_exists.stdout_lines is search("atd.service", multiline=True)
  tags:
  - CCE-80345-2
  - 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_atd_disabled

- name: Unit Socket Exists - atd.socket
  ansible.builtin.command: systemctl -q list-unit-files atd.socket
  register: socket_file_exists
  changed_when: false
  failed_when: socket_file_exists.rc not in [0, 1]
  check_mode: false
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-80345-2
  - 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_atd_disabled

- name: Disable At Service (atd) - Disable Socket atd
  ansible.builtin.systemd:
    name: atd.socket
    enabled: false
    state: stopped
    masked: true
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - socket_file_exists.stdout_lines is search("atd.socket", multiline=True)
  tags:
  - CCE-80345-2
  - 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_atd_disabled

Complexity:low
Disruption:medium
Reboot:true
Strategy:disable
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
spec:
  config:
    ignition:
      version: 3.1.0
    systemd:
      units:
      - name: atd.service
        enabled: false
        mask: true
      - name: atd.socket
        enabled: false
        mask: true

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

class disable_atd {
  service {'atd':
    enable => false,
    ensure => 'stopped',
  }
}

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

SYSTEMCTL_EXEC='/usr/bin/systemctl'
"$SYSTEMCTL_EXEC" stop 'atd.service'
"$SYSTEMCTL_EXEC" disable 'atd.service'
"$SYSTEMCTL_EXEC" mask 'atd.service'
# Disable socket activation if we have a unit file for it
if "$SYSTEMCTL_EXEC" -q list-unit-files atd.socket; then
    "$SYSTEMCTL_EXEC" stop 'atd.socket'
    "$SYSTEMCTL_EXEC" mask 'atd.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 'atd.service' || true

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   System Accounting with auditd   Group contains 7 groups and 28 rules
[ref]   The audit service provides substantial capabilities for recording system activities. By default, the service audits about SELinux AVC denials and certain types of security-relevant events such as system logins, account modifications, and authentication events performed by programs such as sudo. Under its default configuration, auditd has modest disk space requirements, and should not noticeably impact system performance.

NOTE: The Linux Audit daemon auditd can be configured to use the augenrules program to read audit rules files (*.rules) located in /etc/audit/rules.d location and compile them to create the resulting form of the /etc/audit/audit.rules configuration file during the daemon startup (default configuration). Alternatively, the auditd daemon can use the auditctl utility to read audit rules from the /etc/audit/audit.rules configuration file during daemon startup, and load them into the kernel. The expected behavior is configured via the appropriate ExecStartPost directive setting in the /usr/lib/systemd/system/auditd.service configuration file. To instruct the auditd daemon to use the augenrules program to read audit rules (default configuration), use the following setting:
ExecStartPost=-/sbin/augenrules --load
in the /usr/lib/systemd/system/auditd.service configuration file. In order to instruct the auditd daemon to use the auditctl utility to read audit rules, use the following setting:
ExecStartPost=-/sbin/auditctl -R /etc/audit/audit.rules
in the /usr/lib/systemd/system/auditd.service configuration file. Refer to [Service] section of the /usr/lib/systemd/system/auditd.service configuration file for further details.

Government networks often have substantial auditing requirements and auditd can be configured to meet these requirements. Examining some example audit records demonstrates how the Linux audit system satisfies common requirements. The following example from Red Hat Enterprise Linux 7 Documentation available at https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html-single/selinux_users_and_administrators_guide/index#sect-Security-Enhanced_Linux-Fixing_Problems-Raw_Audit_Messages shows the substantial amount of information captured in a two typical "raw" audit messages, followed by a breakdown of the most important fields. In this example the message is SELinux-related and reports an AVC denial (and the associated system call) that occurred when the Apache HTTP Server attempted to access the /var/www/html/file1 file (labeled with the samba_share_t type):
type=AVC msg=audit(1226874073.147:96): avc:  denied  { getattr } for pid=2465 comm="httpd"
path="/var/www/html/file1" dev=dm-0 ino=284133 scontext=unconfined_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:samba_share_t:s0 tclass=file

type=SYSCALL msg=audit(1226874073.147:96): arch=40000003 syscall=196 success=no exit=-13
a0=b98df198 a1=bfec85dc a2=54dff4 a3=2008171 items=0 ppid=2463 pid=2465 auid=502 uid=48
gid=48 euid=48 suid=48 fsuid=48 egid=48 sgid=48 fsgid=48 tty=(none) ses=6 comm="httpd"
exe="/usr/sbin/httpd" subj=unconfined_u:system_r:httpd_t:s0 key=(null)
  • msg=audit(1226874073.147:96)
    • The number in parentheses is the unformatted time stamp (Epoch time) for the event, which can be converted to standard time by using the date command.
  • { getattr }
    • The item in braces indicates the permission that was denied. getattr indicates the source process was trying to read the target file's status information. This occurs before reading files. This action is denied due to the file being accessed having the wrong label. Commonly seen permissions include getattr, read, and write.
  • comm="httpd"
    • The executable that launched the process. The full path of the executable is found in the exe= section of the system call (SYSCALL) message, which in this case, is exe="/usr/sbin/httpd".
  • path="/var/www/html/file1"
    • The path to the object (target) the process attempted to access.
  • scontext="unconfined_u:system_r:httpd_t:s0"
    • The SELinux context of the process that attempted the denied action. In this case, it is the SELinux context of the Apache HTTP Server, which is running in the httpd_t domain.
  • tcontext="unconfined_u:object_r:samba_share_t:s0"
    • The SELinux context of the object (target) the process attempted to access. In this case, it is the SELinux context of file1. Note: the samba_share_t type is not accessible to processes running in the httpd_t domain.
  • From the system call (SYSCALL) message, two items are of interest:
    • success=no: indicates whether the denial (AVC) was enforced or not. success=no indicates the system call was not successful (SELinux denied access). success=yes indicates the system call was successful - this can be seen for permissive domains or unconfined domains, such as initrc_t and kernel_t.
    • exe="/usr/sbin/httpd": the full path to the executable that launched the process, which in this case, is exe="/usr/sbin/httpd".
Group   Configure auditd Rules for Comprehensive Auditing   Group contains 6 groups and 27 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 13 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:

CCE-27339-1

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030410
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204521r809772_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27339-1
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - 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: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27339-1
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chmod for 32bit 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-27339-1
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chmod for 64bit 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-27339-1
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; 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

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:

CCE-27364-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030370
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204517r809570_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27364-9
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - 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: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27364-9
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chown for 32bit 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-27364-9
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for chown for 64bit 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-27364-9
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_chown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; 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

Rule   Record Events that Modify the System's Discretionary Access Controls - fchmod   [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 fchmod -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 fchmod -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 fchmod -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 fchmod -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_fchmod
Identifiers:

CCE-27393-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030410
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204521r809772_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27393-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchmod tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27393-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmod for 32bit platform
  block:

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

  - name: Check existence of fchmod 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:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27393-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmod for 64bit platform
  block:

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

  - name: Check existence of fchmod 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:
      - fchmod
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmod 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27393-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmod
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fchmod"
	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

Rule   Record Events that Modify the System's Discretionary Access Controls - fchmodat   [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 fchmodat -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 fchmodat -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 fchmodat -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 fchmodat -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_fchmodat
Identifiers:

CCE-27388-8

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030410
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204521r809772_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27388-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchmodat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27388-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat for 32bit platform
  block:

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

  - name: Check existence of fchmodat 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:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmodat 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27388-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchmodat for 64bit platform
  block:

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

  - name: Check existence of fchmodat 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:
      - fchmodat
      syscall_grouping:
      - chmod
      - fchmod
      - fchmodat

  - name: Check existence of fchmodat 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27388-8
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030410
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchmodat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fchmodat"
	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

Rule   Record Events that Modify the System's Discretionary Access Controls - fchown   [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 fchown -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 fchown -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 fchown -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 fchown -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_fchown
Identifiers:

CCE-27356-5

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030370
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204517r809570_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27356-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchown tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27356-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchown for 32bit platform
  block:

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

  - name: Check existence of fchown 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:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27356-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchown for 64bit platform
  block:

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

  - name: Check existence of fchown 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:
      - fchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchown 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27356-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fchown"
	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

Rule   Record Events that Modify the System's Discretionary Access Controls - fchownat   [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 fchownat -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 fchownat -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 fchownat -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 fchownat -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_fchownat
Identifiers:

CCE-27387-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030370
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204517r809570_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27387-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fchownat tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27387-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchownat for 32bit platform
  block:

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

  - name: Check existence of fchownat 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:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27387-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fchownat for 64bit platform
  block:

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

  - name: Check existence of fchownat 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:
      - fchownat
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of fchownat 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27387-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fchownat
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fchownat"
	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

Rule   Record Events that Modify the System's Discretionary Access Controls - fremovexattr   [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 fremovexattr -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 fremovexattr -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 fremovexattr -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 fremovexattr -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_fremovexattr
Identifiers:

CCE-27353-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255
stigidRHEL-07-030440
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204524r809775_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27353-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fremovexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27353-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fremovexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr 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:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27353-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fremovexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr 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:
      - fremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fremovexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27353-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fremovexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# 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

Rule   Record Events that Modify the System's Discretionary Access Controls - fsetxattr   [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 fsetxattr -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 fsetxattr -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 fsetxattr -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 fsetxattr -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_fsetxattr
Identifiers:

CCE-27389-6

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
stigidRHEL-07-030440
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204524r809775_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27389-6
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit fsetxattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27389-6
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fsetxattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr 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:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27389-6
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for fsetxattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr 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:
      - fsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of fsetxattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27389-6
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_fsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="fsetxattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# 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

Rule   Record Events that Modify the System's Discretionary Access Controls - lchown   [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 lchown -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 lchown -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 lchown -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 lchown -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_lchown
Identifiers:

CCE-27083-5

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-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
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000499-CTR-001255
stigidRHEL-07-030370
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204517r809570_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27083-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lchown tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27083-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lchown for 32bit platform
  block:

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

  - name: Check existence of lchown 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:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  tags:
  - CCE-27083-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lchown for 64bit platform
  block:

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

  - name: Check existence of lchown 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:
      - lchown
      syscall_grouping:
      - chown
      - fchown
      - fchownat
      - lchown

  - name: Check existence of lchown 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - not ( ansible_architecture == "aarch64" )
  - audit_arch == "b64"
  tags:
  - CCE-27083-5
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030370
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lchown
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && rpm --quiet -q audit && { ! ( grep -q aarch64 /proc/sys/kernel/osrelease ); }; 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="lchown"
	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

Rule   Record Events that Modify the System's Discretionary Access Controls - lremovexattr   [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 lremovexattr -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 lremovexattr -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 lremovexattr -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 lremovexattr -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_lremovexattr
Identifiers:

CCE-27410-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
stigidRHEL-07-030440
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204524r809775_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27410-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lremovexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27410-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lremovexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr 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:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27410-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lremovexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr 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:
      - lremovexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lremovexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27410-0
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lremovexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="lremovexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# 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

Rule   Record Events that Modify the System's Discretionary Access Controls - lsetxattr   [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 lsetxattr -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 lsetxattr -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 lsetxattr -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 lsetxattr -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_lsetxattr
Identifiers:

CCE-27280-7

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000126, CCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000466-GPOS-00210, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
stigidRHEL-07-030440
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204524r809775_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27280-7
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit lsetxattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27280-7
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lsetxattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr 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:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27280-7
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for lsetxattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr 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:
      - lsetxattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of lsetxattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27280-7
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_lsetxattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="lsetxattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# 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

Rule   Record Events that Modify the System's Discretionary Access Controls - removexattr   [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 removexattr -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 removexattr -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 removexattr -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 removexattr -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_removexattr
Identifiers:

CCE-27367-2

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 19, 2, 3, 4, 5, 6, 7, 8, 9
cjis5.4.1.1
cobit5APO10.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
cui3.1.7
disaCCI-000130, CCI-000135, CCI-000169, CCI-000172, CCI-002884
hipaa164.308(a)(1)(ii)(D), 164.308(a)(3)(ii)(A), 164.308(a)(5)(ii)(C), 164.312(a)(2)(i), 164.312(b), 164.312(d), 164.312(e)
isa-62443-20094.2.3.10, 4.3.2.6.7, 4.3.3.3.9, 4.3.3.5.8, 4.3.3.6.6, 4.3.4.4.7, 4.3.4.5.6, 4.3.4.5.7, 4.3.4.5.8, 4.4.2.1, 4.4.2.2, 4.4.2.4
isa-62443-2013SR 1.13, SR 2.10, SR 2.11, SR 2.12, SR 2.6, SR 2.8, SR 2.9, SR 3.1, SR 3.5, SR 3.8, SR 4.1, SR 4.3, SR 5.1, SR 5.2, SR 5.3, SR 6.1, SR 6.2, SR 7.1, SR 7.6
iso27001-2013A.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
nistAU-2(d), AU-12(c), CM-6(a)
nist-csfDE.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
osppFAU_GEN.1.1.c
pcidssReq-10.5.5
os-srgSRG-OS-000037-GPOS-00015, SRG-OS-000042-GPOS-00020, SRG-OS-000062-GPOS-00031, SRG-OS-000392-GPOS-00172, SRG-OS-000458-GPOS-00203, SRG-OS-000462-GPOS-00206, SRG-OS-000463-GPOS-00207, SRG-OS-000468-GPOS-00212, SRG-OS-000471-GPOS-00215, SRG-OS-000474-GPOS-00219, SRG-OS-000466-GPOS-00210, SRG-OS-000064-GPOS-00033
app-srg-ctrSRG-APP-000091-CTR-000160, SRG-APP-000492-CTR-001220, SRG-APP-000493-CTR-001225, SRG-APP-000494-CTR-001230, SRG-APP-000500-CTR-001260, SRG-APP-000507-CTR-001295, SRG-APP-000495-CTR-001235, SRG-APP-000496-CTR-001240, SRG-APP-000497-CTR-001245, SRG-APP-000498-CTR-001250, SRG-APP-000499-CTR-001255, SRG-APP-000501-CTR-001265, SRG-APP-000502-CTR-001270
stigidRHEL-07-030440
anssiR73
cis5.2.3.9
pcidss410.3.4
stigrefSV-204524r809775_rule

Complexity:low
Disruption:low
Reboot:true
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-27367-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Set architecture for audit removexattr tasks
  set_fact:
    audit_arch: b64
  when:
  - '"audit" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - ansible_architecture == "aarch64" or ansible_architecture == "ppc64" or ansible_architecture
    == "ppc64le" or ansible_architecture == "s390x" or ansible_architecture == "x86_64"
  tags:
  - CCE-27367-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for removexattr for 32bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr 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:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-27367-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

- name: Perform remediation of Audit rules for removexattr for 64bit platform
  block:

  - name: Declare list of syscalls
    set_fact:
      syscalls:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr 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:
      - removexattr
      syscall_grouping:
      - fremovexattr
      - lremovexattr
      - removexattr
      - fsetxattr
      - lsetxattr
      - setxattr

  - name: Check existence of removexattr 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'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - audit_arch == "b64"
  tags:
  - CCE-27367-2
  - CJIS-5.4.1.1
  - DISA-STIG-RHEL-07-030440
  - 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
  - PCI-DSSv4-10.3.4
  - audit_rules_dac_modification_removexattr
  - low_complexity
  - low_disruption
  - medium_severity
  - reboot_required
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && 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="removexattr"
	KEY="perm_mod"
	SYSCALL_GROUPING="fremovexattr lremovexattr removexattr fsetxattr lsetxattr setxattr"

	# 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}"