Guide to the Secure Configuration of SUSE Linux Enterprise 15

with profile CIS SUSE Linux Enterprise 15 Benchmark for Level 2 - Server
This profile defines a baseline that aligns to the "Level 2 - Server" configuration from the Center for Internet Security® SUSE Linux Enterprise 15 Benchmark™, v1.1.1, released 01-24-2022. This profile includes Center for Internet Security® SUSE Linux Enterprise 15 CIS Benchmarks™ content.
This guide presents a catalog of security-relevant configuration settings for SUSE Linux Enterprise 15. 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 TitleCIS SUSE Linux Enterprise 15 Benchmark for Level 2 - Server
Profile IDxccdf_org.ssgproject.content_profile_cis

CPE Platforms

  • cpe:/o:suse:linux_enterprise_desktop:15
  • cpe:/o:suse:linux_enterprise_server:15

Revision History

Current version: 0.1.75

  • draft (as of 2024-11-04)

Table of Contents

  1. System Settings
    1. Installing and Maintaining Software
    2. Account and Access Control
    3. AppArmor
    4. GRUB2 bootloader configuration
    5. Configure Syslog
    6. Network Configuration and Firewalls
    7. File Permissions and Masks
  2. Services
    1. Avahi Server
    2. Cron and At Daemons
    3. DHCP
    4. DNS Server
    5. FTP Server
    6. Web Server
    7. IMAP and POP3 Server
    8. LDAP
    9. Mail Server Software
    10. NFS and RPC
    11. Network Time Protocol
    12. Obsolete Services
    13. Print Support
    14. Proxy Server
    15. Samba(SMB) Microsoft Windows File Sharing Server
    16. SNMP Server
    17. SSH Server
    18. X Window System
  3. System Accounting with auditd
    1. Configure auditd Rules for Comprehensive Auditing
    2. Configure auditd Data Retention

Checklist

Group   Guide to the Secure Configuration of SUSE Linux Enterprise 15   Group contains 116 groups and 378 rules
Group   System Settings   Group contains 61 groups and 219 rules
[ref]   Contains rules that check correct system settings.
Group   Installing and Maintaining Software   Group contains 7 groups and 21 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 4 rules
[ref]   System and software integrity can be gained by installing antivirus, increasing system encryption strength with FIPS, verifying installed software, enabling SELinux, installing an Intrusion Prevention System, etc. However, installing or enabling integrity checking tools cannot prevent intrusions, but they can detect that an intrusion may have occurred. Requirements for integrity checking may be highly dependent on the environment in which the system will be used. Snapshot-based approaches such as AIDE may induce considerable overhead in the presence of frequent software updates.
Group   Software Integrity Checking   Group contains 1 group and 3 rules
[ref]   Both the AIDE (Advanced Intrusion Detection Environment) software and the RPM package management system provide mechanisms for verifying the integrity of installed software. AIDE uses snapshots of file metadata (such as hashes) and compares these to current system files in order to detect changes.

The RPM package management system can conduct integrity checks by comparing information in its metadata database with files installed on the system.
Group   Verify Integrity with AIDE   Group contains 3 rules
[ref]   AIDE conducts integrity checks by comparing information about files with previously-gathered information. Ideally, the AIDE database is created immediately after initial system configuration, and then again after any software update. AIDE is highly configurable, with further configuration information located in /usr/share/doc/aide-VERSION .

Rule   Install AIDE   [ref]

The aide package can be installed with the following command:
$ sudo zypper install aide
Rationale:
The AIDE package must be installed if it is to be available for integrity checking.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_aide_installed
Identifiers:

CCE-83289-9

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-002696, CCI-001744
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 4.1, SR 6.2, SR 7.6
ism1034, 1288, 1341, 1417
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
stigidSLES-15-010419
cis1.4.1
anssiR76, R79
pcidss411.5.2
stigrefSV-255922r958794_rule

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

class install_aide {
  package { 'aide':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83289-9
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_aide_installed

- name: Ensure aide is installed
  package:
    name: aide
    state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83289-9
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_aide_installed


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

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

zypper install -y "aide"

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

Rule   Build and Test AIDE Database   [ref]

Run the following command to generate a new database:
$ sudo /usr/bin/aide --init
By default, the database will be written to the file /var/lib/aide/aide.db.new. Storing the database, the configuration file /etc/aide.conf, and the binary /usr/bin/aide (or hashes of these files), in a secure location (such as on read-only media) provides additional assurance about their integrity. The newly-generated database can be installed as follows:
$ sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
To initiate a manual check, run the following command:
$ sudo /usr/bin/aide --check
If this check produces any unexpected output, investigate.
Rationale:
For AIDE to be effective, an initial database of "known-good" information about files must be captured and it should be able to be verified against the installed files.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_aide_build_database
Identifiers:

CCE-85787-0

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-002696, CCI-001744
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 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistCM-6(a)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000445-GPOS-00199
stigidSLES-15-010419
cis1.4.1
anssiR76, R79
pcidss411.5.2
stigrefSV-255922r958794_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85787-0
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Build and Test AIDE Database - Ensure Repositories Are Updated
  ansible.builtin.command: zypper -q --no-remote ref
  failed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85787-0
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

- name: Build and Test AIDE Database - Build and Test AIDE Database
  ansible.builtin.command: /usr/bin/aide --init
  changed_when: true
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85787-0
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

- name: Build and Test AIDE Database - Stage AIDE Database
  ansible.builtin.copy:
    src: /var/lib/aide/aide.db.new
    dest: /var/lib/aide/aide.db
    backup: true
    remote_src: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - (aide_database_stat.stat.exists is defined and aide_database_stat.stat.exists)
  tags:
  - CCE-85787-0
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010419
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_build_database
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

zypper -q --no-remote ref


zypper install -y "aide"

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

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

Rule   Configure Systemd Timer Execution of AIDE   [ref]

At a minimum, AIDE should be configured to run a weekly scan. To implement a systemd service and a timer unit to run the service periodically: For example, if a systemd timer is expected to be started every day at 5AM
OnCalendar=*-*-* 05:00:0
[Timer]
section in the timer unit and a Unit section starting the AIDE check service unit should be referred.
Rationale:
AIDE provides a means to check if unauthorized changes are made to the system. AIDE itself does not setup a periodic execution, so in order to detect unauthorized changes a systemd service to run the check and a systemd timer to take care of periodical execution of that systemd service should be defined.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_aide_periodic_checking_systemd_timer
Identifiers:

CCE-92516-4

References:
cis-csc1, 11, 12, 13, 14, 15, 16, 2, 3, 5, 7, 8, 9
cjis5.10.1.3
cobit5APO01.06, BAI01.06, BAI02.01, BAI03.05, BAI06.01, BAI10.01, BAI10.02, BAI10.03, BAI10.05, DSS01.03, DSS03.05, DSS04.07, DSS05.02, DSS05.03, DSS05.05, DSS05.07, DSS06.02, DSS06.06
disaCCI-001744, CCI-002699, CCI-002702
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 4.1, SR 6.2, SR 7.6
iso27001-2013A.11.2.4, A.12.1.2, A.12.2.1, A.12.4.1, A.12.5.1, A.12.6.2, A.14.1.2, A.14.1.3, A.14.2.2, A.14.2.3, A.14.2.4, A.14.2.7, A.15.2.1, A.8.2.3
nistSI-6(d)
nist-csfDE.CM-1, DE.CM-7, PR.DS-1, PR.DS-6, PR.DS-8, PR.IP-1, PR.IP-3
pcidssReq-11.5
os-srgSRG-OS-000363-GPOS-00150, SRG-OS-000446-GPOS-00200, SRG-OS-000447-GPOS-00201
stigidSLES-15-010570
cis1.4.2
anssiR76
pcidss411.5.2
stigrefSV-234864r958948_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92516-4
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010570
  - NIST-800-53-SI-6(d)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_checking_systemd_timer
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Systemd Timer Execution of AIDE - Define AIDE Periodic Check Service
  ansible.builtin.blockinfile:
    create: true
    dest: /etc/systemd/system/aidecheck.service
    owner: root
    group: root
    mode: '0644'
    block: |
      [Unit]
      Description=Aide Check
      [Service]
      Type=simple
      ExecStart=/usr/bin/aide --check
      [Install]
      WantedBy=multi-user.target
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"aide" in ansible_facts.packages'
  tags:
  - CCE-92516-4
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010570
  - NIST-800-53-SI-6(d)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_checking_systemd_timer
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Systemd Timer Execution of AIDE - Define AIDE Periodic Check Service
    Timer
  ansible.builtin.blockinfile:
    create: true
    dest: /etc/systemd/system/aidecheck.timer
    owner: root
    group: root
    mode: '0644'
    block: |
      [Unit]
      Description=Aide check every day at 5AM
      [Timer]
      OnCalendar=*-*-* 05:00:00
      Unit=aidecheck.service
      [Install]
      WantedBy=multi-user.target
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"aide" in ansible_facts.packages'
  tags:
  - CCE-92516-4
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010570
  - NIST-800-53-SI-6(d)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_checking_systemd_timer
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Systemd Timer Execution of AIDE - Ensure AIDE Service is Enabled
  ansible.builtin.systemd:
    name: aidecheck.service
    enabled: true
    daemon_reload: true
    masked: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"aide" in ansible_facts.packages'
  tags:
  - CCE-92516-4
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010570
  - NIST-800-53-SI-6(d)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_checking_systemd_timer
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure Systemd Timer Execution of AIDE - Ensure AIDE Service Timer is Enabled
  ansible.builtin.systemd:
    name: aidecheck.timer
    state: started
    enabled: true
    daemon_reload: true
    masked: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"aide" in ansible_facts.packages'
  tags:
  - CCE-92516-4
  - CJIS-5.10.1.3
  - DISA-STIG-SLES-15-010570
  - NIST-800-53-SI-6(d)
  - PCI-DSS-Req-11.5
  - PCI-DSSv4-11.5.2
  - aide_periodic_checking_systemd_timer
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel-default && { rpm --quiet -q aide; }; then

zypper install -y "aide"

# create unit file for periodic aide database check
cat > /etc/systemd/system/aidecheck.service <<EOF
[Unit]
Description=Aide Check
[Service]
Type=simple
ExecStart=/usr/bin/aide --check
[Install]
WantedBy=multi-user.target
EOF

# create unit file for the aide check timer
cat > /etc/systemd/system/aidecheck.timer <<EOF
[Unit]
Description=Aide check every day at 5AM
[Timer]
OnCalendar=*-*-* 05:00:00
Unit=aidecheck.service
[Install]
WantedBy=multi-user.target
EOF

#  setup service unit files attributes
chown root:root /etc/systemd/system/aidecheck.*
chmod 0644 /etc/systemd/system/aidecheck.*

# enable the aide related services
systemctl daemon-reload
systemctl enable aidecheck.service
systemctl --now enable aidecheck.timer

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Disk Partitioning   Group contains 7 rules
[ref]   To ensure separation and protection of data, there are top-level system directories which should be placed on their own physical partition or logical volume. The installer's default partitioning scheme creates separate logical volumes for /, /boot, and swap.
  • If starting with any of the default layouts, check the box to \"Review and modify partitioning.\" This allows for the easy creation of additional logical volumes inside the volume group already created, though it may require making /'s logical volume smaller to create space. In general, using logical volumes is preferable to using partitions because they can be more easily adjusted later.
  • If creating a custom layout, create the partitions mentioned in the previous paragraph (which the installer will require anyway), as well as separate ones described in the following sections.
If a system has already been installed, and the default partitioning scheme was used, it is possible but nontrivial to modify it to create separate logical volumes for the directories listed above. The Logical Volume Manager (LVM) makes this possible.

Rule   Ensure /dev/shm is configured   [ref]

The /dev/shm is a traditional shared memory concept. One program will create a memory portion, which other processes (if permitted) can access. If /dev/shm is not configured, tmpfs will be mounted to /dev/shm by systemd.
Warning:  This rule does not have a remedation. It is expected that this will be managed by systemd and will be a tmpfs partition.
Rationale:
Any user can upload and execute files inside the /dev/shm similar to the /tmp partition. Configuring /dev/shm allows an administrator to set the noexec option on the mount, making /dev/shm useless for an attacker to install executable code. It would also prevent an attacker from establishing a hardlink to a system setuid program and wait for it to be updated. Once the program was updated, the hardlink would be broken and the attacker would have his own copy of the program. If the program happened to have a security vulnerability, the attacker could continue to exploit the known flaw.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_dev_shm
Identifiers:

CCE-92477-9

References:
cis1.1.6

Rule   Ensure /home Located On Separate Partition   [ref]

If user home directories will be stored locally, create a separate partition for /home at installation time (or migrate it later using LVM). If /home will be mounted from another system such as an NFS server, then creating a separate partition is not necessary at installation time, and the mountpoint can instead be configured later.
Rationale:
Ensuring that /home is mounted on its own partition enables the setting of more restrictive mount options, and also helps ensure that users cannot trivially fill partitions used for log or audit data storage.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_home
Identifiers:

CCE-85639-3

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
disaCCI-000366
isa-62443-2013SR 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.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-040200
cis1.1.17
anssiR28
stigrefSV-235004r991589_rule

Rule   Ensure /tmp Located On Separate Partition   [ref]

The /tmp directory is a world-writable directory used for temporary file storage. Ensure it has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale:
The /tmp partition is used as temporary storage by many programs. Placing /tmp in its own partition enables the setting of more restrictive mount options, which can help protect programs which use it.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_tmp
Identifiers:

CCE-91179-2

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
disaCCI-000366
isa-62443-2013SR 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.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
cis1.1.2

Rule   Ensure /var Located On Separate Partition   [ref]

The /var directory is used by daemons and other system services to store frequently-changing data. Ensure that /var has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale:
Ensuring that /var is mounted on its own partition enables the setting of more restrictive mount options. This helps protect system services such as daemons or other programs which use it. It is not uncommon for the /var directory to contain world-writable directories installed by other software packages.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_var
Identifiers:

CCE-85640-1

References:
cis-csc12, 15, 8
cobit5APO13.01, DSS05.02
disaCCI-000366
isa-62443-2013SR 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.13.1.1, A.13.2.1, A.14.1.3
nistCM-6(a), SC-5(2)
nist-csfPR.PT-4
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-040210
cis1.1.10
anssiR28
stigrefSV-235005r991589_rule

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-91181-8

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
cis1.1.15
anssiR28

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-85618-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
stigidSLES-15-030810
cis1.1.16
anssiR71
stigrefSV-234980r991589_rule

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

The /var/tmp directory is a world-writable directory used for temporary file storage. Ensure it has its own partition or logical volume at installation time, or migrate it using LVM.
Rationale:
The /var/tmp partition is used as temporary storage by many programs. Placing /var/tmp in its own partition enables the setting of more restrictive mount options, which can help protect programs which use it.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_partition_for_var_tmp
Identifiers:

CCE-91182-6

References:
disaCCI-000366
os-srgSRG-OS-000480-GPOS-00227
cis1.1.11
anssiR28
Group   GNOME Desktop Environment   Group contains 3 rules
[ref]   GNOME is a graphical desktop environment bundled with many Linux distributions that allow users to easily interact with the operating system graphically rather than textually. The GNOME Graphical Display Manager (GDM) provides login, logout, and user switching contexts as well as display server management.

GNOME is developed by the GNOME Project and is considered the default Red Hat Graphical environment.

For more information on GNOME and the GNOME Project, see https://www.gnome.org.

Rule   Remove the GDM Package Group   [ref]

By removing the gdm package, the system no longer has GNOME installed installed. If X Windows is not installed then the system cannot boot into graphical user mode. This prevents the system from being accidentally or maliciously booted into a graphical.target mode. To do so, run the following command:
$ sudo yum remove gdm
Rationale:
Unnecessary service packages must not be installed to decrease the attack surface of the system. A graphical environment is unnecessary for certain types of systems including a virtualization hypervisor.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_gdm_removed
Identifiers:

CCE-92502-4

References:
nistCM-7(a), CM-7(b), CM-6(a)
os-srgSRG-OS-000480-GPOS-00227
cis1.10

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

class remove_gdm {
  package { 'gdm':
    ensure => 'purged',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92502-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
  - package_gdm_removed

- name: Ensure gdm is removed
  package:
    name: gdm
    state: absent
  when: '"gdm" in ansible_facts.packages'
  tags:
  - CCE-92502-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
  - package_gdm_removed

Complexity:low
Disruption:low
Reboot:false
Strategy:disable
# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm; then

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

zypper remove -y "gdm"

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

Rule   Make sure that the dconf databases are up-to-date with regards to respective keyfiles   [ref]

By default, DConf uses a binary database as a data backend. The system-level database is compiled from keyfiles in the /etc/dconf/db/ directory by the
dconf update
command. More specifically, content present in the following directories:
/etc/dconf/db/gdm.d
/etc/dconf/db/local.d
Rationale:
Unlike text-based keyfiles, the binary database is impossible to check by OVAL. Therefore, in order to evaluate dconf configuration, both have to be true at the same time - configuration files have to be compliant, and the database needs to be more recent than those keyfiles, which gives confidence that it reflects them.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_dconf_db_up_to_date
Identifiers:

CCE-83288-1

References:
disaCCI-000366
hipaa164.308(a)(1)(ii)(B), 164.308(a)(5)(ii)(A)
pcidssReq-6.2
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-010090
cis1.10
pcidss48.2.8, 8.2
stigrefSV-234809r958586_rule

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83288-1
  - DISA-STIG-SLES-15-010090
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - dconf_db_up_to_date
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

- name: Run dconf update
  ansible.builtin.command:
    cmd: dconf update
  when:
  - '"gdm" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-83288-1
  - DISA-STIG-SLES-15-010090
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.8
  - dconf_db_up_to_date
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed
  - unknown_strategy

# Remediation is applicable only in certain platforms
if rpm --quiet -q gdm && { [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ]; }; then

dconf update

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Sudo   Group contains 3 rules
[ref]   Sudo, which stands for "su 'do'", provides the ability to delegate authority to certain users, groups of users, or system administrators. When configured for system users and/or groups, Sudo can allow a user or group to execute privileged commands that normally only root is allowed to execute.

For more information on Sudo and addition Sudo configuration options, see https://www.sudo.ws.

Rule   Install sudo Package   [ref]

The sudo package can be installed with the following command:
$ sudo zypper install sudo
Rationale:
sudo is a program designed to allow a system administrator to give limited root privileges to users and log root activity. The basic philosophy is to give as few privileges as possible but still allow system users to get their work done.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_sudo_installed
Identifiers:

CCE-91183-4

References:
disaCCI-002235
ism1382, 1384, 1386
nistCM-6(a)
osppFMT_MOF_EXT.1
os-srgSRG-OS-000324-GPOS-00125
cis1.3.1
anssiR33
pcidss42.2.6, 2.2

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

class install_sudo {
  package { 'sudo':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91183-4
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_sudo_installed

- name: Ensure sudo is installed
  package:
    name: sudo
    state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91183-4
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_sudo_installed


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

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

zypper install -y "sudo"

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

Rule   Ensure Only Users Logged In To Real tty Can Execute Sudo - sudo use_pty   [ref]

The sudo use_pty tag, when specified, will only execute sudo commands from users logged in to a real tty. This should be enabled by making sure that the use_pty tag exists in /etc/sudoers configuration file or any sudo configuration snippets in /etc/sudoers.d/.
Rationale:
Requiring that sudo commands be run in a pseudo-terminal can prevent an attacker from retaining access to the user's terminal after the main program has finished executing.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_sudo_add_use_pty
Identifiers:

CCE-91190-9

References:
pcidssReq-10.2.5
cis1.3.2
anssiR39
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91190-9
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty

- name: Ensure use_pty is enabled in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    regexp: ^[\s]*Defaults.*\buse_pty\b.*$
    line: Defaults use_pty
    validate: /usr/sbin/visudo -cf %s
  when: '"sudo" in ansible_facts.packages'
  tags:
  - CCE-91190-9
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_add_use_pty

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

if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults[\s]*\buse_pty\b.*$' /etc/sudoers; then
        # sudoers file doesn't define Option use_pty
        echo "Defaults use_pty" >> /etc/sudoers
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

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

Rule   Ensure Sudo Logfile Exists - sudo logfile   [ref]

A custom log sudo file can be configured with the 'logfile' tag. This rule configures a sudo custom logfile at the default location suggested by CIS, which uses /var/log/sudo.log.
Rationale:
A sudo log file simplifies auditing of sudo commands.
Severity: 
low
Rule ID:xccdf_org.ssgproject.content_rule_sudo_custom_logfile
Identifiers:

CCE-91311-1

References:
pcidssReq-10.2.5
cis1.3.3
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91311-1
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile
- name: XCCDF Value var_sudo_logfile # promote to variable
  set_fact:
    var_sudo_logfile: !!str /var/log/sudo.log
  tags:
    - always

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

- name: Enable logfile option with appropriate value in /etc/sudoers
  lineinfile:
    path: /etc/sudoers
    line: Defaults logfile={{ var_sudo_logfile }}
    validate: /usr/sbin/visudo -cf %s
  when:
  - '"sudo" in ansible_facts.packages'
  - edit_sudoers_logfile_option is defined and not edit_sudoers_logfile_option.changed
  tags:
  - CCE-91311-1
  - PCI-DSS-Req-10.2.5
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - low_severity
  - no_reboot_needed
  - restrict_strategy
  - sudo_custom_logfile

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

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


if /usr/sbin/visudo -qcf /etc/sudoers; then
    cp /etc/sudoers /etc/sudoers.bak
    if ! grep -P '^[\s]*Defaults[\s]*\blogfile\s*=\s*(?:"?([^",\s]+)"?)\b.*$' /etc/sudoers; then
        # sudoers file doesn't define Option logfile
        echo "Defaults logfile=${var_sudo_logfile}" >> /etc/sudoers
    else
        # sudoers file defines Option logfile, remediate if appropriate value is not set
        if ! grep -P "^[\s]*Defaults.*\blogfile=${var_sudo_logfile}\b.*$" /etc/sudoers; then
            
            escaped_variable=${var_sudo_logfile//$'/'/$'\/'}
            sed -Ei "s/(^[\s]*Defaults.*\blogfile=)[-]?.+(\b.*$)/\1$escaped_variable\2/" /etc/sudoers
        fi
    fi
    
    # Check validity of sudoers and cleanup bak
    if /usr/sbin/visudo -qcf /etc/sudoers; then
        rm -f /etc/sudoers.bak
    else
        echo "Fail to validate remediated /etc/sudoers, reverting to original file."
        mv /etc/sudoers.bak /etc/sudoers
        false
    fi
else
    echo "Skipping remediation, /etc/sudoers failed to validate"
    false
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Updating Software   Group contains 4 rules
[ref]   The zypper command line tool is used to install and update software packages. The system also provides a graphical software update tool in the System menu, in the Administration submenu, called Software Update.

SUSE Linux Enterprise 15 systems contain an installed software catalog called the RPM database, which records metadata of installed packages. Consistently using zypper or the graphical Software Update for all software installation allows for insight into the current inventory of installed software on the system.

Rule   Ensure GPG keys are configured   [ref]

The operation system or installed application can be successfully bootstrapped without the GPG key being trusted. However, you cannot install new packages or update them until the keys are trusted.

Most packages managers implement GPG key signing to verify package integrity during installation.

To verify GPG keys are configured correctly for your package manager, one of the following command groups may provide the needed information depending on the package manager in use.

In SUSE Linux distributions, the administrators have to follow the next steps:
1. Log on to the system as a user with administrator rights.
2. Locate and download package, for example zoom_x86_64.rpm
3. Locate and download the public key (GPG) from the software download site, for example the key for zoom package is package-signing-key-5-12-6.pub
4. Import the key public key:
$ sudo rpm --import package-signing-key-5-12-6.pub
5. List the keys, for example the command:
$ sudo rpm -qa gpg-pubkey*
will provide:
gpg-pubkey-dd79b481-62fe7502
6. Get more details about the key, via the command:
$ sudo rpm -qa gpg-pubkey-dd79b481-62fe7502
7. Check the GPG key, for example the command:
$ sudo rpm -q gpg-pubkey --qf '%{name}-%{version}-%{release} --> %{summary}\n'
will provide:
gpg-pubkey-dd79b481-62fe7502 --> gpg(Zoom Video Communications, Inc. <CryptoOpsCodeSignProd@zoom.us>)


Rationale:
It is important to ensure that updates are obtained from a valid source to protect against spoofing that could lead to the inadvertent installation of malware on the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_GPG_keys_are_configured
Identifiers:

CCE-92542-0

References:
cis1.2.1

Rule   Ensure gpgcheck Enabled In Main zypper Configuration   [ref]

The gpgcheck option controls whether RPM packages' signatures are always checked prior to installation. To configure zypper to check package signatures before installing them, ensure the following line appears in /etc/zypp/zypp.conf in the [main] section:
gpgcheck=1
Rationale:
Changes to any software components can have significant effects on the overall security of the operating system. This requirement ensures the software has not been tampered with and that it has been provided by a trusted vendor.
Accordingly, patches, service packs, device drivers, or operating system components must be signed with a certificate recognized and approved by the organization.
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA).
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_gpgcheck_globally_activated
Identifiers:

CCE-83290-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.4.8
disaCCI-003992
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
stigidSLES-15-010430
cis1.2.3
anssiR59
pcidss46.3.3, 6.3
stigrefSV-234852r986469_rule

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

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

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

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

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

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

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

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

To ensure signature checking is not disabled for any repos, remove any lines from files in /etc/yum.repos.d of the form:
gpgcheck=0
Rationale:
Verifying the authenticity of the software prior to installation validates the integrity of the patch or upgrade received from a vendor. This ensures the software has not been tampered with and that it has been provided by a trusted vendor. Self-signed certificates are disallowed by this requirement. Certificates used to verify the software must be from an approved Certificate Authority (CA)."
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_ensure_gpgcheck_never_disabled
Identifiers:

CCE-85797-9

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-003992
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
cis1.2.3
anssiR59
pcidss46.3.3, 6.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:enable
- name: Grep for zypper repo section names
  shell: |
    set -o pipefail
    grep -HEr '^\[.+\]' -r /etc/zypp/repos.d/
  register: repo_grep_results
  failed_when: repo_grep_results.rc not in [0, 1]
  changed_when: false
  tags:
  - CCE-85797-9
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3
  - PCI-DSSv4-6.3.3
  - enable_strategy
  - ensure_gpgcheck_never_disabled
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed

- name: Set gpgcheck=1 for each zypper repo
  ini_file:
    path: '{{ item[0] }}'
    section: '{{ item[1] }}'
    option: gpgcheck
    value: '1'
    no_extra_spaces: true
  loop: '{{ repo_grep_results.stdout |regex_findall( ''(.+\.repo):\[(.+)\]\n?'' )
    if repo_grep_results is not skipped else [] }}'
  when: repo_grep_results is not skipped
  tags:
  - CCE-85797-9
  - CJIS-5.10.4.1
  - NIST-800-171-3.4.8
  - NIST-800-53-CM-11(a)
  - NIST-800-53-CM-11(b)
  - NIST-800-53-CM-5(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SA-12
  - NIST-800-53-SA-12(10)
  - NIST-800-53-SC-12
  - NIST-800-53-SC-12(3)
  - NIST-800-53-SI-7
  - PCI-DSS-Req-6.2
  - PCI-DSSv4-6.3
  - PCI-DSSv4-6.3.3
  - enable_strategy
  - ensure_gpgcheck_never_disabled
  - high_severity
  - low_complexity
  - medium_disruption
  - no_reboot_needed


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

Rule   Ensure package manager repositories are configured   [ref]

Systems need to have package manager repositories configured to ensure they receive the latest patches and updates.
Rationale:
If the system's package repositories are misconfigured important patches may not be identified or a rogue repository could introduce compromised software.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_package_repositories_are_configured
Identifiers:

CCE-92489-4

References:
cis1.2.2
Group   Account and Access Control   Group contains 16 groups and 65 rules
[ref]   In traditional Unix security, if an attacker gains shell access to a certain login account, they can perform any action or access any file to which that account has access. Therefore, making it more difficult for unauthorized people to gain shell access to accounts, particularly to privileged accounts, is a necessary part of securing a system. This section introduces mechanisms for restricting access to accounts under SUSE Linux Enterprise 15.
Group   Warning Banners for System Accesses   Group contains 1 group and 14 rules
[ref]   Each system should expose as little information about itself as possible.

System banners, which are typically displayed just before a login prompt, give out information about the service or the host's operating system. This might include the distribution name and the system kernel version, and the particular version of a network service. This information can assist intruders in gaining access to the system as it can reveal whether the system is running vulnerable software. Most network services can be configured to limit what information is displayed.

Many organizations implement security policies that require a system banner provide notice of the system's ownership, provide warning to unauthorized users, and remind authorized users of their consent to monitoring.
Group   Implement a GUI Warning Banner   Group contains 2 rules

Rule   Enable GNOME3 Login Warning Banner   [ref]

In the default graphical environment, displaying a login warning banner in the GNOME Display Manager's login screen can be enabled on the login screen by setting banner-message-enable to true.

To enable, add or edit banner-message-enable to /etc/dconf/db/gdm.d/00-security-settings. For example:
[org/gnome/login-screen]
banner-message-enable=true
Once the setting has been added, add a lock to /etc/dconf/db/gdm.d/locks/00-security-settings-lock to prevent user modification. For example:
/org/gnome/login-screen/banner-message-enable
After the settings have been set, run dconf update. The banner text must also be set.
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.

For U.S. Government systems, system use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_dconf_gnome_banner_enabled
Identifiers:

CCE-83265-9

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.9
disaCCI-001387, CCI-001384, CCI-000048, CCI-001386, CCI-001388, CCI-001385
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-8(a), AC-8(b), AC-8(c)
nist-csfPR.AC-7
os-srgSRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
stigidSLES-15-010080
cis1.10
stigrefSV-234808r958586_rule

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83265-9
  - DISA-STIG-SLES-15-010080
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(b)
  - NIST-800-53-AC-8(c)
  - dconf_gnome_banner_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

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

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

- name: Dconf Update
  command: dconf update
  when: '"gdm" in ansible_facts.packages'
  tags:
  - CCE-83265-9
  - DISA-STIG-SLES-15-010080
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(b)
  - NIST-800-53-AC-8(c)
  - dconf_gnome_banner_enabled
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

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

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

mkdir -p "${DBDIR}"

# Comment out the configurations in databases different from the target one
if [ "${#SETTINGSFILES[@]}" -ne 0 ]
then
    if grep -q "^\\s*banner-message-enable\\s*=" "${SETTINGSFILES[@]}"
    then
        
        sed -Ei "s/(^\s*)banner-message-enable(\s*=)/#\1banner-message-enable\2/g" "${SETTINGSFILES[@]}"
    fi
fi

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

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

mkdir -p "${LOCKSFOLDER}"

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

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

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

Rule   Modify the System Login Banner   [ref]

To configure the system login banner edit /etc/issue. Replace the default text with a message compliant with the local site policy or a legal disclaimer. The DoD required text is either:

You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.
-Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.


OR:

I've read & consent to terms in IS user agreem't.
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.

System use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_banner_etc_issue
Identifiers:

CCE-83262-6

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.9
disaCCI-001387, CCI-001384, CCI-000048, CCI-001386, CCI-001388, CCI-001385
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistAC-8(a), AC-8(c)
nist-csfPR.AC-7
os-srgSRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
stigidSLES-15-010020
cis1.8.1.2
stigrefSV-234803r958390_rule

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83262-6
  - DISA-STIG-SLES-15-010020
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - banner_etc_issue
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy
- name: XCCDF Value login_banner_text # promote to variable
  set_fact:
    login_banner_text: !!str ^(Authorized[\s\n]+users[\s\n]+only\.[\s\n]+All[\s\n]+activity[\s\n]+may[\s\n]+be[\s\n]+monitored[\s\n]+and[\s\n]+reported\.|^(?!.*(\\|fedora|rhel|sle|ubuntu)).*)$
  tags:
    - always

- name: Modify the System Login Banner Ensure issue-generator is Installed
  package:
    name: issue-generator
    state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83262-6
  - DISA-STIG-SLES-15-010020
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - banner_etc_issue
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Modify the System Login Banner - Ensure Correct Banner
  copy:
    dest: /etc/issue.d/99-oscap-setting
    content: '{{ login_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
      "\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
      "\n") | regex_replace("\\", "") | wordwrap() }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83262-6
  - DISA-STIG-SLES-15-010020
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - banner_etc_issue
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

- name: Modify the System Login Banner Retart issue-generator Service on Issue Configration
    Change
  ansible.builtin.systemd:
    name: issue-generator
    enabled: 'yes'
    state: restarted
    masked: 'no'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83262-6
  - DISA-STIG-SLES-15-010020
  - NIST-800-171-3.1.9
  - NIST-800-53-AC-8(a)
  - NIST-800-53-AC-8(c)
  - banner_etc_issue
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

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

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


# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
login_banner_text=$(echo "$login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
#    (dod_banners contains the long and short banner)
login_banner_text=$(echo "$login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
login_banner_text=$(echo "$login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newlines. (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "\n")
login_banner_text=$(echo "$login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/\n/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
login_banner_text=$(echo "$login_banner_text" | sed 's/\\//g')
formatted=$(echo "$login_banner_text" | fold -sw 80)
zypper install -y "issue-generator"
cat <<EOF >/etc/issue.d/99-oscap-setting
$formatted
EOF
/usr/bin/systemctl enable "issue-generator"
/usr/bin/systemctl start "issue-generator"
# 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.
if /usr/bin/systemctl --failed | grep -q "issue-generator"; then
    /usr/bin/systemctl reset-failed "issue-generator"
fi

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

Rule   Modify the System Login Banner for Remote Connections   [ref]

To configure the system login banner edit /etc/issue.net. Replace the default text with a message compliant with the local site policy or a legal disclaimer. The DoD required text is either:

You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.
-Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.


OR:

I've read & consent to terms in IS user agreem't.
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.

System use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_banner_etc_issue_net
Identifiers:

CCE-91350-9

References:
disaCCI-000048, CCI-001384, CCI-001385, CCI-001386, CCI-001387, CCI-001388
os-srgSRG-OS-000023-GPOS-00006, SRG-OS-000228-GPOS-00088
cis1.8.1.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91350-9
  - banner_etc_issue_net
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy
- name: XCCDF Value remote_login_banner_text # promote to variable
  set_fact:
    remote_login_banner_text: !!str ^(Authorized[\s\n]+uses[\s\n]+only\.[\s\n]+All[\s\n]+activity[\s\n]+may[\s\n]+be[\s\n]+monitored[\s\n]+and[\s\n]+reported\.|^(?!.*(\\|fedora|rhel|sle|ubuntu)).*)$
  tags:
    - always

- name: Modify the System Login Banner for Remote Connections - ensure correct banner
  copy:
    dest: /etc/issue.net
    content: '{{ remote_login_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
      "\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
      "\n") | regex_replace("\\", "") | wordwrap() }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91350-9
  - banner_etc_issue_net
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

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

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


# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
#    (dod_banners contains the long and short banner)
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newlines. (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "\n")
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/\n/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
remote_login_banner_text=$(echo "$remote_login_banner_text" | sed 's/\\//g')
formatted=$(echo "$remote_login_banner_text" | fold -sw 80)

cat <<EOF >/etc/issue.net
$formatted
EOF

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

Rule   Modify the System Message of the Day Banner   [ref]

To configure the system message banner edit /etc/motd. Replace the default text with a message compliant with the local site policy or a legal disclaimer. The DoD required text is either:

You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions:
-The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations.
-At any time, the USG may inspect and seize data stored on this IS.
-Communications using, or data stored on, this IS are not private, are subject to routine monitoring, interception, and search, and may be disclosed or used for any USG-authorized purpose.
-This IS includes security measures (e.g., authentication and access controls) to protect USG interests -- not for your personal benefit or privacy.
-Notwithstanding the above, using this IS does not constitute consent to PM, LE or CI investigative searching or monitoring of the content of privileged communications, or work product, related to personal representation or services by attorneys, psychotherapists, or clergy, and their assistants. Such communications and work product are private and confidential. See User Agreement for details.


OR:

I've read & consent to terms in IS user agreem't.
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.

System use notifications are required only for access via login interfaces with human users and are not required when such human interfaces do not exist.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_banner_etc_motd
Identifiers:

CCE-91349-1

References:
cis1.8.1.1

Complexity:low
Disruption:medium
Reboot:false
Strategy:unknown
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91349-1
  - banner_etc_motd
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy
- name: XCCDF Value motd_banner_text # promote to variable
  set_fact:
    motd_banner_text: !!str ^(Authorized[\s\n]+uses[\s\n]+only\.[\s\n]+All[\s\n]+activity[\s\n]+may[\s\n]+be[\s\n]+monitored[\s\n]+and[\s\n]+reported\.|^(?!.*(\\|fedora|rhel|sle|ubuntu)).*)$
  tags:
    - always

- name: Modify the System Message of the Day Banner - ensure correct banner
  copy:
    dest: /etc/motd
    content: '{{ motd_banner_text | regex_replace("^\^(.*)\$$", "\1") | regex_replace("^\((.*\.)\|.*\)$",
      "\1") | regex_replace("\[\\s\\n\]\+"," ") | regex_replace("\(\?:\[\\n\]\+\|\(\?:\\\\n\)\+\)",
      "\n") | regex_replace("\\", "") | wordwrap() }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91349-1
  - banner_etc_motd
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - unknown_strategy

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

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


# Multiple regexes transform the banner regex into a usable banner
# 0 - Remove anchors around the banner text
motd_banner_text=$(echo "$motd_banner_text" | sed 's/^\^\(.*\)\$$/\1/g')
# 1 - Keep only the first banners if there are multiple
#    (dod_banners contains the long and short banner)
motd_banner_text=$(echo "$motd_banner_text" | sed 's/^(\(.*\.\)|.*)$/\1/g')
# 2 - Add spaces ' '. (Transforms regex for "space or newline" into a " ")
motd_banner_text=$(echo "$motd_banner_text" | sed 's/\[\\s\\n\]+/ /g')
# 3 - Adds newlines. (Transforms "(?:\[\\n\]+|(?:\\n)+)" into "\n")
motd_banner_text=$(echo "$motd_banner_text" | sed 's/(?:\[\\n\]+|(?:\\\\n)+)/\n/g')
# 4 - Remove any leftover backslash. (From any parethesis in the banner, for example).
motd_banner_text=$(echo "$motd_banner_text" | sed 's/\\//g')
formatted=$(echo "$motd_banner_text" | fold -sw 80)

cat <<EOF >/etc/motd
$formatted
EOF

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

Rule   Verify Group Ownership of System Login Banner   [ref]

To properly set the group owner of /etc/issue, run the command:
$ sudo chgrp root /etc/issue
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_issue
Identifiers:

CCE-91355-8

References:
cis1.8.1.5

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Find /etc/issue.d/ file(s) matching ^.*$ recursively
  command: find -H /etc/issue.d/  -type f ! -group 0 -regextype posix-extended -regex
    "^.*$"
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  tags:
  - CCE-91355-8
  - configure_strategy
  - file_groupowner_etc_issue
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner on /etc/issue.d/ file(s) matching ^.*$
  file:
    path: '{{ item }}'
    group: '0'
    state: file
  with_items:
  - '{{ files_found.stdout_lines }}'
  tags:
  - CCE-91355-8
  - configure_strategy
  - file_groupowner_etc_issue
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

find  /etc/issue.d/  -type f ! -group 0 -regextype posix-extended -regex '^.*$' -exec chgrp -L 0 {} \;

Rule   Verify Group Ownership of System Login Banner for Remote Connections   [ref]

To properly set the group owner of /etc/issue.net, run the command:
$ sudo chgrp root /etc/issue.net
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_issue_net
Identifiers:

CCE-91358-2

References:
cis1.8.1.6
pcidss41.2.8, 1.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - CCE-91358-2
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_groupowner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/issue.net
  file:
    path: /etc/issue.net
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91358-2
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_groupowner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chgrp 0 /etc/issue.net

Rule   Verify Group Ownership of Message of the Day Banner   [ref]

To properly set the group owner of /etc/motd, run the command:
$ sudo chgrp root /etc/motd
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper group ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_groupowner_etc_motd
Identifiers:

CCE-91352-5

References:
cis1.8.1.4

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/motd
  stat:
    path: /etc/motd
  register: file_exists
  tags:
  - CCE-91352-5
  - configure_strategy
  - file_groupowner_etc_motd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure group owner 0 on /etc/motd
  file:
    path: /etc/motd
    group: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91352-5
  - configure_strategy
  - file_groupowner_etc_motd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

Rule   Verify ownership of System Login Banner   [ref]

To properly set the owner of /etc/issue, run the command:
$ sudo chown root /etc/issue 
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_issue
Identifiers:

CCE-91356-6

References:
cis1.8.1.5

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Find /etc/issue.d/ file(s) matching ^.*$ recursively
  command: find -H /etc/issue.d/  -type f ! -uid 0 -regextype posix-extended -regex
    "^.*$"
  register: files_found
  changed_when: false
  failed_when: false
  check_mode: false
  tags:
  - CCE-91356-6
  - configure_strategy
  - file_owner_etc_issue
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner on /etc/issue.d/ file(s) matching ^.*$
  file:
    path: '{{ item }}'
    owner: '0'
    state: file
  with_items:
  - '{{ files_found.stdout_lines }}'
  tags:
  - CCE-91356-6
  - configure_strategy
  - file_owner_etc_issue
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

find  /etc/issue.d/  -type f ! -uid 0 -regextype posix-extended -regex '^.*$' -exec chown -L 0 {} \;

Rule   Verify ownership of System Login Banner for Remote Connections   [ref]

To properly set the owner of /etc/issue.net, run the command:
$ sudo chown root /etc/issue.net 
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_issue_net
Identifiers:

CCE-91359-0

References:
cis1.8.1.6
pcidss41.2.8, 1.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - CCE-91359-0
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_owner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/issue.net
  file:
    path: /etc/issue.net
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91359-0
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_owner_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
chown 0 /etc/issue.net

Rule   Verify ownership of Message of the Day Banner   [ref]

To properly set the owner of /etc/motd, run the command:
$ sudo chown root /etc/motd 
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper ownership will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_owner_etc_motd
Identifiers:

CCE-91353-3

References:
cis1.8.1.4

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/motd
  stat:
    path: /etc/motd
  register: file_exists
  tags:
  - CCE-91353-3
  - configure_strategy
  - file_owner_etc_motd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure owner 0 on /etc/motd
  file:
    path: /etc/motd
    owner: '0'
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91353-3
  - configure_strategy
  - file_owner_etc_motd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

Rule   Verify permissions on System Login Banner   [ref]

To properly set the permissions of /etc/issue, run the command:
$ sudo chmod 0644 /etc/issue
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_issue
Identifiers:

CCE-91354-1

References:
cis1.8.1.5

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue
  stat:
    path: /etc/issue
  register: file_exists
  tags:
  - CCE-91354-1
  - configure_strategy
  - file_permissions_etc_issue
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/issue
  file:
    path: /etc/issue
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91354-1
  - configure_strategy
  - file_permissions_etc_issue
  - 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/issue

Rule   Verify permissions on System Login Banner for Remote Connections   [ref]

To properly set the permissions of /etc/issue.net, run the command:
$ sudo chmod 0644 /etc/issue.net
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_issue_net
Identifiers:

CCE-91357-4

References:
cis1.8.1.6
pcidss41.2.8, 1.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/issue.net
  stat:
    path: /etc/issue.net
  register: file_exists
  tags:
  - CCE-91357-4
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_permissions_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/issue.net
  file:
    path: /etc/issue.net
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91357-4
  - PCI-DSSv4-1.2
  - PCI-DSSv4-1.2.8
  - configure_strategy
  - file_permissions_etc_issue_net
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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




chmod u-xs,g-xws,o-xwt /etc/issue.net

Rule   Verify permissions on Message of the Day Banner   [ref]

To properly set the permissions of /etc/motd, run the command:
$ sudo chmod 0644 /etc/motd
Rationale:
Display of a standardized and approved use notification before granting access to the operating system ensures privacy and security notification verbiage used is consistent with applicable federal laws, Executive Orders, directives, policies, regulations, standards, and guidance.
Proper permissions will ensure that only root user can modify the banner.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_etc_motd
Identifiers:

CCE-91351-7

References:
cis1.8.1.4

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Test for existence /etc/motd
  stat:
    path: /etc/motd
  register: file_exists
  tags:
  - CCE-91351-7
  - configure_strategy
  - file_permissions_etc_motd
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Ensure permission u-xs,g-xws,o-xwt on /etc/motd
  file:
    path: /etc/motd
    mode: u-xs,g-xws,o-xwt
  when: file_exists.stat is defined and file_exists.stat.exists
  tags:
  - CCE-91351-7
  - configure_strategy
  - file_permissions_etc_motd
  - 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/motd
Group   Protect Accounts by Configuring PAM   Group contains 4 groups and 11 rules
[ref]   PAM, or Pluggable Authentication Modules, is a system which implements modular authentication for Linux programs. PAM provides a flexible and configurable architecture for authentication, and it should be configured to minimize exposure to unnecessary risk. This section contains guidance on how to accomplish that.

PAM is implemented as a set of shared objects which are loaded and invoked whenever an application wishes to authenticate a user. Typically, the application must be running as root in order to take advantage of PAM, because PAM's modules often need to be able to access sensitive stores of account information, such as /etc/shadow. Traditional privileged network listeners (e.g. sshd) or SUID programs (e.g. sudo) already meet this requirement. An SUID root application, userhelper, is provided so that programs which are not SUID or privileged themselves can still take advantage of PAM.

PAM looks in the directory /etc/pam.d for application-specific configuration information. For instance, if the program login attempts to authenticate a user, then PAM's libraries follow the instructions in the file /etc/pam.d/login to determine what actions should be taken.

One very important file in /etc/pam.d is /etc/pam.d/system-auth. This file, which is included by many other PAM configuration files, defines 'default' system authentication measures. Modifying this file is a good way to make far-reaching authentication changes, for instance when implementing a centralized authentication service.
Warning:  Be careful when making changes to PAM's configuration files. The syntax for these files is complex, and modifications can have unexpected consequences. The default configurations shipped with applications should be sufficient for most users.
Warning:  Running authconfig or system-config-authentication will re-write the PAM configuration files, destroying any manually made changes and replacing them with a series of system defaults. One reference to the configuration file syntax can be found at https://fossies.org/linux/Linux-PAM-docs/doc/sag/Linux-PAM_SAG.pdf.
Group   Set Lockouts for Failed Password Attempts   Group contains 4 rules
[ref]   The pam_faillock PAM module provides the capability to lock out user accounts after a number of failed login attempts. Its documentation is available in /usr/share/doc/pam-VERSION/txts/README.pam_faillock.

Warning:  Locking out user accounts presents the risk of a denial-of-service attack. The lockout policy must weigh whether the risk of such a denial-of-service attack outweighs the benefits of thwarting password guessing attacks.

Rule   Limit Password Reuse   [ref]

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

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

CCE-91398-8

References:
disaCCI-000200
os-srgSRG-OS-000077-GPOS-00045
cis5.3.3

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

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91398-8
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

- name: Configure 'pam_pwhistory.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_pwhistory.so
    state: present
  when:
  - '"pam" in ansible_facts.packages'
  - check_pam_module_result.stdout is defined and '"pam_pwhistory.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-91398-8
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

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

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

- name: Set argument_value fact
  set_fact:
    argument_value: ''
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91398-8
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

- name: Add "use_authtok" argument to "pam_pwhistory.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_pwhistory.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> use_authtok\g<2>
    backrefs: true
  when:
  - '"pam" in ansible_facts.packages'
  - check_pam_module_argument_result is not skipped and '"use_authtok" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-91398-8
  - accounts_password_pam_pwhistory_remember
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_remember='5'

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

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


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

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

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

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

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

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

Rule   Set Deny For Failed Password Attempts   [ref]

The SUSE Linux Enterprise 15 operating system must lock an account after - at most - 5 consecutive invalid access attempts.
Rationale:
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, otherwise known as brute-force attacks, is reduced. Limits are imposed by locking the account. To configure the operating system to lock an account after three unsuccessful consecutive access attempts using pam_tally2.so, modify the content of both /etc/pam.d/login and /etc/pam.d/common-account as follows:

  • add or modify the pam_tally2.so module line in /etc/pam.d/login to ensure both onerr=fail and deny=5 are present. For example:
    auth required pam_tally2.so onerr=fail silent audit deny=5
               
  • add or modify the following line in /etc/pam.d/common-account:
    account required pam_tally2.so
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_passwords_pam_tally2
Identifiers:

CCE-85554-4

References:
disaCCI-000044
pcidssReq-8.1.6
os-srgSRG-OS-000021-GPOS-00005
stigidSLES-15-020010
cis5.3.2
anssiR31
pcidss48.3.4, 8.3
stigrefSV-234867r958388_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
- name: XCCDF Value var_password_pam_tally2 # promote to variable
  set_fact:
    var_password_pam_tally2: !!str 5
  tags:
    - always

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
    is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
    in /etc/pam.d/login
  block:

  - name: Set Deny For Failed Password Attempts - Check if required PAM module line
      is present in /etc/pam.d/login with different control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/login
      regexp: ^\s*auth\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Set Deny For Failed Password Attempts - Ensure the correct control for the
      required PAM module line in /etc/pam.d/login
    ansible.builtin.replace:
      dest: /etc/pam.d/login
      regexp: ^(\s*auth\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
      is included in /etc/pam.d/login
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/login
      line: auth    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
    is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\sdeny\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Ensure the "deny" PAM option for "pam_tally2.so"
    is included in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1 deny={{ var_password_pam_tally2 }}
    state: present
  register: result_pam_accounts_passwords_pam_tally2_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_option_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Ensure the required value for "deny"
    PAM option from "pam_tally2.so" in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s+.*)(deny)=[0-9a-zA-Z]+\s*(.*)
    line: \1\2={{ var_password_pam_tally2 }} \3
  register: result_pam_accounts_passwords_pam_tally2_edit
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_option_present.found > 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
    is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
    in /etc/pam.d/login
  block:

  - name: Set Deny For Failed Password Attempts - Check if required PAM module line
      is present in /etc/pam.d/login with different control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/login
      regexp: ^\s*auth\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Set Deny For Failed Password Attempts - Ensure the correct control for the
      required PAM module line in /etc/pam.d/login
    ansible.builtin.replace:
      dest: /etc/pam.d/login
      regexp: ^(\s*auth\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
      is included in /etc/pam.d/login
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/login
      insertafter: (fail)
      line: auth    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
    is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\sonerr\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Ensure the "onerr" PAM option for
    "pam_tally2.so" is included in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1 onerr=fail
    state: present
  register: result_pam_accounts_passwords_pam_tally2_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_option_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Ensure the required value for "onerr"
    PAM option from "pam_tally2.so" in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s+.*)(onerr)=[0-9a-zA-Z]+\s*(.*)
    line: \1\2=fail \3
  register: result_pam_accounts_passwords_pam_tally2_edit
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_option_present.found > 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if expected PAM module line
    is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Include or update the PAM module line
    in /etc/pam.d/common-account
  block:

  - name: Set Deny For Failed Password Attempts - Check if required PAM module line
      is present in /etc/pam.d/common-account with different control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/common-account
      regexp: ^\s*account\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Set Deny For Failed Password Attempts - Ensure the correct control for the
      required PAM module line in /etc/pam.d/common-account
    ansible.builtin.replace:
      dest: /etc/pam.d/common-account
      regexp: ^(\s*account\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Set Deny For Failed Password Attempts - Ensure the required PAM module line
      is included in /etc/pam.d/common-account
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/common-account
      line: account    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Set Deny For Failed Password Attempts - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Define a fact for control already
    filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Check if the required PAM module option
    is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\s\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Set Deny For Failed Password Attempts - Ensure the "" PAM option for "pam_tally2.so"
    is included in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    backrefs: true
    regexp: ^(\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1
    state: present
  register: result_pam_accounts_passwords_pam_tally2_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_option_present.found == 0
  tags:
  - CCE-85554-4
  - DISA-STIG-SLES-15-020010
  - PCI-DSS-Req-8.1.6
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

var_password_pam_tally2='5'

# Use a non-number regexp to force update of the value of the deny option




if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/login"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*auth\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/login")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/login"
    else
        echo "auth    required    pam_tally2.so" >> "/etc/pam.d/login"
    fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*\sdeny\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_tally2.so.*/ s/$/ deny=${var_password_pam_tally2}/" "/etc/pam.d/login"
else
    sed -i -E --follow-symlinks "s/(\s*auth\s+required\s+pam_tally2.so\s+.*)(deny=)[[:alnum:]]+\s*(.*)/\1\2${var_password_pam_tally2} \3/" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/login"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*auth\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/login")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/login"
    else
        LAST_MATCH_LINE=$(grep -nP "(fail)" "/etc/pam.d/login" | tail -n 1 | cut -d: -f 1)
        if [ ! -z $LAST_MATCH_LINE ]; then
            sed -i --follow-symlinks $LAST_MATCH_LINE" a auth     required    pam_tally2.so" "/etc/pam.d/login"
        else
            echo "auth    required    pam_tally2.so" >> "/etc/pam.d/login"
        fi
    fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*\sonerr\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_tally2.so.*/ s/$/ onerr=fail/" "/etc/pam.d/login"
else
    sed -i -E --follow-symlinks "s/(\s*auth\s+required\s+pam_tally2.so\s+.*)(onerr=)[[:alnum:]]+\s*(.*)/\1\2fail \3/" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/common-account"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*account\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/common-account")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*account\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/common-account"
    else
        echo "account    required    pam_tally2.so" >> "/etc/pam.d/common-account"
    fi
fi
# Check the option
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*\s\b" "/etc/pam.d/common-account"; then
    sed -i -E --follow-symlinks "/\s*account\s+required\s+pam_tally2.so.*/ s/$/ /" "/etc/pam.d/common-account"
fi

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

Rule   Configure the root Account lock for Failed Password Attempts via pam_tally2   [ref]

This rule configures the system to lock out the root account after a number of incorrect login attempts using pam_tally2.so.
Rationale:
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_passwords_pam_tally2_deny_root
Identifiers:

CCE-91281-6

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
disaCCI-002238, CCI-000044
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(b), IA-5(c)
nist-csfPR.AC-7
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
cis5.3.2
anssiR31

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_password_pam_tally2 # promote to variable
  set_fact:
    var_password_pam_tally2: !!str 5
  tags:
    - always

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Ensure the "onerr=fail" option from "pam_tally2.so" is not present in /etc/pam.d/login
  ansible.builtin.replace:
    dest: /etc/pam.d/login
    regexp: (.*auth.*{{ pam_module_control | regex_escape() }}.*pam_tally2.so.*)\bonerr=fail\b=?[0-9a-zA-Z]*(.*)
    replace: \1\2
  register: result_pam_option_removal
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if expected PAM module line is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Include or update the PAM module line in /etc/pam.d/login
  block:

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Check if required PAM module line is present in /etc/pam.d/login with different
      control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/login
      regexp: ^\s*auth\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the correct control for the required PAM module line in /etc/pam.d/login
    ansible.builtin.replace:
      dest: /etc/pam.d/login
      regexp: ^(\s*auth\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the required PAM module line is included in /etc/pam.d/login
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/login
      line: auth    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if the required PAM module option is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\sdeny\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Ensure the "deny" PAM option for "pam_tally2.so" is included in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1 deny={{ var_password_pam_tally2 }}
    state: present
  register: result_pam_accounts_passwords_pam_tally2_deny_root_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present.found
    == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Ensure the required value for "deny" PAM option from "pam_tally2.so" in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s+.*)(deny)=[0-9a-zA-Z]+\s*(.*)
    line: \1\2={{ var_password_pam_tally2 }} \3
  register: result_pam_accounts_passwords_pam_tally2_deny_root_edit
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present.found
    > 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if expected PAM module line is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Include or update the PAM module line in /etc/pam.d/login
  block:

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Check if required PAM module line is present in /etc/pam.d/login with different
      control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/login
      regexp: ^\s*auth\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the correct control for the required PAM module line in /etc/pam.d/login
    ansible.builtin.replace:
      dest: /etc/pam.d/login
      regexp: ^(\s*auth\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the required PAM module line is included in /etc/pam.d/login
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/login
      line: auth    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if the required PAM module option is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\seven_deny_root\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Ensure the "even_deny_root" PAM option for "pam_tally2.so" is included in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1 even_deny_root
    state: present
  register: result_pam_accounts_passwords_pam_tally2_deny_root_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present.found
    == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if expected PAM module line is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Include or update the PAM module line in /etc/pam.d/common-account
  block:

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Check if required PAM module line is present in /etc/pam.d/common-account
      with different control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/common-account
      regexp: ^\s*account\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the correct control for the required PAM module line in /etc/pam.d/common-account
    ansible.builtin.replace:
      dest: /etc/pam.d/common-account
      regexp: ^(\s*account\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure the required PAM module line is included in /etc/pam.d/common-account
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/common-account
      line: account    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Configure the root Account lock for Failed Password Attempts via pam_tally2
      - Ensure authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Define a fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Check if the required PAM module option is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\s\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure the root Account lock for Failed Password Attempts via pam_tally2
    - Ensure the "" PAM option for "pam_tally2.so" is included in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    backrefs: true
    regexp: ^(\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1
    state: present
  register: result_pam_accounts_passwords_pam_tally2_deny_root_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_deny_root_option_present.found
    == 0
  tags:
  - CCE-91281-6
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - accounts_passwords_pam_tally2_deny_root
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_password_pam_tally2='5'



if grep -qP "^\s*auth\s+required\s+pam_tally2.so\s.*\bonerr=fail\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "s/(.*auth.*required.*pam_tally2.so.*)\sonerr=fail=?[[:alnum:]]*(.*)/\1\2/g" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/login"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*auth\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/login")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/login"
    else
        echo "auth    required    pam_tally2.so" >> "/etc/pam.d/login"
    fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*\sdeny\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_tally2.so.*/ s/$/ deny=${var_password_pam_tally2}/" "/etc/pam.d/login"
else
    sed -i -E --follow-symlinks "s/(\s*auth\s+required\s+pam_tally2.so\s+.*)(deny=)[[:alnum:]]+\s*(.*)/\1\2${var_password_pam_tally2} \3/" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/login"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*auth\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/login")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/login"
    else
        echo "auth    required    pam_tally2.so" >> "/etc/pam.d/login"
    fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*\seven_deny_root\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_tally2.so.*/ s/$/ even_deny_root/" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/common-account"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*account\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/common-account")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*account\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/common-account"
    else
        echo "account    required    pam_tally2.so" >> "/etc/pam.d/common-account"
    fi
fi
# Check the option
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*\s\b" "/etc/pam.d/common-account"; then
    sed -i -E --follow-symlinks "/\s*account\s+required\s+pam_tally2.so.*/ s/$/ /" "/etc/pam.d/common-account"
fi

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

Rule   Set Lockout Time for Failed Password Attempts using pam_tally2   [ref]

This rule configures the system to lock out accounts during a specified time period after a number of incorrect login attempts using pam_tally2.so.
Rationale:
By limiting the number of failed logon attempts, the risk of unauthorized system access via user password guessing, also known as brute-forcing, is reduced. Limits are imposed by locking the account.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_passwords_pam_tally2_unlock_time
Identifiers:

CCE-91282-4

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
disaCCI-002238, CCI-000044
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nistCM-6(a), AC-7(b), IA-5(c)
nist-csfPR.AC-7
pcidssReq-8.1.7
os-srgSRG-OS-000329-GPOS-00128, SRG-OS-000021-GPOS-00005
cis5.3.2
anssiR31
pcidss48.3.4, 8.3

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_passwords_pam_tally2_unlock_time # promote to variable
  set_fact:
    var_accounts_passwords_pam_tally2_unlock_time: !!str 1800
  tags:
    - always

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Define a
    fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
    expected PAM module line is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Include or
    update the PAM module line in /etc/pam.d/login
  block:

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
      required PAM module line is present in /etc/pam.d/login with different control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/login
      regexp: ^\s*auth\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      the correct control for the required PAM module line in /etc/pam.d/login
    ansible.builtin.replace:
      dest: /etc/pam.d/login
      regexp: ^(\s*auth\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      the required PAM module line is included in /etc/pam.d/login
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/login
      line: auth    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Define a
    fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
    the required PAM module option is present in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    regexp: ^\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\sunlock_time\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_unlock_time_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure the
    "unlock_time" PAM option for "pam_tally2.so" is included in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1 unlock_time={{ var_accounts_passwords_pam_tally2_unlock_time }}
    state: present
  register: result_pam_accounts_passwords_pam_tally2_unlock_time_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_unlock_time_option_present.found
    == 0
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure the
    required value for "unlock_time" PAM option from "pam_tally2.so" in /etc/pam.d/login
  ansible.builtin.lineinfile:
    path: /etc/pam.d/login
    backrefs: true
    regexp: ^(\s*auth\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s+.*)(unlock_time)=[0-9a-zA-Z]+\s*(.*)
    line: \1\2={{ var_accounts_passwords_pam_tally2_unlock_time }} \3
  register: result_pam_accounts_passwords_pam_tally2_unlock_time_edit
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_unlock_time_option_present.found
    > 0
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Define a
    fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
    expected PAM module line is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_line_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Include or
    update the PAM module line in /etc/pam.d/common-account
  block:

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
      required PAM module line is present in /etc/pam.d/common-account with different
      control
    ansible.builtin.lineinfile:
      path: /etc/pam.d/common-account
      regexp: ^\s*account\s+.*\s+pam_tally2.so\s*
      state: absent
    check_mode: true
    changed_when: false
    register: result_pam_line_other_control_present

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      the correct control for the required PAM module line in /etc/pam.d/common-account
    ansible.builtin.replace:
      dest: /etc/pam.d/common-account
      regexp: ^(\s*account\s+).*(\bpam_tally2.so.*)
      replace: \1{{ pam_module_control }} \2
    register: result_pam_module_edit
    when:
    - result_pam_line_other_control_present.found == 1

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      the required PAM module line is included in /etc/pam.d/common-account
    ansible.builtin.lineinfile:
      dest: /etc/pam.d/common-account
      line: account    {{ pam_module_control }}    pam_tally2.so
    register: result_pam_module_add
    when:
    - result_pam_line_other_control_present.found == 0 or result_pam_line_other_control_present.found
      > 1

  - name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure
      authselect changes are applied
    ansible.builtin.command:
      cmd: authselect apply-changes -b
    when:
    - result_authselect_present is defined
    - result_authselect_present.stat.exists
    - |-
      (result_pam_module_add is defined and result_pam_module_add.changed)
       or (result_pam_module_edit is defined and result_pam_module_edit.changed)
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_line_present.found is defined
  - result_pam_line_present.found == 0
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Define a
    fact for control already filtered in case filters are used
  ansible.builtin.set_fact:
    pam_module_control: required
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Check if
    the required PAM module option is present in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    regexp: ^\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so\s*.*\s\b
    state: absent
  check_mode: true
  changed_when: false
  register: result_pam_module_accounts_passwords_pam_tally2_unlock_time_option_present
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Lockout Time for Failed Password Attempts using pam_tally2 - Ensure the
    "" PAM option for "pam_tally2.so" is included in /etc/pam.d/common-account
  ansible.builtin.lineinfile:
    path: /etc/pam.d/common-account
    backrefs: true
    regexp: ^(\s*account\s+{{ pam_module_control | regex_escape() }}\s+pam_tally2.so.*)
    line: \1
    state: present
  register: result_pam_accounts_passwords_pam_tally2_unlock_time_add
  when:
  - '"pam" in ansible_facts.packages'
  - result_pam_module_accounts_passwords_pam_tally2_unlock_time_option_present.found
    == 0
  tags:
  - CCE-91282-4
  - NIST-800-53-AC-7(b)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - accounts_passwords_pam_tally2_unlock_time
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_passwords_pam_tally2_unlock_time='1800'

# Use a non-number regexp to force update of the value of the deny option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/login"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*auth\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/login")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*auth\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/login"
    else
        echo "auth    required    pam_tally2.so" >> "/etc/pam.d/login"
    fi
fi
# Check the option
if ! grep -qP "^\s*auth\s+required\s+pam_tally2.so\s*.*\sunlock_time\b" "/etc/pam.d/login"; then
    sed -i -E --follow-symlinks "/\s*auth\s+required\s+pam_tally2.so.*/ s/$/ unlock_time=${var_accounts_passwords_pam_tally2_unlock_time}/" "/etc/pam.d/login"
else
    sed -i -E --follow-symlinks "s/(\s*auth\s+required\s+pam_tally2.so\s+.*)(unlock_time=)[[:alnum:]]+\s*(.*)/\1\2${var_accounts_passwords_pam_tally2_unlock_time} \3/" "/etc/pam.d/login"
fi
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*" "/etc/pam.d/common-account"; then
    # Line matching group + control + module was not found. Check group + module.
    if [ "$(grep -cP '^\s*account\s+.*\s+pam_tally2.so\s*' "/etc/pam.d/common-account")" -eq 1 ]; then
        # The control is updated only if one single line matches.
        sed -i -E --follow-symlinks "s/^(\s*account\s+).*(\bpam_tally2.so.*)/\1required \2/" "/etc/pam.d/common-account"
    else
        echo "account    required    pam_tally2.so" >> "/etc/pam.d/common-account"
    fi
fi
# Check the option
if ! grep -qP "^\s*account\s+required\s+pam_tally2.so\s*.*\s\b" "/etc/pam.d/common-account"; then
    sed -i -E --follow-symlinks "/\s*account\s+required\s+pam_tally2.so.*/ s/$/ /" "/etc/pam.d/common-account"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Set Password Quality Requirements   Group contains 1 group and 6 rules
[ref]   The default pam_pwquality PAM module provides strength checking for passwords. It performs a number of checks, such as making sure passwords are not similar to dictionary words, are of at least a certain length, are not the previous password reversed, and are not simply a change of case from the previous password. It can also require passwords to be in certain character classes. The pam_pwquality module is the preferred way of configuring password requirements.

The man pages pam_pwquality(8) provide information on the capabilities and configuration of each.
Group   Set Password Quality Requirements, if using pam_cracklib   Group contains 6 rules
[ref]   The pam_cracklib PAM module can be configured to meet requirements for a variety of policies.

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

Rule   Set Password Strength Minimum Digit Characters   [ref]

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

CCE-85564-3

References:
disaCCI-000194
pcidssReq-8.2.3
os-srgSRG-OS-000071-GPOS-00039
stigidSLES-15-020150
cis5.3.1
anssiR31
pcidss48.3.6, 8.3
stigrefSV-234884r986480_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_dcredit # promote to variable
  set_fact:
    var_password_pam_dcredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "dcredit={{ var_password_pam_dcredit
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+dcredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_dcredit }}\g<2>
    backrefs: true
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "dcredit" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+dcredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "dcredit" argument to "pam_cracklib.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> dcredit={{ var_password_pam_dcredit }}\g<2>
    backrefs: true
  when: check_pam_module_argument_result is not skipped and '"dcredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-85564-3
  - DISA-STIG-SLES-15-020150
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_dcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_dcredit='-1'

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


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

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

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

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

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

Rule   Set Password Strength Minimum Lowercase Characters   [ref]

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

CCE-85676-5

References:
disaCCI-000193
nistIA-5(1)(a), IA-5(1).1(v)
pcidssReq-8.2.3
os-srgSRG-OS-000070-GPOS-00038
stigidSLES-15-020140
cis5.3.1
anssiR31
pcidss48.3.6, 8.3
stigrefSV-234883r986479_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_lcredit # promote to variable
  set_fact:
    var_password_pam_lcredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "lcredit={{ var_password_pam_lcredit
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+lcredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_lcredit }}\g<2>
    backrefs: true
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "lcredit" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+lcredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "lcredit" argument to "pam_cracklib.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> lcredit={{ var_password_pam_lcredit }}\g<2>
    backrefs: true
  when: check_pam_module_argument_result is not skipped and '"lcredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-85676-5
  - DISA-STIG-SLES-15-020140
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_lcredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_lcredit='-1'

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


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

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

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

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

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

Rule   Set Password Minimum Length   [ref]

The pam_cracklib module's minlen parameter controls requirements for minimum characters required in a password. Add minlen=14 to set minimum password length requirements.
Rationale:
Password length is one factor of several that helps to determine strength and how long it takes to crack a password. Use of more characters in a password helps to exponentially increase the time and/or resources required to compromise the password.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_cracklib_accounts_password_pam_minlen
Identifiers:

CCE-85573-4

References:
disaCCI-000205
pcidssReq-8.2.3
os-srgSRG-OS-000078-GPOS-00046
stigidSLES-15-020260
cis5.3.1
anssiR31
pcidss48.3.6, 8.3
stigrefSV-234895r986494_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_minlen # promote to variable
  set_fact:
    var_password_pam_minlen: !!str 14
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "minlen={{ var_password_pam_minlen
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+minlen=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_minlen }}\g<2>
    backrefs: true
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "minlen" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+minlen(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "minlen" argument to "pam_cracklib.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> minlen={{ var_password_pam_minlen }}\g<2>
    backrefs: true
  when: check_pam_module_argument_result is not skipped and '"minlen" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-85573-4
  - DISA-STIG-SLES-15-020260
  - PCI-DSS-Req-8.2.3
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.6
  - cracklib_accounts_password_pam_minlen
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_minlen='14'

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


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

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

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

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

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

Rule   Set Password Strength Minimum Special Characters   [ref]

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

CCE-85574-2

References:
disaCCI-001619
nistIA-5(a), IA-5(v)
pcidssReq-8.2.3
os-srgSRG-OS-000266-GPOS-00101
stigidSLES-15-020270
cis5.3.1
anssiR31
stigrefSV-234896r991561_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_ocredit # promote to variable
  set_fact:
    var_password_pam_ocredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "ocredit={{ var_password_pam_ocredit
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+ocredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_ocredit }}\g<2>
    backrefs: true
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "ocredit" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+ocredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85574-2
  - DISA-STIG-SLES-15-020270
  - NIST-800-53-IA-5(a)
  - NIST-800-53-IA-5(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ocredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

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

var_password_pam_ocredit='-1'

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


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

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

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

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

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

Rule   Set Password Retry Limit   [ref]

The pam_cracklib module's retry parameter controls the maximum number of times to prompt the user for the password before returning with error. Make sure it is configured with a value that is no more than 3. For example, retry=1.
Rationale:
To reduce opportunities for successful guesses and brute-force attacks.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_cracklib_accounts_password_pam_retry
Identifiers:

CCE-85575-9

References:
disaCCI-000366
pcidssReq-8.1.6, Req-8.1.7
os-srgSRG-OS-000480-GPOS-00225
stigidSLES-15-020290
cis5.3.1
pcidss48.3.4, 8.3
stigrefSV-234897r991587_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_retry # promote to variable
  set_fact:
    var_password_pam_retry: !!str 3
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "retry={{ var_password_pam_retry
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+retry=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_retry }}\g<2>
    backrefs: true
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "retry" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+retry(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "retry" argument to "pam_cracklib.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> retry={{ var_password_pam_retry }}\g<2>
    backrefs: true
  when: check_pam_module_argument_result is not skipped and '"retry" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-85575-9
  - DISA-STIG-SLES-15-020290
  - PCI-DSS-Req-8.1.6
  - PCI-DSS-Req-8.1.7
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.4
  - cracklib_accounts_password_pam_retry
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_retry='3'

VALUES+=("$var_password_pam_retry")
VALUE_NAMES+=("retry")
ARGS+=("")
NEW_ARGS+=("")


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

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

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

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

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

Rule   Set Password Strength Minimum Uppercase Characters   [ref]

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

CCE-85675-7

References:
disaCCI-000192
nistIA-5(1)(a), IA-5(1).1(v)
pcidssReq-8.2.3
os-srgSRG-OS-000069-GPOS-00037
stigidSLES-15-020130
cis5.3.1
anssiR31
stigrefSV-234882r986478_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_password_pam_ucredit # promote to variable
  set_fact:
    var_password_pam_ucredit: !!str -1
  tags:
    - always

- name: Set control_flag fact
  set_fact:
    control_flag: requisite
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check to see if 'pam_cracklib.so' module is configured in '/etc/pam.d/common-password'
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+\S+\s+pam_cracklib.so' /etc/pam.d/common-password || true
  register: check_pam_module_result
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Configure 'pam_cracklib.so' module in '/etc/pam.d/common-password'
  lineinfile:
    path: /etc/pam.d/common-password
    line: password requisite pam_cracklib.so
    state: present
  when: check_pam_module_result.stdout is defined and '"pam_cracklib.so" not in check_pam_module_result.stdout'
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure 'pam_cracklib.so' module has conforming control flag
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+)\S+(\s+pam_cracklib.so\s+.*)
    line: \g<1>requisite\g<2>
    backrefs: true
  when: control_flag|length
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure "pam_cracklib.so" module has argument "ucredit={{ var_password_pam_ucredit
    }}"
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so(?:\s+\S+)*\s+ucredit=)(?:\S+)((\s+\S+)*\s*\\*\s*)$
    line: \g<1>{{ var_password_pam_ucredit }}\g<2>
    backrefs: true
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Check the presence of "ucredit" argument in "pam_cracklib.so" module
  shell: |
    set -o pipefail
    grep -E '^\s*password\s+requisite\s+pam_cracklib.so.*\s+ucredit(=|\s|\s*$)' /etc/pam.d/common-password || true
  register: check_pam_module_argument_result
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Add "ucredit" argument to "pam_cracklib.so" module
  lineinfile:
    path: /etc/pam.d/common-password
    regexp: ^(\s*password\s+requisite\s+pam_cracklib.so)((\s+\S+)*\s*(\\)*$)
    line: \g<1> ucredit={{ var_password_pam_ucredit }}\g<2>
    backrefs: true
  when: check_pam_module_argument_result is not skipped and '"ucredit" not in check_pam_module_argument_result.stdout'
  tags:
  - CCE-85675-7
  - DISA-STIG-SLES-15-020130
  - NIST-800-53-IA-5(1)(a)
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSS-Req-8.2.3
  - cracklib_accounts_password_pam_ucredit
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

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

var_password_pam_ucredit='-1'

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


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

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

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

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

for idx in "${!ARGS[@]}"
do
    if ! grep -q -P "^\s*password\s+requisite\s+pam_cracklib.so.*\s+${ARGS[$idx]}\s*$" /etc/pam.d/common-password ; then
        sed --follow-symlinks -i -E -e "s/^\\s*password\\s+requisite\\s+pam_cracklib.so.*\$/& ${NEW_ARGS[$idx]}/" /etc/pam.d/common-password
        if [ -n "${DEL_ARGS[$idx]}" ]; then
            sed --follow-symlinks -i -E -e "s/\s+${DEL_ARGS[$idx]}\S+\s+/ /g" /etc/pam.d/common-password
        fi
    fi
done
Group   Set Password Hashing Algorithm   Group contains 1 rule
[ref]   The system's default algorithm for storing password hashes in /etc/shadow is SHA-512. This can be configured in several locations.

Rule   Set Password Hashing Algorithm in /etc/login.defs   [ref]

In /etc/login.defs, add or update the following line to ensure the system will use SHA512 as the hashing algorithm:
ENCRYPT_METHOD SHA512
         
Rationale:
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised. Passwords that are encrypted with a weak algorithm are no more protected than if they are kept in plain text.

Using a stronger hashing algorithm makes password cracking attacks more difficult.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_set_password_hashing_algorithm_logindefs
Identifiers:

CCE-83279-0

References:
cis-csc1, 12, 15, 16, 5
cjis5.6.2.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.13.11
disaCCI-004062
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
ism0418, 1055, 1402
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
nistIA-5(c), IA-5(1)(c), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
os-srgSRG-OS-000073-GPOS-00041
stigidSLES-15-010260
cis5.4.1.1
pcidss48.3.2, 8.3
stigrefSV-234825r971535_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83279-0
  - CJIS-5.6.2.2
  - DISA-STIG-SLES-15-010260
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_logindefs
- name: XCCDF Value var_password_hashing_algorithm # promote to variable
  set_fact:
    var_password_hashing_algorithm: !!str SHA512
  tags:
    - always

- name: Set Password Hashing Algorithm in /etc/login.defs
  lineinfile:
    dest: /etc/login.defs
    regexp: ^#?ENCRYPT_METHOD
    line: ENCRYPT_METHOD {{ var_password_hashing_algorithm }}
    state: present
    create: true
  when: '"shadow" in ansible_facts.packages'
  tags:
  - CCE-83279-0
  - CJIS-5.6.2.2
  - DISA-STIG-SLES-15-010260
  - NIST-800-171-3.13.11
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(c)
  - NIST-800-53-IA-5(c)
  - PCI-DSS-Req-8.2.1
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - set_password_hashing_algorithm_logindefs

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

var_password_hashing_algorithm='SHA512'

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

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

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

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Protect Physical Console Access   Group contains 2 rules
[ref]   It is impossible to fully protect a system from an attacker with physical access, so securing the space in which the system is located should be considered a necessary step. However, there are some steps which, if taken, make it more difficult for an attacker to quickly or undetectably modify a system from its console.

Rule   Require Authentication for Emergency Systemd Target   [ref]

Emergency mode is intended as a system recovery method, providing a single user root access to the system during a failed boot sequence.

By default, Emergency mode is protected by requiring a password and is set in /usr/lib/systemd/system/emergency.service.
Rationale:
This prevents attackers with physical access from trivially bypassing security on the machine and gaining root access. Such accesses are further prevented by configuring the bootloader password.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_require_emergency_target_auth
Identifiers:

CCE-91347-5

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui3.1.1, 3.4.5
disaCCI-000213
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.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistIA-2, AC-3, CM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
os-srgSRG-OS-000080-GPOS-00048
cis1.5.3

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91347-5
  - NIST-800-171-3.1.1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - require_emergency_target_auth
  - restrict_strategy

- name: Require emergency mode password
  ansible.builtin.blockinfile:
    create: true
    dest: /etc/systemd/system/emergency.service.d/10-oscap.conf
    block: |-
      [Service]
      ExecStart=-/usr/lib/systemd/systemd-sulogin-shell emergency
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91347-5
  - NIST-800-171-3.1.1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - require_emergency_target_auth
  - restrict_strategy

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

service_dropin_cfg_dir="/etc/systemd/system/emergency.service.d"
service_dropin_file="${service_dropin_cfg_dir}/10-oscap.conf"


sulogin="/usr/lib/systemd/systemd-sulogin-shell emergency"


mkdir -p "${service_dropin_cfg_dir}"
echo "[Service]" >> "${service_dropin_file}"
echo "ExecStart=-$sulogin" >> "${service_dropin_file}"

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

Rule   Require Authentication for Single User Mode   [ref]

Single-user mode is intended as a system recovery method, providing a single user root access to the system by providing a boot option at startup.

By default, single-user mode is protected by requiring a password and is set in /usr/lib/systemd/system/rescue.service.
Rationale:
This prevents attackers with physical access from trivially bypassing security on the machine and gaining root access. Such accesses are further prevented by configuring the bootloader password.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_require_singleuser_auth
Identifiers:

CCE-91428-3

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui3.1.1, 3.4.5
disaCCI-000213
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.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
ism0421, 0422, 0431, 0974, 1173, 1401, 1504, 1505, 1546, 1557, 1558, 1559, 1560, 1561
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-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, AC-3, CM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
osppFIA_UAU.1
os-srgSRG-OS-000080-GPOS-00048
cis1.5.3

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91428-3
  - NIST-800-171-3.1.1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - require_singleuser_auth
  - restrict_strategy

- name: Require single user mode password
  lineinfile:
    create: true
    dest: /usr/lib/systemd/system/rescue.service
    regexp: ^#?ExecStart=
    line: ExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91428-3
  - NIST-800-171-3.1.1
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-3
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - require_singleuser_auth
  - restrict_strategy

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

found=false

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

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Service\]([^\n\[]*\n+)+?[[:space:]]*ExecStart" "$f"; then

            sed -i "s/ExecStart[^(\n)]*/ExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue/" "$f"

            found=true

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

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

            found=true
    fi
done

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

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

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Protect Accounts by Restricting Password-Based Login   Group contains 4 groups and 28 rules
[ref]   Conventionally, Unix shell accounts are accessed by providing a username and password to a login program, which tests these values for correctness using the /etc/passwd and /etc/shadow files. Password-based login is vulnerable to guessing of weak passwords, and to sniffing and man-in-the-middle attacks against passwords entered over a network or at an insecure console. Therefore, mechanisms for accessing accounts by entering usernames and passwords should be restricted to those which are operationally necessary.
Group   Set Account Expiration Parameters   Group contains 3 rules

Rule   Ensure shadow Group is Empty   [ref]

The shadow group allows system programs which require access the ability to read the /etc/shadow file. No users should be assigned to the shadow group.
Warning:  This rule remediation will ensure the group membership is empty in /etc/group. To avoid any disruption the remediation won't change the primary group of users in /etc/passwd if any user has the shadow GID as primary group.
Rationale:
Any users assigned to the shadow group would be granted read access to the /etc/shadow file. If attackers can gain read access to the /etc/shadow file, they can easily run a password cracking program against the hashed passwords to break them. Other security information that is stored in the /etc/shadow file (such as expiration) could also be useful to subvert additional user accounts.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_shadow_group_empty
Identifiers:

CCE-91344-2

References:
pcidssReq-8.2.1
cis6.2.18
pcidss48.3.2, 8.3


sed -ri 's/(^shadow:[^:]*:[^:]*:)([^:]+$)/\1/' /etc/group
Group   Set Password Expiration Parameters   Group contains 7 rules
[ref]   The file /etc/login.defs controls several password-related settings. Programs such as passwd, su, and login consult /etc/login.defs to determine behavior with regard to password aging, expiration warnings, and length. See the man page login.defs(5) for more information.

Users should be forced to change their passwords, in order to decrease the utility of compromised passwords. However, the need to change passwords often should be balanced against the risk that users will reuse or write down passwords if forced to change them too often. Forcing password changes every 90-360 days, depending on the environment, is recommended. Set the appropriate value as PASS_MAX_DAYS and apply it to existing accounts with the -M flag.

The PASS_MIN_DAYS (-m) setting prevents password changes for 7 days after the first change, to discourage password cycling. If you use this setting, train users to contact an administrator for an emergency password change in case a new password becomes compromised. The PASS_WARN_AGE (-W) setting gives users 7 days of warnings at login time that their passwords are about to expire.

For example, for each existing human user USER, expiration parameters could be adjusted to a 180 day maximum password age, 7 day minimum password age, and 7 day warning period with the following command:
$ sudo chage -M 180 -m 7 -W 7 USER

Rule   Set Existing Passwords Maximum Age   [ref]

Configure non-compliant accounts to enforce a 365-day maximum password lifetime restriction by running the following command:
$ sudo chage -M 365
          USER
         
Rationale:
Any password, no matter how complex, can eventually be cracked. Therefore, passwords need to be changed periodically. If the operating system does not limit the lifetime of passwords and force users to change their passwords, there is the risk that the operating system passwords could be compromised.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_set_max_life_existing
Identifiers:

CCE-85571-8

References:
disaCCI-004066
nistIA-5(f), IA-5(1)(d), CM-6(a)
os-srgSRG-OS-000076-GPOS-00044
stigidSLES-15-020230
cis5.4.1.2
pcidss48.3.9, 8.3
stigrefSV-234892r986492_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_maximum_age_login_defs # promote to variable
  set_fact:
    var_accounts_maximum_age_login_defs: !!str 365
  tags:
    - always

- name: Collect users with not correct maximum time period between password changes
  ansible.builtin.command:
    cmd: awk -F':' '(/^[^:]+:[^!*]/ && ($5 > {{ var_accounts_maximum_age_login_defs
      }} || $5 == "")) {print $1}' /etc/shadow
  register: user_names
  tags:
  - CCE-85571-8
  - DISA-STIG-SLES-15-020230
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.9
  - accounts_password_set_max_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Change the maximum time period between password changes
  ansible.builtin.command:
    cmd: passwd -q -x {{ var_accounts_maximum_age_login_defs }} {{ item }}
  with_items: '{{ user_names.stdout_lines }}'
  when: user_names.stdout_lines | length > 0
  tags:
  - CCE-85571-8
  - DISA-STIG-SLES-15-020230
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-5(1)(d)
  - NIST-800-53-IA-5(f)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.9
  - accounts_password_set_max_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_maximum_age_login_defs='365'


while IFS= read -r i; do
    
    passwd -q -x $var_accounts_maximum_age_login_defs $i

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

Rule   Set Existing Passwords Minimum Age   [ref]

Configure non-compliant accounts to enforce a 24 hours/1 day minimum password lifetime by running the following command:
$ sudo chage -m 1 USER
         
Rationale:
Enforcing a minimum password lifetime helps to prevent repeated password changes to defeat the password reuse or history enforcement requirement. If users are allowed to immediately and continually change their password, the password could be repeatedly changed in a short period of time to defeat the organization's policy regarding password reuse.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_set_min_life_existing
Identifiers:

CCE-85710-2

References:
disaCCI-004066
nistIA-5(1).1(v)
os-srgSRG-OS-000075-GPOS-00043
stigidSLES-15-020210
cis5.4.1.3
stigrefSV-234890r986487_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_minimum_age_login_defs # promote to variable
  set_fact:
    var_accounts_minimum_age_login_defs: !!str 1
  tags:
    - always

- name: Collect users with not correct minimum time period between password changes
  command: |
    awk -F':' '(/^[^:]+:[^!*]/ && ($4 < {{ var_accounts_minimum_age_login_defs }} || $4 == "")) {print $1}' /etc/shadow
  register: user_names
  tags:
  - CCE-85710-2
  - DISA-STIG-SLES-15-020210
  - NIST-800-53-IA-5(1).1(v)
  - accounts_password_set_min_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Change the minimum time period between password changes
  command: |
    passwd -q -n {{ var_accounts_minimum_age_login_defs }} {{ item }}
  with_items: '{{ user_names.stdout_lines }}'
  when: user_names.stdout_lines | length > 0
  tags:
  - CCE-85710-2
  - DISA-STIG-SLES-15-020210
  - NIST-800-53-IA-5(1).1(v)
  - accounts_password_set_min_life_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_minimum_age_login_defs='1'


while IFS= read -r i; do
    
    passwd -q -n $var_accounts_minimum_age_login_defs $i

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

Rule   Set Existing Passwords Warning Age   [ref]

To configure how many days prior to password expiration that a warning will be issued to users, run the command:
$ sudo chage --warndays 7
          USER
         
The DoD requirement is 7, and CIS recommendation is no less than 7 days. This profile requirement is 7.
Rationale:
Providing an advance warning that a password will be expiring gives users time to think of a secure password. Users caught unaware may choose a simple password or write it down where it may be discovered.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_set_warn_age_existing
Identifiers:

CCE-92479-5

References:
disaCCI-000198
nistIA-5(1).1(v)
cis5.4.1.4
pcidss48.3.9, 8.3

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: XCCDF Value var_accounts_password_warn_age_login_defs # promote to variable
  set_fact:
    var_accounts_password_warn_age_login_defs: !!str 7
  tags:
    - always

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

- name: Set Existing Passwords Warning Age - Ensure the Number of Days of Warning
    Before Password Expires
  ansible.builtin.command:
    cmd: chage --warndays {{ var_accounts_password_warn_age_login_defs }} {{ item
      }}
  with_items: '{{ result_pass_warn_age_user_names.stdout_lines }}'
  when: result_pass_warn_age_user_names is not skipped and result_pass_warn_age_user_names.stdout_lines
    | length > 0
  tags:
  - CCE-92479-5
  - NIST-800-53-IA-5(1).1(v)
  - PCI-DSSv4-8.3
  - PCI-DSSv4-8.3.9
  - accounts_password_set_warn_age_existing
  - configure_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

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

var_accounts_password_warn_age_login_defs='7'


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

Rule   Set existing passwords a period of inactivity before they been locked   [ref]

Configure user accounts that have been inactive for over a given period of time to be automatically disabled by running the following command:
$ sudo chage --inactive 30USER
         
Rationale:
Inactive accounts pose a threat to system security since the users are not logging in to notice failed login attempts or other anomalies.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_set_post_pw_existing
Identifiers:

CCE-92480-3

References:
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.6
disaCCI-000017, CCI-000795, CCI-003627, CCI-003628
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 6.2
iso27001-2013A.12.4.1, A.12.4.3, A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-004-6 R2.2.2, CIP-004-6 R2.2.3, CIP-007-3 R.1.3, CIP-007-3 R5, CIP-007-3 R5.1.1, CIP-007-3 R5.1.3, CIP-007-3 R5.2.1, CIP-007-3 R5.2.3
nistIA-4(e), AC-2(3), CM-6(a)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7
pcidssReq-8.1.4
os-srgSRG-OS-000118-GPOS-00060
cis5.4.1.5
pcidss48.2.6, 8.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_account_disable_post_pw_expiration # promote to variable
  set_fact:
    var_account_disable_post_pw_expiration: !!str 30
  tags:
    - always

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

- name: Change the period of inactivity
  ansible.builtin.command:
    cmd: chage --inactive {{ var_account_disable_post_pw_expiration }} {{ item }}
  with_items: '{{ user_names.stdout_lines }}'
  when: user_names is not skipped and user_names.stdout_lines | length > 0
  tags:
  - CCE-92480-3
  - NIST-800-171-3.5.6
  - NIST-800-53-AC-2(3)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-4(e)
  - PCI-DSS-Req-8.1.4
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.6
  - accounts_set_post_pw_existing
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_account_disable_post_pw_expiration='30'


while IFS= read -r i; do
    chage --inactive $var_account_disable_post_pw_expiration $i
done <   <(awk -v var="$var_account_disable_post_pw_expiration" -F: '(($7 > var || $7 == "") && $2 ~ /^\$/) {print $1}' /etc/shadow)
Group   Verify Proper Storage and Existence of Password Hashes   Group contains 8 rules
[ref]   By default, password hashes for local accounts are stored in the second field (colon-separated) in /etc/shadow. This file should be readable only by processes running with root credentials, preventing users from casually accessing others' password hashes and attempting to crack them. However, it remains possible to misconfigure the system and store password hashes in world-readable files such as /etc/passwd, or to even store passwords themselves in plaintext on the system. Using system-provided tools for password change/creation should allow administrators to avoid such misconfiguration.

Rule   Verify All Account Password Hashes are Shadowed   [ref]

If any password hashes are stored in /etc/passwd (in the second field, instead of an x or *), the cause of this misconfiguration should be investigated. The account should have its password reset and the hash should be properly stored, or the account should be deleted entirely.
Rationale:
The hashes for all user account passwords should be stored in the file /etc/shadow and never in /etc/passwd, which is readable by all users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed
Identifiers:

CCE-85846-4

References:
cis-csc1, 12, 15, 16, 5
cjis5.5.2
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.5.10
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
ism1410
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
nistIA-5(h), CM-6(a)
nist-csfPR.AC-1, PR.AC-6, PR.AC-7
pcidssReq-8.2.1
cis6.2.1
pcidss48.3.2, 8.3

Rule   Verify All Account Password Hashes are Shadowed with SHA512   [ref]

Verify the operating system requires the shadow password suite configuration be set to encrypt interactive user passwords using a strong cryptographic hash. Check that the interactive user account passwords are using a strong password hash with the following command:
$ sudo cut -d: -f2 /etc/shadow
$6$kcOnRq/5$NUEYPuyL.wghQwWssXRcLRFiiru7f5JPV6GaJhNC2aK5F3PZpE/BCCtwrxRc/AInKMNX3CdMw11m9STiql12f/
Password hashes ! or * indicate inactive accounts not available for logon and are not evaluated. If any interactive user password hash does not begin with $6, this is a finding.
Rationale:
Passwords need to be protected at all times, and encryption is the standard method for protecting passwords. If passwords are not encrypted, they can be plainly read (i.e., clear text) and easily compromised.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512
Identifiers:

CCE-85566-8

References:
disaCCI-000803, CCI-004062
nistIA-5(1)(c), IA-5(1).1(v), IA-7, IA-7.1
os-srgSRG-OS-000073-GPOS-00041, SRG-OS-000120-GPOS-00061
stigidSLES-15-020180
cis5.4.1.1
stigrefSV-234887r986483_rule

Rule   Ensure all users last password change date is in the past   [ref]

All users should have a password change date in the past.
Warning:  Automatic remediation is not available, in order to avoid any system disruption.
Rationale:
If a user recorded password change date is in the future then they could bypass any set password expiration.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_password_last_change_is_in_past
Identifiers:

CCE-92504-0

References:
cis5.4.1.6
pcidss48.3.5, 8.3

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-85847-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
cis6.2.13
pcidss48.2.2, 8.2

Rule   Verify No .forward Files Exist   [ref]

The .forward file specifies an email address to forward the user's mail to.
Rationale:
Use of the .forward file poses a security risk in that sensitive data may be inadvertently transferred outside the organization. The .forward file also poses a risk as it can be used to execute commands that may perform unintended actions.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_forward_files
Identifiers:

CCE-92622-0

References:
cis6.2.9

Rule   Ensure there are no legacy + NIS entries in /etc/passwd   [ref]

The + character in /etc/passwd file marks a place where entries from a network information service (NIS) should be directly inserted.
Rationale:
Using this method to include entries into /etc/passwd is considered legacy and should be avoided. These entries may provide a way for an attacker to gain access to the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_legacy_plus_entries_etc_passwd
Identifiers:

CCE-91399-6

References:
cis6.2.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure there are no legacy + NIS entries in /etc/passwd - Backup the Old /etc/passwd
    File
  ansible.builtin.copy:
    src: /etc/passwd
    dest: /etc/passwd-
    remote_src: true
  tags:
  - CCE-91399-6
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_legacy_plus_entries_etc_passwd
  - no_reboot_needed
  - restrict_strategy

- name: Ensure there are no legacy + NIS entries in /etc/passwd - Remove Lines Starting
    with + From /etc/passwd
  ansible.builtin.lineinfile:
    regexp: ^\+.*$
    state: absent
    path: /etc/passwd
  tags:
  - CCE-91399-6
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_legacy_plus_entries_etc_passwd
  - no_reboot_needed
  - restrict_strategy


if grep -q '^\+' /etc/passwd; then
# backup old file to /etc/passwd-
	cp /etc/passwd /etc/passwd-
	sed -i '/^\+.*$/d' /etc/passwd
fi

Rule   Ensure there are no legacy + NIS entries in /etc/shadow   [ref]

The + character in /etc/shadow file marks a place where entries from a network information service (NIS) should be directly inserted.
Rationale:
Using this method to include entries into /etc/shadow is considered legacy and should be avoided. These entries may provide a way for an attacker to gain access to the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_legacy_plus_entries_etc_shadow
Identifiers:

CCE-91402-8

References:
cis6.2.2

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure there are no legacy + NIS entries in /etc/shadow - Backup the Old /etc/shadow
    File
  ansible.builtin.copy:
    src: /etc/shadow
    dest: /etc/shadow-
    remote_src: true
  tags:
  - CCE-91402-8
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_legacy_plus_entries_etc_shadow
  - no_reboot_needed
  - restrict_strategy

- name: Ensure there are no legacy + NIS entries in /etc/shadow - Remove Lines Starting
    with + From /etc/shadow
  ansible.builtin.lineinfile:
    regexp: ^\+.*$
    state: absent
    path: /etc/shadow
  tags:
  - CCE-91402-8
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_legacy_plus_entries_etc_shadow
  - no_reboot_needed
  - restrict_strategy


if grep -q '^\+' /etc/shadow; then
# backup old file to /etc/shadow-
	cp /etc/shadow /etc/shadow-
	sed -i '/^\+.*$/d' /etc/shadow
fi

Rule   Verify No netrc Files Exist   [ref]

The .netrc files contain login information used to auto-login into FTP servers and reside in the user's home directory. These files may contain unencrypted passwords to remote FTP servers making them susceptible to access by unauthorized users and should not be used. Any .netrc files should be removed.
Rationale:
Unencrypted passwords for remote FTP servers may be stored in .netrc files.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_netrc_files
Identifiers:

CCE-92655-0

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
disaCCI-000196
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nerc-cipCIP-003-8 R1.3, CIP-003-8 R3, CIP-003-8 R3.1, CIP-003-8 R3.2, CIP-003-8 R3.3, CIP-003-8 R5.1.1, CIP-003-8 R5.3, CIP-004-6 R2.2.3, CIP-004-6 R2.3, CIP-007-3 R5.1, CIP-007-3 R5.1.2, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistIA-5(h), IA-5(1)(c), CM-6(a), IA-5(7)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
cis6.2.10
Group   Restrict Root Logins   Group contains 7 rules
[ref]   Direct root logins should be allowed only for emergency use. In normal situations, the administrator should access the system via a unique unprivileged account, and then use su or sudo to execute privileged commands. Discouraging administrators from accessing the root account directly ensures an audit trail in organizations with multiple administrators. Locking down the channels through which root can connect directly also reduces opportunities for password-guessing against the root account. The login program uses the file /etc/securetty to determine which interfaces should allow root logins. The virtual devices /dev/console and /dev/tty* represent the system consoles (accessible via the Ctrl-Alt-F1 through Ctrl-Alt-F6 keyboard sequences on a default installation). The default securetty file also contains /dev/vc/*. These are likely to be deprecated in most environments, but may be retained for compatibility. Root should also be prohibited from connecting via network protocols. Other sections of this document include guidance describing how to prevent root from logging in via SSH.

Rule   Verify Only Root Has UID 0   [ref]

If any account other than root has a UID of 0, this misconfiguration should be investigated and the accounts other than root should be removed or have their UID changed.
If the account is associated with system commands or applications the UID should be changed to one greater than "0" but less than "1000." Otherwise assign a UID greater than "1000" that has not already been assigned.
Rationale:
An account has root authority if it has a UID of 0. Multiple accounts with a UID of 0 afford more opportunity for potential intruders to guess a password for a privileged account. Proper configuration of sudo is recommended to afford multiple system administrators access to root privileges in an accountable manner.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_accounts_no_uid_except_zero
Identifiers:

CCE-85664-1

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.02, DSS06.03, DSS06.10
cui3.1.1, 3.1.5
disaCCI-000366
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
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
nistCM-6(b), CM-6.1(iv)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.DS-5
pcidssReq-8.5
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-020100
cis6.2.3
pcidss48.2.1, 8.2
stigrefSV-234876r991589_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all /etc/passwd file entries
  getent:
    database: passwd
    split: ':'
  tags:
  - CCE-85664-1
  - DISA-STIG-SLES-15-020100
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSS-Req-8.5
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.1
  - accounts_no_uid_except_zero
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

- name: Lock the password of the user accounts other than root with uid 0
  command: passwd -l {{ item.key }}
  loop: '{{ getent_passwd | dict2items | rejectattr(''key'', ''search'', ''root'')
    | list }}'
  when: item.value.1  == '0'
  tags:
  - CCE-85664-1
  - DISA-STIG-SLES-15-020100
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.5
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSS-Req-8.5
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.1
  - accounts_no_uid_except_zero
  - high_severity
  - low_complexity
  - low_disruption
  - no_reboot_needed
  - restrict_strategy

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

Rule   Verify Root Has A Primary GID 0   [ref]

The root user should have a primary group of 0.
Rationale:
To help ensure that root-owned files are not inadvertently exposed to other users.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_accounts_root_gid_zero
Identifiers:

CCE-91289-9

References:
pcidssReq-8.1.1
cis5.4.3
pcidss48.2.1, 8.2

Rule   Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty   [ref]

Ensure that the group sugroup referenced by var_pam_wheel_group_for_su variable and used as value for the pam_wheel.so group option exists and has no members. This empty group used by pam_wheel.so in /etc/pam.d/su ensures that no user can run commands with altered privileges through the su command.
Warning:  Note that this rule just ensures the group exists and has no members. This rule does not configure pam_wheel.so module. The pam_wheel.so module configuration is accomplished by use_pam_wheel_group_for_su rule.
Rationale:
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_pam_wheel_group_empty
Identifiers:

CCE-92528-9

References:
cis5.6
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92528-9
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

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

- name: Ensure the Group Used by pam_wheel.so Module Exists on System and is Empty
    - Ensure {{ var_pam_wheel_group_for_su }} Group is Empty
  ansible.builtin.lineinfile:
    path: /etc/group
    regexp: ^({{ var_pam_wheel_group_for_su }}:[^:]+:[0-9]+:).*$
    line: \1
    backrefs: true
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-92528-9
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - ensure_pam_wheel_group_empty
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_pam_wheel_group_for_su='sugroup'


if ! grep -q "^${var_pam_wheel_group_for_su}:[^:]*:[^:]*:[^:]*" /etc/group; then
    groupadd ${var_pam_wheel_group_for_su}
fi

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

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

Rule   Direct root Logins Not Allowed   [ref]

To further limit access to the root account, administrators can disable root logins at the console by editing the /etc/securetty file. This file lists all devices the root user is allowed to login to. If the file does not exist at all, the root user can login through any communication device on the system, whether via the console or via a raw network interface. This is dangerous as user can login to the system as root via Telnet, which sends the password in plain text over the network. By default, SUSE Linux Enterprise 15's /etc/securetty file only allows the root user to login at the console physically attached to the system. To prevent root from logging in, remove the contents of this file. To prevent direct root logins, remove the contents of this file by typing the following command:
$ sudo echo > /etc/securetty
Warning:  This rule only checks the /etc/securetty file existence and its content. If you need to restrict user access using the /etc/securetty file, make sure the pam_securetty.so PAM module is properly enabled in relevant PAM files.
Rationale:
Disabling direct root logins ensures proper accountability and multifactor authentication to privileged accounts. Users will first login, then escalate to privileged (root) access via su / sudo. This is required for FISMA Low and FISMA Moderate systems.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_direct_root_logins
Identifiers:

CCE-91427-5

References:
cis-csc1, 12, 15, 16, 5
cobit5DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.10
cui3.1.1, 3.1.6
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.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
cis5.5
anssiR33
pcidss48.6.1, 8.6

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91427-5
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - low_complexity
  - low_disruption
  - medium_severity
  - no_direct_root_logins
  - no_reboot_needed
  - restrict_strategy

- name: Direct root Logins Not Allowed
  copy:
    dest: /etc/securetty
    content: ''
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91427-5
  - NIST-800-171-3.1.1
  - NIST-800-171-3.1.6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-IA-2
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - low_complexity
  - low_disruption
  - medium_severity
  - no_direct_root_logins
  - no_reboot_needed
  - restrict_strategy

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

echo > /etc/securetty

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

Rule   Ensure that System Accounts Do Not Run a Shell Upon Login   [ref]

Some accounts are not associated with a human user of the system, and exist to perform some administrative functions. Should an attacker be able to log into these accounts, they should not be granted access to a shell.

The login shell for each local account is stored in the last field of each line in /etc/passwd. System accounts are those user accounts with a user ID less than 1000. The user ID is stored in the third field. If any system account other than root has a login shell, disable it with the command:
$ sudo usermod -s /sbin/nologin account
         
Warning:  Do not perform the steps in this section on the root account. Doing so might cause the system to become inaccessible.
Rationale:
Ensuring shells are not given to system accounts upon login makes it more difficult for attackers to make use of system accounts.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_no_shelllogin_for_systemaccounts
Identifiers:

CCE-85672-4

References:
cis-csc1, 12, 13, 14, 15, 16, 18, 3, 5, 7, 8
cobit5DSS01.03, DSS03.05, DSS05.04, DSS05.05, DSS05.07, DSS06.03
disaCCI-000366
isa-62443-20094.3.3.2.2, 4.3.3.5.1, 4.3.3.5.2, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 6.2
ism1491
iso27001-2013A.12.4.1, A.12.4.3, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistAC-6, CM-6(a), CM-6(b), CM-6.1(iv)
nist-csfDE.CM-1, DE.CM-3, PR.AC-1, PR.AC-4, PR.AC-6
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-020091
cis5.4.2
pcidss48.2.2, 8.2
stigrefSV-234875r991589_rule

Complexity:low
Disruption:medium
Reboot:false
Strategy:restrict
- name: Ensure that System Accounts Do Not Run a Shell Upon Login - Get All Local
    Users From /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - CCE-85672-4
  - DISA-STIG-SLES-15-020091
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

- name: Ensure that System Accounts Do Not Run a Shell Upon Login - Create local_users
    Variable From getent_passwd Facts
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd | dict2items }}'
  tags:
  - CCE-85672-4
  - DISA-STIG-SLES-15-020091
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

- name: Ensure that System Accounts Do Not Run a Shell Upon Login -  Disable Login
    Shell for System Accounts
  ansible.builtin.user:
    name: '{{ item.key }}'
    shell: /sbin/nologin
  loop: '{{ local_users }}'
  when:
  - item.key not in ['root']
  - item.value[1]|int < 1000
  - item.value[5] not in ['/sbin/shutdown', '/sbin/halt', '/bin/sync']
  tags:
  - CCE-85672-4
  - DISA-STIG-SLES-15-020091
  - NIST-800-53-AC-6
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(b)
  - NIST-800-53-CM-6.1(iv)
  - PCI-DSSv4-8.2
  - PCI-DSSv4-8.2.2
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - no_shelllogin_for_systemaccounts
  - restrict_strategy

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

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

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

Rule   Enforce Usage of pam_wheel with Group Parameter for su Authentication   [ref]

To ensure that only users who are members of the group set in the group option of pam_wheel.so module can run commands with altered privileges through the su command, make sure that the following line exists in the file /etc/pam.d/su:
auth required pam_wheel.so use_uid group=sugroup
         
Warning:  Note that ensure_pam_wheel_group_empty rule complements this requirement by ensuring the referenced group exists and has no members.
Rationale:
The su program allows to run commands with a substitute user and group ID. It is commonly used to run commands as the root user. Limiting access to such command is considered a good security practice.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_use_pam_wheel_group_for_su
Identifiers:

CCE-92522-2

References:
cis5.6
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92522-2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su
- name: XCCDF Value var_pam_wheel_group_for_su # promote to variable
  set_fact:
    var_pam_wheel_group_for_su: !!str sugroup
  tags:
    - always

- name: Enforce Usage of pam_wheel with Group Parameter for su Authentication - Add
    the group to the /etc/pam.d/su file
  ansible.builtin.lineinfile:
    path: /etc/pam.d/su
    state: present
    regexp: ^[\s]*#[\s]*auth[\s]+required[\s]+pam_wheel\.so[\s]+use_uid group=$
    line: auth             required        pam_wheel.so use_uid group={{ var_pam_wheel_group_for_su
      }}
  when: '"pam" in ansible_facts.packages'
  tags:
  - CCE-92522-2
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - use_pam_wheel_group_for_su

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

var_pam_wheel_group_for_su='sugroup'


PAM_CONF=/etc/pam.d/su

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

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

Rule   Ensure All Groups on the System Have Unique Group ID   [ref]

Change the group name or delete groups, so each has a unique id.
Warning:  Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale:
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_group_unique_id
Identifiers:

CCE-91339-2

References:
disaCCI-000764
os-srgSRG-OS-000104-GPOS-00051
cis6.2.15
pcidss48.2.1, 8.2

Rule   Ensure All Groups on the System Have Unique Group Names   [ref]

Change the group name or delete groups, so each has a unique name.
Warning:  Automatic remediation of this control is not available due to the unique requirements of each system.
Rationale:
To assure accountability and prevent unauthenticated access, groups must be identified uniquely to prevent potential misuse and compromise of the system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_group_unique_name
Identifiers:

CCE-91340-0

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

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

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

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

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

CCE-91401-0

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.4

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-91401-0
  - 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-91401-0
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-6(a)
  - accounts_root_path_dirs_no_write
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

Rule   Ensure that Root's Path Does Not Include Relative Paths or Null Directories   [ref]

Ensure that none of the directories in root's path is equal to a single . character, or that it contains any instances that lead to relative path traversal, such as .. or beginning a path without the slash (/) character. Also ensure that there are no "empty" elements in the path, such as in these examples:
PATH=:/bin
PATH=/bin:
PATH=/bin::/sbin
These empty elements have the same effect as a single . character.
Rationale:
Including these entries increases the risk that root could execute code from an untrusted location.
Severity: 
unknown
Rule ID:xccdf_org.ssgproject.content_rule_root_path_no_dot
Identifiers:

CCE-91400-2

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.4
Group   Ensure that Users Have Sensible Umask Values   Group contains 3 rules
[ref]   The umask setting controls the default permissions for the creation of new files. With a default umask setting of 077, files and directories created by users will not be readable by any other user on the system. Users who wish to make specific files group- or world-readable can accomplish this by using the chmod command. Additionally, users can make all their files readable to their group by default by setting a umask of 027 in their shell configuration files. If default per-user groups exist (that is, if every user has a default group whose name is the same as that user's username and whose only member is the user), then it may even be safe for users to select a umask of 007, making it very easy to intentionally share files with groups of which the user is a member.

Rule   Ensure the Default Bash Umask is Set Correctly   [ref]

To ensure the default umask for users of the Bash shell is set properly, add or correct the umask setting in /etc/bash.bashrc to read as follows:
umask 027
         
Rationale:
The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc
Identifiers:

CCE-91215-4

References:
cis-csc18
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03
disaCCI-000366
isa-62443-20094.3.4.3.3
iso27001-2013A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.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
nistAC-6(1), CM-6(a)
nist-csfPR.IP-2
os-srgSRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
cis5.4.5
anssiR36

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91215-4
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_user_umask # promote to variable
  set_fact:
    var_accounts_user_umask: !!str 027
  tags:
    - always

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

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

- name: Ensure the Default umask is Appended Correctly
  ansible.builtin.lineinfile:
    create: true
    path: /etc/bash.bashrc
    line: umask {{ var_accounts_user_umask }}
  when:
  - '"bash" in ansible_facts.packages'
  - umask_replace.found == 0
  tags:
  - CCE-91215-4
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_bashrc
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_user_umask='027'






grep -q "^[^#]*\bumask" /etc/bash.bashrc && \
  sed -i -E -e "s/^([^#]*\bumask)[[:space:]]+[[:digit:]]+/\1 $var_accounts_user_umask/g" /etc/bash.bashrc
if ! [ $? -eq 0 ]; then
    echo "umask $var_accounts_user_umask" >> /etc/bash.bashrc
fi

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

Rule   Ensure the Default Umask is Set Correctly in /etc/profile   [ref]

To ensure the default umask controlled by /etc/profile is set properly, add or correct the umask setting in /etc/profile to read as follows:
umask 027
         
Note that /etc/profile also reads scrips within /etc/profile.d directory. These scripts are also valid files to set umask value. Therefore, they should also be considered during the check and properly remediated, if necessary.
Rationale:
The umask value influences the permissions assigned to files when they are created. A misconfigured umask value could result in files with excessive permissions that can be read or written to by unauthorized users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile
Identifiers:

CCE-91216-2

References:
cis-csc18
cobit5APO13.01, BAI03.01, BAI03.02, BAI03.03
disaCCI-000366
isa-62443-20094.3.4.3.3
iso27001-2013A.14.1.1, A.14.2.1, A.14.2.5, A.6.1.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
nistAC-6(1), CM-6(a)
nist-csfPR.IP-2
os-srgSRG-OS-000480-GPOS-00228, SRG-OS-000480-GPOS-00227
cis5.4.5
anssiR36

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: XCCDF Value var_accounts_user_umask # promote to variable
  set_fact:
    var_accounts_user_umask: !!str 027
  tags:
    - always

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

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

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

- name: Ensure the Default Umask is Set Correctly in /etc/profile - Ensure umask Value
    For All Existing umask Definition in /etc/profile
  ansible.builtin.replace:
    path: /etc/profile
    regexp: ^(\s*)umask\s+\d+
    replace: \1umask {{ var_accounts_user_umask }}
  register: result_umask_replaced_profile
  tags:
  - CCE-91216-2
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - accounts_umask_etc_profile
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_user_umask='027'


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

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

if ! grep -qrE '^[^#]*umask' /etc/profile*; then
  echo "umask $var_accounts_user_umask" >> /etc/profile
fi

Rule   Set Interactive Session Timeout   [ref]

Setting the TMOUT option in /etc/profile ensures that all user sessions will terminate based on inactivity. The value of TMOUT should be exported and read only. The TMOUT setting in /etc/profile.d/autologout.sh should read as follows:
TMOUT=900
        
readonly TMOUT export TMOUT
Rationale:
Terminating an idle session within a short time period reduces the window of opportunity for unauthorized personnel to take control of a management session enabled on the console or console port that has been left unattended.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_tmout
Identifiers:

CCE-83269-1

References:
cis-csc1, 12, 15, 16
cobit5DSS05.04, DSS05.10, DSS06.10
cui3.1.11
disaCCI-000057, CCI-001133
isa-62443-20094.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9
isa-62443-2013SR 1.1, SR 1.10, SR 1.2, SR 1.5, SR 1.7, SR 1.8, SR 1.9
iso27001-2013A.18.1.4, A.9.2.1, A.9.2.4, A.9.3.1, A.9.4.2, A.9.4.3
nerc-cipCIP-004-6 R2.2.3, CIP-007-3 R5.1, CIP-007-3 R5.2, CIP-007-3 R5.3.1, CIP-007-3 R5.3.2, CIP-007-3 R5.3.3
nistAC-12, SC-10, AC-2(5), CM-6(a)
nist-csfPR.AC-7
os-srgSRG-OS-000163-GPOS-00072, SRG-OS-000029-GPOS-00010
stigidSLES-15-010130
cis5.4.4
anssiR32
pcidss48.6.1, 8.6
stigrefSV-234813r958402_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-83269-1
  - DISA-STIG-SLES-15-010130
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_accounts_tmout # promote to variable
  set_fact:
    var_accounts_tmout: !!str 900
  tags:
    - always

- name: Set Interactive Session Timeout
  block:

  - name: Check for duplicate values
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*TMOUT=
      state: absent
    check_mode: true
    changed_when: false
    register: dupes

  - name: Deduplicate values from /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*TMOUT=
      state: absent
    when: dupes.found is defined and dupes.found > 1

  - name: Insert correct line to /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*TMOUT=
      line: TMOUT={{ var_accounts_tmout }}
      state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83269-1
  - DISA-STIG-SLES-15-010130
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Interactive Session Timeout
  block:

  - name: Check for duplicate values
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*readonly\s+
      state: absent
    check_mode: true
    changed_when: false
    register: dupes

  - name: Deduplicate values from /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*readonly\s+
      state: absent
    when: dupes.found is defined and dupes.found > 1

  - name: Insert correct line to /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*readonly\s+
      line: readonly TMOUT
      state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83269-1
  - DISA-STIG-SLES-15-010130
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set Interactive Session Timeout
  block:

  - name: Check for duplicate values
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*export\s+
      state: absent
    check_mode: true
    changed_when: false
    register: dupes

  - name: Deduplicate values from /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*export\s+
      state: absent
    when: dupes.found is defined and dupes.found > 1

  - name: Insert correct line to /etc/profile.d/autologout.sh
    lineinfile:
      path: /etc/profile.d/autologout.sh
      create: true
      regexp: (?i)^\s*export\s+
      line: export TMOUT
      state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-83269-1
  - DISA-STIG-SLES-15-010130
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Set the permission for /etc/profile.d/autologout.sh
  file:
    path: /etc/profile.d/autologout.sh
    mode: '0755'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - lookup('ansible.builtin.file', '/etc/profile.d/autologout.sh', errors='warn')
  tags:
  - CCE-83269-1
  - DISA-STIG-SLES-15-010130
  - NIST-800-171-3.1.11
  - NIST-800-53-AC-12
  - NIST-800-53-AC-2(5)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-SC-10
  - PCI-DSSv4-8.6
  - PCI-DSSv4-8.6.1
  - accounts_tmout
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

var_accounts_tmout='900'


if [ -f /etc/profile.d/autologout.sh ]; then
    if grep --silent '^\s*TMOUT' /etc/profile.d/autologout.sh ; then
        sed -i -E "s/^(\s*)TMOUT\s*=\s*(\w|\$)*(.*)$/\1TMOUT=$var_accounts_tmout\3/g" /etc/profile.d/autologout.sh
    fi
else
    echo -e "\n# Set TMOUT to $var_accounts_tmout per security requirements" >> /etc/profile.d/autologout.sh
    echo "TMOUT=$var_accounts_tmout" >> /etc/profile.d/autologout.sh
fi
if ! grep --silent '^\s*readonly TMOUT' /etc/profile.d/autologout.sh ; then
    echo "readonly TMOUT" >> /etc/profile.d/autologout.sh
fi

if ! grep --silent '^\s*export TMOUT' /etc/profile.d/autologout.sh ; then
    echo "export TMOUT" >> /etc/profile.d/autologout.sh
fi
chmod +x /etc/profile.d/autologout.sh

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

Rule   All Interactive Users Home Directories Must Exist   [ref]

Create home directories to all local interactive users that currently do not have a home directory assigned. Use the following commands to create the user home directory assigned in /etc/passwd:
$ sudo mkdir /home/USER
        
Rationale:
If a local interactive user has a home directory defined that does not exist, the user may be given access to the / directory as the current working directory upon logon. This could create a Denial of Service because the user would not be able to access their logon configuration files, and it may give them visibility to system files they normally would not be able to access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_user_interactive_home_directory_exists
Identifiers:

CCE-85628-6

References:
disaCCI-000366
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-040080
cis6.2.5
stigrefSV-234992r991589_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all local users from /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - CCE-85628-6
  - DISA-STIG-SLES-15-040080
  - accounts_user_interactive_home_directory_exists
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Create local_users variable from the getent output
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
  tags:
  - CCE-85628-6
  - DISA-STIG-SLES-15-040080
  - accounts_user_interactive_home_directory_exists
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure interactive users have a home directory exists
  ansible.builtin.user:
    name: '{{ item.key }}'
    create_home: true
  loop: '{{ local_users }}'
  when:
  - item.value[2]|int >= 1000
  - item.value[2]|int != 65534
  tags:
  - CCE-85628-6
  - DISA-STIG-SLES-15-040080
  - accounts_user_interactive_home_directory_exists
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

for user in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534) print $1}' /etc/passwd); do
    mkhomedir_helper $user 0077;
done

Rule   All User Files and Directories In The Home Directory Must Have Mode 0750 Or Less Permissive   [ref]

Set the mode on files and directories in the local interactive user home directory with the following command:
$ sudo chmod 0750 /home/USER/FILE_DIR
        
Files that begin with a "." are excluded from this requirement.
Rationale:
If a local interactive user files have excessive permissions, unintended users may be able to access or modify them.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_users_home_files_permissions
Identifiers:

CCE-91403-6

References:
disaCCI-000366
os-srgSRG-OS-000480-GPOS-00227
cis6.2.6
anssiR50

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all local users from /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - CCE-91403-6
  - accounts_users_home_files_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Create local_users variable from the getent output
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
  tags:
  - CCE-91403-6
  - accounts_users_home_files_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Test for existence home directories to avoid creating them.
  ansible.builtin.stat:
    path: '{{ item.value[4] }}'
  register: path_exists
  loop: '{{ local_users }}'
  when:
  - item.value[1]|int >= 1000
  - item.value[1]|int != 65534
  - item.value[4] != "/"
  tags:
  - CCE-91403-6
  - accounts_users_home_files_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure interactive local users have proper permissions on their respective
    home directories
  ansible.builtin.file:
    path: '{{ item.0.value[4] }}'
    mode: u-s,g-w-s,o=-
    follow: false
    recurse: true
  loop: '{{ local_users|zip(path_exists.results)|list }}'
  when: item.1.stat is defined and item.1.stat.exists
  tags:
  - CCE-91403-6
  - accounts_users_home_files_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

for home_dir in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534 && $6 != "/") print $6 }' /etc/passwd); do
    # Only update the permissions when necessary. This will avoid changing the inode timestamp when
    # the permission is already defined as expected, therefore not impacting in possible integrity
    # check systems that also check inodes timestamps.
    find "$home_dir" -perm /7027 -exec chmod u-s,g-w-s,o=- {} \;
done

Rule   Ensure users' .netrc Files are not group or world accessible   [ref]

While the system administrator can establish secure permissions for users' .netrc files, the users can easily override these. This rule ensures every .netrc file or directory under the home directory related to an interactive user is not group or world accessible
Rationale:
.netrc files may contain unencrypted passwords that may be used to attack other systems. Note: While the complete removal of .netrc files is recommended, if any are required on the system, secure permissions must be applied.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_accounts_users_netrc_file_permissions
Identifiers:

CCE-92697-2

References:
cis6.2.11

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all local users from /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - CCE-92697-2
  - accounts_users_netrc_file_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Create local_users variable from the getent output
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
  tags:
  - CCE-92697-2
  - accounts_users_netrc_file_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Test for existence of .netrc file in home directories to avoid creating them,
    but only fixing permissions
  ansible.builtin.stat:
    path: '{{ item.value[4] }}/.netrc'
  register: path_exists
  loop: '{{ local_users }}'
  when:
  - item.value[1]|int >= 1000
  - item.value[1]|int != 65534
  tags:
  - CCE-92697-2
  - accounts_users_netrc_file_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure group and world cannot access respective .netrc files
  ansible.builtin.file:
    path: '{{ item.item.value[4] }}/.netrc'
    mode: '0600'
    state: file
  loop: '{{ path_exists.results }}'
  when: item.stat is defined and item.stat.exists
  tags:
  - CCE-92697-2
  - accounts_users_netrc_file_permissions
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

for user in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534) print $1 }' /etc/passwd); do
    home_dir=$(getent passwd "$user" | cut -d: -f6)
    find "${home_dir}/.netrc" -exec chmod 0600 {} \;
done

Rule   All Interactive User Home Directories Must Have mode 0750 Or Less Permissive   [ref]

Change the mode of interactive users home directories to 0750. To change the mode of interactive users home directory, use the following command:
$ sudo chmod 0750 /home/USER
        
Rationale:
Excessive permissions on local interactive user home directories may allow unauthorized access to user files by other users.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_file_permissions_home_directories
Identifiers:

CCE-85629-4

References:
disaCCI-000366
os-srgSRG-OS-000480-GPOS-00227
stigidSLES-15-040090
cis6.2.6
stigrefSV-234993r991589_rule

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Get all local users from /etc/passwd
  ansible.builtin.getent:
    database: passwd
    split: ':'
  tags:
  - CCE-85629-4
  - DISA-STIG-SLES-15-040090
  - file_permissions_home_directories
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Create local_users variable from the getent output
  ansible.builtin.set_fact:
    local_users: '{{ ansible_facts.getent_passwd|dict2items }}'
  tags:
  - CCE-85629-4
  - DISA-STIG-SLES-15-040090
  - file_permissions_home_directories
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Test for existence home directories to avoid creating them.
  ansible.builtin.stat:
    path: '{{ item.value[4] }}'
  register: path_exists
  loop: '{{ local_users }}'
  when:
  - item.value[1]|int >= 1000
  - item.value[1]|int != 65534
  - item.value[4] != "/"
  tags:
  - CCE-85629-4
  - DISA-STIG-SLES-15-040090
  - file_permissions_home_directories
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure interactive local users have proper permissions on their respective
    home directories
  ansible.builtin.file:
    path: '{{ item.0.value[4] }}'
    mode: u-s,g-w-s,o=-
    follow: false
    recurse: false
  loop: '{{ local_users|zip(path_exists.results)|list }}'
  when: item.1.stat is defined and item.1.stat.exists
  tags:
  - CCE-85629-4
  - DISA-STIG-SLES-15-040090
  - file_permissions_home_directories
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

for home_dir in $(awk -F':' '{ if ($3 >= 1000 && $3 != 65534 && $6 != "/") print $6 }' /etc/passwd); do
    # Only update the permissions when necessary. This will avoid changing the inode timestamp when
    # the permission is already defined as expected, therefore not impacting in possible integrity
    # check systems that also check inodes timestamps.
    find "$home_dir" -maxdepth 0 -perm /7027 -exec chmod u-s,g-w-s,o=- {} \;
done
Group   AppArmor   Group contains 4 rules
[ref]   Many security vulnerabilities result from bugs in trusted programs. A trusted program runs with privileges that attackers want to possess. The program fails to keep that trust if there is a bug in the program that allows the attacker to acquire said privilege.

AppArmor® is an application security solution designed specifically to apply privilege confinement to suspect programs. AppArmor allows the administrator to specify the domain of activities the program can perform by developing a security profile. A security profile is a listing of files that the program may access and the operations the program may perform. AppArmor secures applications by enforcing good application behavior without relying on attack signatures, so it can prevent attacks even if previously unknown vulnerabilities are being exploited.

For more information on using AppArmor, see https://www.suse.com/documentation/sles-12/book_security/data/cha_apparmor_intro.html.

Rule   Install the pam_apparmor Package   [ref]

The pam_apparmor package can be installed with the following command:
$ sudo zypper install pam_apparmor
Rationale:
Protection of system integrity using AppArmor depends on this package being installed.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_pam_apparmor_installed
Identifiers:

CCE-85765-6

References:
disaCCI-001764, CCI-001774, CCI-002165, CCI-002233, CCI-002235
nistAC-3(4), AC-6(8), AC-6(10), CM-7(5)(b), CM-7(2), SC-7(21), CM-6(a)
os-srgSRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000326-GPOS-00126, SRG-OS-000370-GPOS-00155, SRG-OS-000480-GPOS-00230, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232
stigidSLES-15-010390
cis1.7.1.1
anssiR45
stigrefSV-234848r958702_rule

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

class install_pam_apparmor {
  package { 'pam_apparmor':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Ensure pam_apparmor is installed
  package:
    name: pam_apparmor
    state: present
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-85765-6
  - DISA-STIG-SLES-15-010390
  - NIST-800-53-AC-3(4)
  - NIST-800-53-AC-6(10)
  - NIST-800-53-AC-6(8)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(2)
  - NIST-800-53-CM-7(5)(b)
  - NIST-800-53-SC-7(21)
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_pam_apparmor_installed


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

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

zypper install -y "pam_apparmor"

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

Rule   Enforce all AppArmor Profiles   [ref]

AppArmor profiles define what resources applications are able to access. To set all profiles to enforce mode run the following command:
$ sudo aa-enforce /etc/apparmor.d/*
To list unconfined processes run the following command:
$ sudo aa-unconfined
Any unconfined processes may need to have a profile created or activated for them and then be restarted.
Rationale:
Security configuration requirements vary from site to site. Some sites may mandate a policy that is stricter than the default policy, which is perfectly acceptable. This recommendation is intended to ensure that any policies that exist on the system are activated.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_all_apparmor_profiles_enforced
Identifiers:

CCE-92536-2

References:
cis1.7.1.4
anssiR45

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92536-2
  - all_apparmor_profiles_enforced
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Enforce all AppArmor Profiles - Ensure all AppArmor Profiles are reloaded
  ansible.builtin.command: apparmor_parser -q -r /etc/apparmor.d/
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  tags:
  - CCE-92536-2
  - all_apparmor_profiles_enforced
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Enforce all AppArmor Profiles - Ensure all AppArmor Profiles are enforcing
  ansible.builtin.command: aa-enforce /etc/apparmor.d/*
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  tags:
  - CCE-92536-2
  - all_apparmor_profiles_enforced
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Enforce all AppArmor Profiles - Collect unconfined processes
  ansible.builtin.command: aa-unconfined
  register: unconfined_processes
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  tags:
  - CCE-92536-2
  - all_apparmor_profiles_enforced
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Enforce all AppArmor Profiles - Provide details about unconfined processes
  ansible.builtin.assert:
    that:
    - unconfined_processes.stdout_lines | length > 0
    success_msg: The process {{ item }} may need to have a profile created or activated
      for them and then be restarted.
    fail_msg: ''
  with_items: '{{ unconfined_processes.stdout_lines }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  - unconfined_processes is not skipped
  tags:
  - CCE-92536-2
  - all_apparmor_profiles_enforced
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { rpm --quiet -q apparmor-profiles; }; then

# make sure apparmor-utils is installed for aa-complain and aa-enforce
zypper install -y "apparmor-utils"

# Ensure all AppArmor Profiles are enforcing
apparmor_parser -q -r /etc/apparmor.d/
aa-enforce /etc/apparmor.d/*


UNCONFINED=$(aa-unconfined)
if [ ! -z "$UNCONFINED" ]

then
  echo -e "***WARNING***: There are some unconfined processes:"
  echo -e "----------------------------"
  echo "The may need to have a profile created or activated for them and then be restarted."
  for PROCESS in "${UNCONFINED[@]}"
  do
      echo "$PROCESS"
  done
  echo -e "----------------------------"
  echo "The may need to have a profile created or activated for them and then be restarted."
fi

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

Rule   All AppArmor Profiles are in enforce or complain mode   [ref]

AppArmor profiles define what resources applications are able to access. To set all profiles to either enforce or complain mode run the following command to set all profiles to enforce mode:
$ sudo aa-enforce /etc/apparmor.d/*
run the following command to set all profiles to complain mode:
$ sudo aa-complain /etc/apparmor.d/*
To list unconfined processes run the following command:
$ sudo aa-unconfined
Any unconfined processes may need to have a profile created or activated for them and then be restarted.
Rationale:
Security configuration requirements vary from site to site. Some sites may mandate a policy that is stricter than the default policy, which is perfectly acceptable. This recommendation is intended to ensure that any policies that exist on the system are activated.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_all_apparmor_profiles_in_enforce_complain_mode
Identifiers:

CCE-92548-7

References:
cis1.7.1.3

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
- name: XCCDF Value var_apparmor_mode # promote to variable
  set_fact:
    var_apparmor_mode: !!str complain
  tags:
    - always

- name: All AppArmor Profiles are in enforce or complain mode - Ensure all AppArmor
    Profiles are reloaded
  ansible.builtin.command: apparmor_parser -q -r /etc/apparmor.d/
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: All AppArmor Profiles are in enforce or complain mode - Set all AppArmor profiles
    to enforce mode
  ansible.builtin.command: aa-enforce /etc/apparmor.d/*
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  - var_apparmor_mode == 'enforce'
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: All AppArmor Profiles are in enforce or complain mode - Set all AppArmor profiles
    to complain mode
  ansible.builtin.command: aa-complain /etc/apparmor.d/*
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  - var_apparmor_mode == 'complain'
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: All AppArmor Profiles are in enforce or complain mode - Collect unconfined
    processes
  ansible.builtin.command: aa-unconfined
  register: unconfined_processes
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: All AppArmor Profiles are in enforce or complain mode - Provide details about
    unconfined processes
  ansible.builtin.assert:
    that:
    - unconfined_processes.stdout_lines | length > 0
    success_msg: The process {{ item }} may need to have a profile created or activated
      for them and then be restarted.
    fail_msg: ''
  with_items: '{{ unconfined_processes.stdout_lines }}'
  when:
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  - '"apparmor-profiles" in ansible_facts.packages'
  - unconfined_processes is not skipped
  tags:
  - CCE-92548-7
  - all_apparmor_profiles_in_enforce_complain_mode
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

# Remediation is applicable only in certain platforms
if [ ! -f /.dockerenv ] && [ ! -f /run/.containerenv ] && { rpm --quiet -q apparmor-profiles; }; then

var_apparmor_mode='complain'


# make sure apparmor-utils is installed for aa-complain and aa-enforce
zypper install -y "apparmor-utils"

# Reload all AppArmor profiles
apparmor_parser -q -r /etc/apparmor.d/

# Set the mode
APPARMOR_MODE="$var_apparmor_mode"

if [ "$APPARMOR_MODE" = "enforce" ]
then
  # Set all profiles to enforce mode
  aa-enforce /etc/apparmor.d/*
fi

if [ "$APPARMOR_MODE" = "complain" ]
then
  # Set all profiles to complain mode
  aa-complain /etc/apparmor.d/*
fi


UNCONFINED=$(aa-unconfined)
if [ ! -z "$UNCONFINED" ]

then
  echo -e "***WARNING***: There are some unconfined processes:"
  echo -e "----------------------------"
  echo "The may need to have a profile created or activated for them and then be restarted."
  for PROCESS in "${UNCONFINED[@]}"
  do
      echo "$PROCESS"
  done
  echo -e "----------------------------"
  echo "The may need to have a profile created or activated for them and then be restarted."
fi

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

Rule   Ensure AppArmor is Active and Configured   [ref]

Verify that the Apparmor tool is configured to control whitelisted applications and user home directory access control.

The apparmor service can be enabled with the following command:
$ sudo systemctl enable apparmor.service
Rationale:
Using a whitelist provides a configuration management method for allowing the execution of only authorized software. Using only authorized software decreases risk by limiting the number of potential vulnerabilities.

The organization must identify authorized software programs and permit execution of authorized software by adding each authorized program to the "pam_apparmor" exception policy. The process used to identify software programs that are authorized to execute on organizational information systems is commonly referred to as whitelisting.

Verification of whitelisted software occurs prior to execution or at system startup.

Users' home directories/folders may contain information of a sensitive nature. Nonprivileged users should coordinate any sharing of information with a System Administrator (SA) through shared resources.

Apparmor can confine users to their home directory, not allowing them to make any changes outside of their own home directories. Confining users to their home directory will minimize the risk of sharing information.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_apparmor_configured
Identifiers:

CCE-85752-4

References:
disaCCI-001764, CCI-001774, CCI-002165, CCI-002233, CCI-002235
nistAC-3(4), AC-6(8), AC-6(10), CM-7(5)(b), CM-7(2), SC-7(21), CM-6(a)
os-srgSRG-OS-000312-GPOS-00122, SRG-OS-000312-GPOS-00123, SRG-OS-000312-GPOS-00124, SRG-OS-000324-GPOS-00125, SRG-OS-000326-GPOS-00126, SRG-OS-000370-GPOS-00155, SRG-OS-000480-GPOS-00230, SRG-OS-000480-GPOS-00227, SRG-OS-000480-GPOS-00231, SRG-OS-000480-GPOS-00232
stigidSLES-15-010390
cis1.7.1.2
anssiR45
stigrefSV-234848r958702_rule

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

class enable_apparmor {
  service {'apparmor':
    enable => true,
    ensure => 'running',
  }
}

- name: Start apparmor.service
  systemd:
    name: apparmor.service
    state: started
    enabled: true
  when: ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-85752-4
  - DISA-STIG-SLES-15-010390
  - NIST-800-53-AC-3(4)
  - NIST-800-53-AC-6(10)
  - NIST-800-53-AC-6(8)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(2)
  - NIST-800-53-CM-7(5)(b)
  - NIST-800-53-SC-7(21)
  - apparmor_configured
  - medium_severity


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

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

# Enable apparmor
/usr/bin/systemctl enable "apparmor"
/usr/bin/systemctl start "apparmor"
# 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.
if /usr/bin/systemctl --failed | grep -q "apparmor"; then
    /usr/bin/systemctl reset-failed "apparmor"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   GRUB2 bootloader configuration   Group contains 2 groups and 5 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 SUSE Linux Enterprise 15 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 4 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-85849-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-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.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
cis1.5.2
anssiR29
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85849-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
  - 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" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-85849-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
  - 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" 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-85849-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
  - 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 && { [ ! -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/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-85848-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-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.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
cis1.5.2
anssiR29
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85848-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
  - 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" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-85848-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
  - 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" 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-85848-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
  - 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 && { [ ! -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/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-91426-7

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
cis1.5.2
anssiR29
pcidss42.2.6, 2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91426-7
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_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" in ansible_facts.packages'
  - ansible_virtualization_type not in ["docker", "lxc", "openvz", "podman", "container"]
  tags:
  - CCE-91426-7
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_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" 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-91426-7
  - NIST-800-171-3.4.5
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - configure_strategy
  - file_permissions_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 && { [ ! -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   Set Boot Loader Password in grub2   [ref]

The grub2 boot loader should have a superuser account and password protection enabled to protect boot-time settings.

Since plaintext passwords are a security risk, generate a hash for the password by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.

Using the hash from the output, modify the /etc/grub.d/40_custom file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the root account and password. Once the superuser password has been added, update the grub.cfg file by running:
grub2-mkconfig -o /boot/grub2/grub2.cfg
Warning:  To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation must be automated as a component of machine provisioning, or followed manually as outlined above. Also, do NOT manually add the superuser account and password to the grub.cfg file as the grub2-mkconfig command overwrites this file.
Rationale:
Password protection on the boot loader configuration ensures users with physical access cannot trivially alter important bootloader settings. These include which kernel to use, and whether to enter single-user mode.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_grub2_password
Identifiers:

CCE-83274-1

References:
cis-csc1, 11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS05.10, DSS06.03, DSS06.06, DSS06.10
cui3.4.5
disaCCI-000213
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.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.18.1.4, A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.2, A.9.2.3, A.9.2.4, A.9.2.6, A.9.3.1, A.9.4.1, A.9.4.2, A.9.4.3, A.9.4.4, A.9.4.5
nistCM-6(a)
nist-csfPR.AC-1, PR.AC-4, PR.AC-6, PR.AC-7, PR.PT-3
os-srgSRG-OS-000080-GPOS-00048
stigidSLES-15-010190
cis1.5.1
anssiR5
stigrefSV-234819r958472_rule
Group   UEFI GRUB2 bootloader configuration   Group contains 1 rule
[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   Set the UEFI Boot Loader Password   [ref]

The grub2 boot loader should have a superuser account and password protection enabled to protect boot-time settings.

Since plaintext passwords are a security risk, generate a hash for the password by running the following command:
# grub2-mkpasswd-pbkdf2
When prompted, enter the password that was selected.

Using the hash from the output, modify the /etc/grub.d/40_custom file with the following content:
set superusers="boot"
password_pbkdf2 boot grub.pbkdf2.sha512.VeryLongString
NOTE: the bootloader superuser account and password MUST differ from the root account and password. Once the superuser password has been added, update the grub.cfg file by running:
grub2-mkconfig -o /boot/grub2/grub2.cfg
Warning:  To prevent hard-coded passwords, automatic remediation of this control is not available. Remediation must be automated as a component of machine provisioning, or followed manually as outlined above. Also, do NOT manually add the superuser account and password to the grub.cfg file as the grub2-mkconfig command overwrites this file.
Rationale:
Password protection on the boot loader configuration ensures users with physical access cannot trivially alter important bootloader settings. These include which kernel to use, and whether to enter single-user mode.
Severity: 
high
Rule ID:xccdf_org.ssgproject.content_rule_grub2_uefi_password
Identifiers:

CCE-83275-8

References:
cis-csc11, 12, 14, 15, 16, 18, 3, 5
cobit5DSS05.02, DSS05.04, DSS05.05, DSS05.07, DSS06.03, DSS06.06
cui3.4.5
disaCCI-000213
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.5.3, 4.3.3.5.4, 4.3.3.5.5, 4.3.3.5.6, 4.3.3.5.7, 4.3.3.5.8, 4.3.3.6.1, 4.3.3.6.2, 4.3.3.6.3, 4.3.3.6.4, 4.3.3.6.5, 4.3.3.6.6, 4.3.3.6.7, 4.3.3.6.8, 4.3.3.6.9, 4.3.3.7.1, 4.3.3.7.2, 4.3.3.7.3, 4.3.3.7.4
isa-62443-2013SR 1.1, SR 1.10, SR 1.11, SR 1.12, SR 1.13, SR 1.2, SR 1.3, SR 1.4, SR 1.5, SR 1.6, SR 1.7, SR 1.8, SR 1.9, SR 2.1, SR 2.2, SR 2.3, SR 2.4, SR 2.5, SR 2.6, SR 2.7
iso27001-2013A.6.1.2, A.7.1.1, A.9.1.2, A.9.2.1, A.9.2.3, A.9.4.1, A.9.4.4, A.9.4.5
nistCM-6(a)
nist-csfPR.AC-4, PR.AC-6, PR.PT-3
osppFIA_UAU.1
os-srgSRG-OS-000080-GPOS-00048
stigidSLES-15-010200
cis1.5.1
anssiR5
stigrefSV-234820r958472_rule
Group   Configure Syslog   Group contains 4 groups and 13 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 SUSE Linux Enterprise 15, rsyslog has replaced ksyslogd as the syslog daemon of choice, and it includes some additional security features such as reliable, connection-oriented (i.e. TCP) transmission of logs, the option to log to database formats, and the encryption of log data en route to a central logging server. This section discusses how to configure rsyslog for best effect, and how to use tools provided with the system to maintain and monitor logs.
Group   Ensure Proper Configuration of Log Files   Group contains 4 rules
[ref]   The file /etc/rsyslog.conf controls where log message are written. These are controlled by lines called rules, which consist of a selector and an action. These rules are often customized depending on the role of the system, the requirements of the environment, and whatever may enable the administrator to most effectively make use of log data. The default rules in SUSE Linux Enterprise 15 are:
*.info;mail.none;authpriv.none;cron.none                /var/log/messages
authpriv.*                                              /var/log/secure
mail.*                                                  -/var/log/maillog
cron.*                                                  /var/log/cron
*.emerg                                                 *
uucp,news.crit                                          /var/log/spooler
local7.*                                                /var/log/boot.log
See the man page rsyslog.conf(5) for more information. Note that the rsyslog daemon can be configured to use a timestamp format that some log processing programs may not understand. If this occurs, edit the file /etc/rsyslog.conf and add or edit the following line:
$ ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

Rule   Ensure Log Files Are Owned By Appropriate Group   [ref]

The group-owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's group owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chgrp root LOGFILE
        
Rationale:
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_groupownership
Identifiers:

CCE-85838-1

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-001314
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
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-10.5.1, Req-10.5.2
cis4.2.1.3
anssiR71
pcidss410.3.2, 10.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Set rsyslog logfile configuration
    facts
  ansible.builtin.set_fact:
    rsyslog_etc_config: /etc/rsyslog.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Get IncludeConfig directive
  ansible.builtin.shell: |
    set -o pipefail
    grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
  register: rsyslog_old_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Get include files directives
  ansible.builtin.shell: |
    set -o pipefail
    awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
  register: rsyslog_new_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Aggregate rsyslog includes
  ansible.builtin.set_fact:
    include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
      }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - List all config files
  ansible.builtin.find:
    paths: '{{ item | dirname }}'
    patterns: '{{ item | basename }}'
    hidden: false
    follow: true
  loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - include_config_output is defined
  register: rsyslog_config_files
  failed_when: false
  changed_when: false
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Extract log files old format
  ansible.builtin.shell: |
    set -o pipefail
    grep -oP '^[^(\s|#|\$)]+[\s]+.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
    awk '{print $NF}' | \
    sed -e 's/^-//' || true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_old
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Extract log files new format
  ansible.builtin.shell: |
    set -o pipefail
    grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
    grep -aoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
    grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
    tr -d "\""|| true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_new
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group - Sum all log files found
  ansible.builtin.set_fact:
    log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
      | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
      | list | flatten | unique }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

- name: Ensure Log Files Are Owned By Appropriate Group -Setup log files attribute
  ansible.builtin.file:
    path: '{{ item }}'
    group: root
    state: file
  loop: '{{ log_files | list | flatten | unique }}'
  failed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85838-1
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_groupownership

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

# List of log file paths to be inspected for correct permissions
# * Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
# * And also the log file paths listed after rsyslog's $IncludeConfig directive
#   (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

# Declare an array to hold the final list of different log file paths
declare -a LOG_FILE_PATHS

# Array to hold all rsyslog config entries
RSYSLOG_CONFIGS=()
RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

# Get full list of files to be checked
# RSYSLOG_CONFIGS may contain globs such as
# /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
# So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
RSYSLOG_CONFIG_FILES=()
for ENTRY in "${RSYSLOG_CONFIGS[@]}"
do
	# If directory, rsyslog will search for config files in recursively.
	# However, files in hidden sub-directories or hidden files will be ignored.
	if [ -d "${ENTRY}" ]
	then
		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
	elif [ -f "${ENTRY}" ]
	then
		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
	else
		echo "Invalid include object: ${ENTRY}"
	fi
done

# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	# From each of these files extract just particular log file path(s), thus:
	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
	# * Ignore empty lines,
	# * Strip quotes and closing brackets from paths.
	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
	# * From the remaining valid rows select only fields constituting a log file path
	# Text file column is understood to represent a log file path if and only if all of the
	# following are met:
	# * it contains at least one slash '/' character,
	# * it is preceded by space
	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
	# Search log file for path(s) only in case it exists!
	if [[ -f "${LOG_FILE}" ]]
	then
		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
		# Since above sed command might return more than one item (delimited by newline), split
		# the particular matches entries into new array specific for this log file
		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
		# items from newly created array for this log file
		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
		# Delete the temporary array
		unset ARRAY_FOR_LOG_FILE
	fi
done

# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")")
done

# Ensure the correct attribute if file exists
FILE_CMD="chgrp"
for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
do
	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
	if [ -z "$LOG_FILE_PATH" ]
	then
		continue
	fi
	$FILE_CMD "root" "$LOG_FILE_PATH"
done

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

Rule   Ensure Log Files Are Owned By Appropriate User   [ref]

The owner of all log files written by rsyslog should be root. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's owner:
$ ls -l LOGFILE
        
If the owner is not root, run the following command to correct this:
$ sudo chown root LOGFILE
        
Rationale:
The log files generated by rsyslog contain valuable information regarding system configuration, user authentication, and other such information. Log files should be protected from unauthorized access.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_ownership
Identifiers:

CCE-85839-9

References:
cis-csc12, 13, 14, 15, 16, 18, 3, 5
cobit5APO01.06, DSS05.04, DSS05.07, DSS06.02
disaCCI-001314
isa-62443-20094.3.3.7.3
isa-62443-2013SR 2.1, SR 5.2
ism0988, 1405
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-10.5.1, Req-10.5.2
cis4.2.1.3
anssiR71
pcidss410.3.2, 10.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Set rsyslog logfile configuration
    facts
  ansible.builtin.set_fact:
    rsyslog_etc_config: /etc/rsyslog.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Get IncludeConfig directive
  ansible.builtin.shell: |
    set -o pipefail
    grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
  register: rsyslog_old_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Get include files directives
  ansible.builtin.shell: |
    set -o pipefail
    awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
  register: rsyslog_new_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Aggregate rsyslog includes
  ansible.builtin.set_fact:
    include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
      }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - List all config files
  ansible.builtin.find:
    paths: '{{ item | dirname }}'
    patterns: '{{ item | basename }}'
    hidden: false
    follow: true
  loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - include_config_output is defined
  register: rsyslog_config_files
  failed_when: false
  changed_when: false
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Extract log files old format
  ansible.builtin.shell: |
    set -o pipefail
    grep -oP '^[^(\s|#|\$)]+[\s]+.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
    awk '{print $NF}' | \
    sed -e 's/^-//' || true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_old
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Extract log files new format
  ansible.builtin.shell: |
    set -o pipefail
    grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
    grep -aoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
    grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
    tr -d "\""|| true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_new
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User - Sum all log files found
  ansible.builtin.set_fact:
    log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
      | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
      | list | flatten | unique }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

- name: Ensure Log Files Are Owned By Appropriate User -Setup log files attribute
  ansible.builtin.file:
    path: '{{ item }}'
    owner: root
    state: file
  loop: '{{ log_files | list | flatten | unique }}'
  failed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85839-9
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.2
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_ownership

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

# List of log file paths to be inspected for correct permissions
# * Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
# * And also the log file paths listed after rsyslog's $IncludeConfig directive
#   (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

# Declare an array to hold the final list of different log file paths
declare -a LOG_FILE_PATHS

# Array to hold all rsyslog config entries
RSYSLOG_CONFIGS=()
RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

# Get full list of files to be checked
# RSYSLOG_CONFIGS may contain globs such as
# /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
# So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
RSYSLOG_CONFIG_FILES=()
for ENTRY in "${RSYSLOG_CONFIGS[@]}"
do
	# If directory, rsyslog will search for config files in recursively.
	# However, files in hidden sub-directories or hidden files will be ignored.
	if [ -d "${ENTRY}" ]
	then
		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
	elif [ -f "${ENTRY}" ]
	then
		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
	else
		echo "Invalid include object: ${ENTRY}"
	fi
done

# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	# From each of these files extract just particular log file path(s), thus:
	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
	# * Ignore empty lines,
	# * Strip quotes and closing brackets from paths.
	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
	# * From the remaining valid rows select only fields constituting a log file path
	# Text file column is understood to represent a log file path if and only if all of the
	# following are met:
	# * it contains at least one slash '/' character,
	# * it is preceded by space
	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
	# Search log file for path(s) only in case it exists!
	if [[ -f "${LOG_FILE}" ]]
	then
		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
		# Since above sed command might return more than one item (delimited by newline), split
		# the particular matches entries into new array specific for this log file
		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
		# items from newly created array for this log file
		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
		# Delete the temporary array
		unset ARRAY_FOR_LOG_FILE
	fi
done

# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")")
done

# Ensure the correct attribute if file exists
FILE_CMD="chown"
for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
do
	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
	if [ -z "$LOG_FILE_PATH" ]
	then
		continue
	fi
	$FILE_CMD "root" "$LOG_FILE_PATH"
done

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

Rule   Ensure System Log Files Have Correct Permissions   [ref]

The file permissions for all log files written by rsyslog should be set to 640, or more restrictive. These log files are determined by the second part of each Rule line in /etc/rsyslog.conf and typically all appear in /var/log. For each log file LOGFILE referenced in /etc/rsyslog.conf, run the following command to inspect the file's permissions:
$ ls -l LOGFILE
        
If the permissions are not 640 or more restrictive, run the following command to correct this:
$ sudo chmod 640 LOGFILE
        
"
Rationale:
Log files can contain valuable information regarding system configuration. If the system log files are not protected unauthorized users could change the logged data, eliminating their forensic value.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_files_permissions
Identifiers:

CCE-85837-3

References:
disaCCI-001314
ism0988, 1405
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)
pcidssReq-10.5.1, Req-10.5.2
cis4.2.1.3
anssiR71
pcidss410.3.1, 10.3

Complexity:low
Disruption:medium
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Set rsyslog logfile configuration
    facts
  ansible.builtin.set_fact:
    rsyslog_etc_config: /etc/rsyslog.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Get IncludeConfig directive
  ansible.builtin.shell: |
    set -o pipefail
    grep -e '$IncludeConfig' {{ rsyslog_etc_config }} | cut -d ' ' -f 2 || true
  register: rsyslog_old_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Get include files directives
  ansible.builtin.shell: |
    set -o pipefail
    awk '/)/{f=0} /include\(/{f=1} f{ nf=gensub("^(include\\(|\\s*)file=\"(\\S+)\".*","\\2",1); if($0!=nf){ print nf }}' {{ rsyslog_etc_config }} || true
  register: rsyslog_new_inc
  changed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Aggregate rsyslog includes
  ansible.builtin.set_fact:
    include_config_output: '{{ rsyslog_old_inc.stdout_lines + rsyslog_new_inc.stdout_lines
      }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_old_inc is not skipped and rsyslog_new_inc is not skipped
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - List all config files
  ansible.builtin.find:
    paths: '{{ item | dirname }}'
    patterns: '{{ item | basename }}'
    hidden: false
    follow: true
  loop: '{{ include_config_output | list + [rsyslog_etc_config] }}'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - include_config_output is defined
  register: rsyslog_config_files
  failed_when: false
  changed_when: false
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Extract log files old format
  ansible.builtin.shell: |
    set -o pipefail
    grep -oP '^[^(\s|#|\$)]+[\s]+.*[\s]+-?(/+[^:;\s]+);*\.*$' {{ item.1.path }} | \
    awk '{print $NF}' | \
    sed -e 's/^-//' || true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_old
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Extract log files new format
  ansible.builtin.shell: |
    set -o pipefail
    grep -ozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" {{ item.1.path }} | \
    grep -aoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)" | \
    grep -oE "\"([/[:alnum:][:punct:]]*)\"" | \
    tr -d "\""|| true
  loop: '{{ rsyslog_config_files.results | default([]) | subelements(''files'') }}'
  register: log_files_new
  changed_when: false
  when:
  - '"kernel-default" in ansible_facts.packages'
  - rsyslog_config_files is not skipped
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions - Sum all log files found
  ansible.builtin.set_fact:
    log_files: '{{ log_files_new.results | map(attribute=''stdout_lines'') | list
      | flatten | unique + log_files_old.results | map(attribute=''stdout_lines'')
      | list | flatten | unique }}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

- name: Ensure System Log Files Have Correct Permissions -Setup log files attribute
  ansible.builtin.file:
    path: '{{ item }}'
    mode: '0640'
    state: file
  loop: '{{ log_files | list | flatten | unique }}'
  failed_when: false
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-85837-3
  - NIST-800-53-AC-6(1)
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.5.1
  - PCI-DSS-Req-10.5.2
  - PCI-DSSv4-10.3
  - PCI-DSSv4-10.3.1
  - configure_strategy
  - low_complexity
  - medium_disruption
  - medium_severity
  - no_reboot_needed
  - rsyslog_files_permissions

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

# List of log file paths to be inspected for correct permissions
# * Primarily inspect log file paths listed in /etc/rsyslog.conf
RSYSLOG_ETC_CONFIG="/etc/rsyslog.conf"
# * And also the log file paths listed after rsyslog's $IncludeConfig directive
#   (store the result into array for the case there's shell glob used as value of IncludeConfig)
readarray -t OLD_INC < <(grep -e "\$IncludeConfig[[:space:]]\+[^[:space:];]\+" /etc/rsyslog.conf | cut -d ' ' -f 2)
readarray -t RSYSLOG_INCLUDE_CONFIG < <(for INCPATH in "${OLD_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)
readarray -t NEW_INC < <(sed -n '/^\s*include(/,/)/Ip' /etc/rsyslog.conf | sed -n 's@.*file\s*=\s*"\([/[:alnum:][:punct:]]*\)".*@\1@Ip')
readarray -t RSYSLOG_INCLUDE < <(for INCPATH in "${NEW_INC[@]}"; do eval printf '%s\\n' "${INCPATH}"; done)

# Declare an array to hold the final list of different log file paths
declare -a LOG_FILE_PATHS

# Array to hold all rsyslog config entries
RSYSLOG_CONFIGS=()
RSYSLOG_CONFIGS=("${RSYSLOG_ETC_CONFIG}" "${RSYSLOG_INCLUDE_CONFIG[@]}" "${RSYSLOG_INCLUDE[@]}")

# Get full list of files to be checked
# RSYSLOG_CONFIGS may contain globs such as
# /etc/rsyslog.d/*.conf /etc/rsyslog.d/*.frule
# So, loop over the entries in RSYSLOG_CONFIGS and use find to get the list of included files.
RSYSLOG_CONFIG_FILES=()
for ENTRY in "${RSYSLOG_CONFIGS[@]}"
do
	# If directory, rsyslog will search for config files in recursively.
	# However, files in hidden sub-directories or hidden files will be ignored.
	if [ -d "${ENTRY}" ]
	then
		readarray -t FINDOUT < <(find "${ENTRY}" -not -path '*/.*' -type f)
		RSYSLOG_CONFIG_FILES+=("${FINDOUT[@]}")
	elif [ -f "${ENTRY}" ]
	then
		RSYSLOG_CONFIG_FILES+=("${ENTRY}")
	else
		echo "Invalid include object: ${ENTRY}"
	fi
done

# Browse each file selected above as containing paths of log files
# ('/etc/rsyslog.conf' and '/etc/rsyslog.d/*.conf' in the default configuration)
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	# From each of these files extract just particular log file path(s), thus:
	# * Ignore lines starting with space (' '), comment ('#"), or variable syntax ('$') characters,
	# * Ignore empty lines,
	# * Strip quotes and closing brackets from paths.
	# * Ignore paths that match /dev|/etc.*\.conf, as those are paths, but likely not log files
	# * From the remaining valid rows select only fields constituting a log file path
	# Text file column is understood to represent a log file path if and only if all of the
	# following are met:
	# * it contains at least one slash '/' character,
	# * it is preceded by space
	# * it doesn't contain space (' '), colon (':'), and semicolon (';') characters
	# Search log file for path(s) only in case it exists!
	if [[ -f "${LOG_FILE}" ]]
	then
		NORMALIZED_CONFIG_FILE_LINES=$(sed -e "/^[#|$]/d" "${LOG_FILE}")
		LINES_WITH_PATHS=$(grep '[^/]*\s\+\S*/\S\+$' <<< "${NORMALIZED_CONFIG_FILE_LINES}")
		FILTERED_PATHS=$(awk '{if(NF>=2&&($NF~/^\//||$NF~/^-\//)){sub(/^-\//,"/",$NF);print $NF}}' <<< "${LINES_WITH_PATHS}")
		CLEANED_PATHS=$(sed -e "s/[\"')]//g; /\\/etc.*\.conf/d; /\\/dev\\//d" <<< "${FILTERED_PATHS}")
		MATCHED_ITEMS=$(sed -e "/^$/d" <<< "${CLEANED_PATHS}")
		# Since above sed command might return more than one item (delimited by newline), split
		# the particular matches entries into new array specific for this log file
		readarray -t ARRAY_FOR_LOG_FILE <<< "$MATCHED_ITEMS"
		# Concatenate the two arrays - previous content of $LOG_FILE_PATHS array with
		# items from newly created array for this log file
		LOG_FILE_PATHS+=("${ARRAY_FOR_LOG_FILE[@]}")
		# Delete the temporary array
		unset ARRAY_FOR_LOG_FILE
	fi
done

# Check for RainerScript action log format which might be also multiline so grep regex is a bit
# curly:
# extract possibly multiline action omfile expressions
# extract File="logfile" expression
# match only "logfile" expression
for LOG_FILE in "${RSYSLOG_CONFIG_FILES[@]}"
do
	ACTION_OMFILE_LINES=$(grep -iozP "action\s*\(\s*type\s*=\s*\"omfile\"[^\)]*\)" "${LOG_FILE}")
	OMFILE_LINES=$(echo "${ACTION_OMFILE_LINES}"| grep -iaoP "\bFile\s*=\s*\"([/[:alnum:][:punct:]]*)\"\s*\)")
	LOG_FILE_PATHS+=("$(echo "${OMFILE_LINES}"| grep -oE "\"([/[:alnum:][:punct:]]*)\""|tr -d "\"")")
done

# Ensure the correct attribute if file exists
FILE_CMD="chmod"
for LOG_FILE_PATH in "${LOG_FILE_PATHS[@]}"
do
	# Sanity check - if particular $LOG_FILE_PATH is empty string, skip it from further processing
	if [ -z "$LOG_FILE_PATH" ]
	then
		continue
	fi
	$FILE_CMD "0640" "$LOG_FILE_PATH"
done

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

Rule   Ensure logging is configured   [ref]

The /etc/rsyslog.conf and /etc/rsyslog.d/*.conf files specifies rules for logging and which files are to be used to log certain classes of messages.
Warning:  This rule does not come with remediation as there is no one way to solve the problem, and the requirement from CIS specification does not require one particular way, but persuades the system administrator to perform configuration suitable for the specific environment. This also means that the OVAL check is too generic, and the user most probably should perform additional manual verification.
Rationale:
A great deal of important security-related information is sent via rsyslog (e.g., successful and failed su attempts, failed login attempts, root login attempts, etc.).
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_rsyslog_logging_configured
Identifiers:

CCE-92497-7

References:
cis4.2.1.4
Group   systemd-journald   Group contains 3 rules
[ref]   systemd-journald is a system service that collects and stores logging data. It creates and maintains structured, indexed journals based on logging information that is received from a variety of sources. For more information on systemd-journald and additional systemd-journald configuration options, see https://systemd.io/.

Rule   Ensure journald is configured to compress large log files   [ref]

The journald system can compress large log files to avoid fill the system disk.
Rationale:
Log files that are not properly compressed run the risk of growing so large that they fill up the log partition. Valuable logging information could be lost if the log partition becomes full.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_journald_compress
Identifiers:

CCE-91377-2

References:
cis4.2.2.2

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91377-2
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Search for a section
    in files
  ansible.builtin.find:
    paths: '{{item.path}}'
    patterns: '{{item.pattern}}'
    contains: ^\s*\[Journal\]
    read_whole_file: true
    use_regex: true
  register: systemd_dropin_files_with_section
  loop:
  - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
    pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
  - path: /etc/systemd/journal.d
    pattern: .*\.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91377-2
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Count number of
    files which contain the correct section
  ansible.builtin.set_fact:
    count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
      | map(attribute=''matched'') | list | map(''int'') | sum}}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91377-2
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Add missing configuration
    to correct section
  ini_file:
    path: '{{item}}'
    section: Journal
    option: Compress
    value: 'yes'
    state: present
    no_extra_spaces: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int > 0
  loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
    | map(attribute=''path'') | list }}'
  tags:
  - CCE-91377-2
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to compress large log files - Add configuration
    to new remediation file
  ini_file:
    path: /etc/systemd/journal.d/complianceascode_hardening.conf
    section: Journal
    option: Compress
    value: 'yes'
    state: present
    no_extra_spaces: true
    create: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int == 0
  tags:
  - CCE-91377-2
  - journald_compress
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

found=false

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

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Journal\]([^\n\[]*\n+)+?[[:space:]]*Compress" "$f"; then

            sed -i "s/Compress[^(\n)]*/Compress=yes/" "$f"

            found=true

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

            sed -i "/[[:space:]]*\[Journal\]/a Compress=yes" "$f"

            found=true
    fi
done

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

    echo -e "[Journal]\nCompress=yes" >> "$file"

fi

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

Rule   Ensure journald is configured to send logs to rsyslog   [ref]

Data from journald may be stored in volatile memory or persisted locally. Utilities exist to accept remote export of journald logs.
Rationale:
Storing log data on a remote host protects log integrity from local attacks. If an attacker gains root access on the local system, they could tamper with or remove log data that is stored on the local system.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_journald_forward_to_syslog
Identifiers:

CCE-91376-4

References:
cis4.2.2.1

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91376-4
  - journald_forward_to_syslog
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to send logs to rsyslog - Search for a section
    in files
  ansible.builtin.find:
    paths: '{{item.path}}'
    patterns: '{{item.pattern}}'
    contains: ^\s*\[Journal\]
    read_whole_file: true
    use_regex: true
  register: systemd_dropin_files_with_section
  loop:
  - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
    pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
  - path: /etc/systemd/journal.d
    pattern: .*\.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91376-4
  - journald_forward_to_syslog
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to send logs to rsyslog - Count number of files
    which contain the correct section
  ansible.builtin.set_fact:
    count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
      | map(attribute=''matched'') | list | map(''int'') | sum}}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91376-4
  - journald_forward_to_syslog
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to send logs to rsyslog - Add missing configuration
    to correct section
  ini_file:
    path: '{{item}}'
    section: Journal
    option: ForwardToSyslog
    value: 'yes'
    state: present
    no_extra_spaces: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int > 0
  loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
    | map(attribute=''path'') | list }}'
  tags:
  - CCE-91376-4
  - journald_forward_to_syslog
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to send logs to rsyslog - Add configuration
    to new remediation file
  ini_file:
    path: /etc/systemd/journal.d/complianceascode_hardening.conf
    section: Journal
    option: ForwardToSyslog
    value: 'yes'
    state: present
    no_extra_spaces: true
    create: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int == 0
  tags:
  - CCE-91376-4
  - journald_forward_to_syslog
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

found=false

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

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Journal\]([^\n\[]*\n+)+?[[:space:]]*ForwardToSyslog" "$f"; then

            sed -i "s/ForwardToSyslog[^(\n)]*/ForwardToSyslog=yes/" "$f"

            found=true

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

            sed -i "/[[:space:]]*\[Journal\]/a ForwardToSyslog=yes" "$f"

            found=true
    fi
done

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

    echo -e "[Journal]\nForwardToSyslog=yes" >> "$file"

fi

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

Rule   Ensure journald is configured to write log files to persistent disk   [ref]

The journald system may store log files in volatile memory or locally on disk. If the logs are only stored in volatile memory they will we lost upon reboot.
Rationale:
Log files contain valuable data and need to be persistent to aid in possible investigations.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_journald_storage
Identifiers:

CCE-91378-0

References:
cis4.2.2.3

Complexity:low
Disruption:low
Reboot:false
Strategy:restrict
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-91378-0
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Search
    for a section in files
  ansible.builtin.find:
    paths: '{{item.path}}'
    patterns: '{{item.pattern}}'
    contains: ^\s*\[Journal\]
    read_whole_file: true
    use_regex: true
  register: systemd_dropin_files_with_section
  loop:
  - path: '{{ ''/etc/systemd/journald.conf'' | dirname }}'
    pattern: '{{ ''/etc/systemd/journald.conf'' | basename | regex_escape }}'
  - path: /etc/systemd/journal.d
    pattern: .*\.conf
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91378-0
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Count
    number of files which contain the correct section
  ansible.builtin.set_fact:
    count_of_systemd_dropin_files_with_section: '{{systemd_dropin_files_with_section.results
      | map(attribute=''matched'') | list | map(''int'') | sum}}'
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-91378-0
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Add
    missing configuration to correct section
  ini_file:
    path: '{{item}}'
    section: Journal
    option: Storage
    value: persistent
    state: present
    no_extra_spaces: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int > 0
  loop: '{{systemd_dropin_files_with_section.results | sum(attribute=''files'', start=[])
    | map(attribute=''path'') | list }}'
  tags:
  - CCE-91378-0
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

- name: Ensure journald is configured to write log files to persistent disk - Add
    configuration to new remediation file
  ini_file:
    path: /etc/systemd/journal.d/complianceascode_hardening.conf
    section: Journal
    option: Storage
    value: persistent
    state: present
    no_extra_spaces: true
    create: true
  when:
  - '"kernel-default" in ansible_facts.packages'
  - count_of_systemd_dropin_files_with_section | int == 0
  tags:
  - CCE-91378-0
  - journald_storage
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy

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

found=false

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

    # find key in section and change value
    if grep -qzosP "[[:space:]]*\[Journal\]([^\n\[]*\n+)+?[[:space:]]*Storage" "$f"; then

            sed -i "s/Storage[^(\n)]*/Storage=persistent/" "$f"

            found=true

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

            sed -i "/[[:space:]]*\[Journal\]/a Storage=persistent" "$f"

            found=true
    fi
done

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

    echo -e "[Journal]\nStorage=persistent" >> "$file"

fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
Group   Ensure All Logs are Rotated by logrotate   Group contains 3 rules
[ref]   Edit the file /etc/logrotate.d/syslog. Find the first line, which should look like this (wrapped for clarity):
/var/log/messages /var/log/secure /var/log/maillog /var/log/spooler \
  /var/log/boot.log /var/log/cron {
Edit this line so that it contains a one-space-separated listing of each log file referenced in /etc/rsyslog.conf.

All logs in use on a system must be rotated regularly, or the log files will consume disk space over time, eventually interfering with system operation. The file /etc/logrotate.d/syslog is the configuration file used by the logrotate program to maintain all log files written by syslog. By default, it rotates logs weekly and stores four archival copies of each log. These settings can be modified by editing /etc/logrotate.conf, but the defaults are sufficient for purposes of this guide.

Note that logrotate is run nightly by the cron job /etc/cron.daily/logrotate. If particularly active logs need to be rotated more often than once a day, some other mechanism must be used.

Rule   Ensure logrotate is Installed   [ref]

logrotate is installed by default. The logrotate package can be installed with the following command:
 $ sudo zypper install logrotate
Rationale:
The logrotate package provides the logrotate services.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_package_logrotate_installed
Identifiers:

CCE-92561-0

References:
cis-csc1, 14, 15, 16, 3, 5, 6
cobit5APO11.04, BAI03.05, 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
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
pcidssReq-10.7
cis4.2.4
anssiR71
pcidss410.5.1, 10.5

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

class install_logrotate {
  package { 'logrotate':
    ensure => 'installed',
  }
}

Complexity:low
Disruption:low
Reboot:false
Strategy:enable
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-92561-0
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_logrotate_installed

- name: Ensure logrotate is installed
  package:
    name: logrotate
    state: present
  when: '"kernel-default" in ansible_facts.packages'
  tags:
  - CCE-92561-0
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - PCI-DSSv4-10.5
  - PCI-DSSv4-10.5.1
  - enable_strategy
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - package_logrotate_installed


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

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

zypper install -y "logrotate"

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

Rule   Ensure Logrotate Runs Periodically   [ref]

The logrotate utility allows for the automatic rotation of log files. The frequency of rotation is specified in /etc/logrotate.conf, which triggers a cron task or a timer. To configure logrotate to run daily, add or correct the following line in /etc/logrotate.conf:
# rotate log files frequency
daily
Rationale:
Log files that are not properly rotated run the risk of growing so large that they fill up the /var/log partition. Valuable logging information could be lost if the /var/log partition becomes full.
Severity: 
medium
Rule ID:xccdf_org.ssgproject.content_rule_ensure_logrotate_activated
Identifiers:

CCE-85850-6

References:
cis-csc1, 14, 15, 16, 3, 5, 6
cobit5APO11.04, BAI03.05, 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
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
pcidssReq-10.7
cis4.2.4
anssiR71

Complexity:low
Disruption:low
Reboot:false
Strategy:configure
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CCE-85850-6
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - configure_strategy
  - ensure_logrotate_activated
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Configure daily log rotation in /etc/logrotate.conf
  lineinfile:
    create: true
    dest: /etc/logrotate.conf
    regexp: ^daily$
    line: daily
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"logrotate" in ansible_facts.packages'
  tags:
  - CCE-85850-6
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - configure_strategy
  - ensure_logrotate_activated
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Make sure daily log rotation setting is not overriden in /etc/logrotate.conf
  lineinfile:
    create: false
    dest: /etc/logrotate.conf
    regexp: ^[\s]*(weekly|monthly|yearly)$
    state: absent
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"logrotate" in ansible_facts.packages'
  tags:
  - CCE-85850-6
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - configure_strategy
  - ensure_logrotate_activated
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

- name: Enable timer logrotate
  systemd:
    name: logrotate.timer
    enabled: 'yes'
    state: started
    masked: 'no'
  when:
  - '"kernel-default" in ansible_facts.packages'
  - '"logrotate" in ansible_facts.packages'
  tags:
  - CCE-85850-6
  - NIST-800-53-CM-6(a)
  - PCI-DSS-Req-10.7
  - configure_strategy
  - ensure_logrotate_activated
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed

# Remediation is applicable only in certain platforms
if rpm --quiet -q kernel-default && { rpm --quiet -q logrotate; }; then

LOGROTATE_CONF_FILE="/etc/logrotate.conf"

SYSTEMCTL_EXEC='/usr/bin/systemctl'


# daily rotation is configured
grep -q "^daily$" $LOGROTATE_CONF_FILE|| echo "daily" &