diff --git a/README.md b/README.md index 4de70bb..1affdae 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # cis_security -A role to implement Center for Internet Security (CIS) controls for RHEL (7-8) and RHEL clones (Oracle, CentOS), recent Fedora (31-32), SLES 15, and Ubuntu 18.04 / 20.04 LTS and certain Windows servers. +A role to implement Center for Internet Security (CIS) controls for RHEL (7-9) and RHEL clones (Oracle, CentOS), recent Fedora (31-32), SLES 15, and Ubuntu \[18-22\].04 LTS and certain Windows servers. -### Introduction +## Introduction The [Center for Internet Security](https://www.cisecurity.org/) provides a set of security benchmarks for operating systems designed to decrease the vulnerability vectors of a system. @@ -20,7 +20,8 @@ Benchmark Versions: | Operating System | OS Benchmark version | | -----------------|--------------------- | | RHEL 7 | v2.2.0 | -| RHEL 8 | v1.0.1 | +| RHEL 8 | v2.0.1 | +| RHEL 9 | V1.0.0 | | CentOS 7 | v2.2.0 | | CentOS 8 | v1.0.0 | | Fedora 31 | \(Fedora 28\) v1.1.0 | @@ -30,6 +31,7 @@ Benchmark Versions: | SUSE Linux Enterprise 15 SP1 | \(SUSE Linux Enterprise 12\) v2.1.0 | | Ubuntu 18.04 LTS | v2.0.1 | | Ubuntu 20.04 LTS | \(Ubuntu 18.04 LTS\) v2.0.1 | +| Ubuntu 22.04 LTS | v1.0.0 | | Windows Server 2019 | v1.8.1 | | Windows 10 | \(Windows Server 2019\) v1.8.1 | @@ -37,14 +39,17 @@ Benchmark Versions: been made to update the controls to work with the newer operating systems. Older versions of the benchmarks are listed in parenthesis. - SUSE Linux Enterprise 15 SP1 uses the RHEL 7 task file since their controls are so similar. If you want to exclude a SUSE tag, make sure you use the associated RHEL 7 tag number if they are different. Tags can be found in the appropriate controls_list file found in the docs directory. -### Requirements +## Requirements + To implement the collection correctly, you will require the following Control machine: -- Ansible 2.9+ + +- Ansible 2.11+ - Machine connected to a package repository source (Satellite or yum repo) Target machine: + - SSH connection with prviiledge escalation on Linux machines. - Python interpreter - WinRM connection with user with admin priviledge for Windows. Alternatively you can use an SSH connection. @@ -55,13 +60,14 @@ Some of the Ansible modules that are used require Ansible 2.7 and newer. For most of the collection to work, you will need to have a package repo where you can install packages for the target machine. Registering with Satellite, a package repository, SCM, or a local package collection is recommended before using this, unless you exclude any tags that install packages. -### Use and Care +## Use and Care + The collection is designed to run on the machines in the chart above. It may run on other Red Hat and Ubuntu deriviatives, but it has not been tested on them. Upon initiation, the collection will automatically detect the OS and run the appropriate task list. As the role runs, you will see an output listing the control number and a brief description of the task being performed (or skipped): -``` +```bash TASK [security-rollup : 1.7.1.3 - Set SELinux policy to targeted] ****************************** ok: [192.168.122.252] ``` @@ -69,16 +75,19 @@ ok: [192.168.122.252] The controls are implemented as Ansible tags. By default all tags are run on a given system. To disable a tag from running, run the playbook with the tag excluded (--skip-tags "x.y.z"): -``` +```bash ansible-playbook -i --skip-tags "x.y.z" ``` + Multiple tags can be listed, separated by commas: -``` + +```bash ansible-playbook -i --skip-tags "x.y.z,a.b.c" ``` + Note: Some automation tasks handle multiple controls. In the role you may see something like this: -``` +```yaml - name: 6.1.[2,4] - Ensure permissions on /etc/passwd /etc/group file: path: /etc/{{item}} @@ -92,12 +101,14 @@ Note: Some automation tasks handle multiple controls. In the role you may see so - 6.1.2 - 6.1.4 ``` -* In this control, two tags are being processed, '6.1.2' and '6.1.4' if you want this control to not + +- In this control, two tags are being processed, '6.1.2' and '6.1.4' if you want this control to not run, you must exclude both tags: -``` +```bash ansible-playbook -i --skip-tags "6.1.2,6.1.4" ``` + Some controls are surrouned by Ansible blocks that themselves have tags. Excluding the tag that applies to the block will exclude all of the tasks inside of the block. If the block's tag is **not** excluded, then individual tasks inside of the block can be excluded by excluding their tags. @@ -110,18 +121,20 @@ tasks, or set values. These are explained and given default values in the **role file. Do not set these values in that file, but create and include your own variable file to override the defaults or set them as host variables. -### Idempotency +## Idempotency + Every effort has been made to make the controls idempotent, however some Ansible modules do not have the ability to measure every need as currently written and shell or command has been utilized to implement controls. This has the effect of bringing down the quality score on Ansible Galaxy, but the roles can be run multiple times without fear of breaking. -### Learning Tool +## Learning Tool + A secondary purpose of this collection is to show numerous ways that Ansible can be used to manage systems with various modules. The first time a module is used it is commented on many times to explain what the module is doing. Other times you may see something like the following: -``` +```yaml - name: 5.4.4 - Ensure umask is set replace: path: "{{ item }}" @@ -146,12 +159,13 @@ to explain what the module is doing. Other times you may see something like the tags: - 5.4.5 ``` + Both of these tasks manipulate the same file in the same way. They could have been written with the same module, even in the same task with a loop, but here it illustrates different ways files can be manipuldated with modules. +## Change Log -### Change Log - 1/20/2020 - dsglaser@gmail.com - Initial creation - 1/22/2020 - dsglaser@gmail.com - Added enhanced selinux controls - 2/18/2020 - dsglaser@gmail.com - Added support for Ubuntu 18.04 LTS, added RHEL clone links diff --git a/docs/controls_list.md b/docs/controls_list.md index d865696..7d53d98 100644 --- a/docs/controls_list.md +++ b/docs/controls_list.md @@ -1,265 +1,338 @@ -Below are the tags used in the CIS roles on Linux Machines. +# CIS Security -| RHEL 8 / Fedora 31 / CentOS 8 / Oracle 8 | RHEL 7 / Centos 7 / Oracle 7 / SLES 15 | Ubuntu 18.04 / 20.04 | Control Description | Notes | -| -----------------|--------------------- | -----------------|--------------------- | --------------------- | -| 1.1.1.1 | 1.1.1.1 | 1.1.1.1 | Remove cramfs | -| | 1.1.1.2 | 1.1.1.2 | Remove freevxfs -| | 1.1.1.3 | 1.1.1.3 | Remove jffs2 -| | 1.1.1.4 | 1.1.1.4 | Remove hfs -| | 1.1.1.5 | 1.1.1.5 | Remove hfsplus -| 1.1.1.2 | 1.1.1.8 | 1.1.1.8 | Remove vfat -| 1.1.1.3 | 1.1.1.6 | 1.1.1.6 | Remove squashfs -| 1.1.1.4 | 1.1.1.7 | 1.1.1.7 | Remove udf -| | 1.1.2 | | Report if /tmp is not on a separate partition | a) If report comes back saying partitions are not separate, the no\[dev,suid,exec\] checks aren't run b) On a failed control, simply prints a notification to the user | -| 1.1.2 | | 1.1.2 | Ensure tmpfs is configured -| 1.1.3 | 1.1.4 | 1.1.3 | Ensure nodev option on /tmp partition -| 1.1.4 | 1.1.5 | 1.1.4 | Ensure nosuid option set on /tmp partition -| 1.1.5 | 1.1.3 | 1.1.5 | Ensure noexec option set on /tmp partition -| 1.1.6 | 1.1.10 | 1.1.6 | Report if /var is not on a separate partition -| 1.1.7 | 1.1.11 | 1.1.7 | Report if /var/tmp is not on a separate partition a) If report comes back saying partitions are not separate, the no\[dev,suid,exec\] checks aren't run b) On a failed control, simply prints a notification to the user | -| 1.1.8 | 1.1.13 | 1.1.8 | Ensure nodev option on /var/tmp partition -| 1.1.9 | 1.1.14 | 1.1.9 | Ensure nosuid option set on /var/tmp partition -| 1.1.10 | 1.1.12 | 1.1.10 | Ensure noexec option set on /var/tmp partition -| 1.1.11 | 1.1.15 | 1.1.11 | Report if /var/log is not on a separate partition | On a failed control, simply prints a notification to the user | -| 1.1.12 | 1.1.16 | 1.1.12 | Report if /var/log/audit is not on a separate partition | On a failed control, simply prints a notification to the user | -| 1.1.13 | 1.1.17 | 1.1.13 | Report if /home is not on a separate partition | On a failed control, simply prints a notification to the user | -| 1.1.14 | 1.1.18 | 1.1.14 | Ensure nodev option on /home partition |skipped: environment dependent | -| 1.1.15 | 1.1.8 | 1.1.15 | Report if /dev/shm does not have nodev set -u -| 1.1.16 | 1.1.9 | 1.1.16 | Report if /dev/shm does not have nosuid set -| 1.1.17 | 1.1.7 | 1.1.17 | Report if /dev/shm does not have noexec set -| 1.1.18 | 1.1.20 | 1.1.18 | Ensure nodev option set on removable media | skipped: environment dependent| -| 1.1.19 | 1.1.21 | 1.1.19 | Ensure nosuid option set on removable media |skipped: environment dependent| -| 1.1.20 | 1.1.19 | 1.1.20 | Ensure noexec option set on removable media |skipped: environment dependent| -| 1.1.21 | 1.1.22 | 1.1.21 | Ensure sticky bit on world writable directories -| 1.1.22 | 1.1.23 | 1.1.22 | Disable automounting -| 1.1.23 | 1.1.24 | 1.1.23 | Disable USB storage module -| 1.2.1 | 1.2.2 | 1.2.1 | Ensure system is configured for updates |skipped: environment dependent| -| 1.2.2 | 1.2.5 | | Disable the rhnsd Daemon | RHEL control only | -| 1.2.3 | 1.2.1,3 | 1.2.2 | Ensure gpg keys are configured | gpgcheck set to yes. 1.2.2 on SLES 15, but skipped | -| 1.2.4 | 1.2.2 | 1.2.2 | Ensure gpgcheck is globally activated. Missing on SLES 15 benchmark -| 1.2.5 | 1.2.4 | | Ensure machine is registerd with Red Hat |skipped: environment dependent| -| 1.3.1 | | 1.3.1 | Ensure sudo is installed -| 1.3.2 | | 1.3.2 | Ensure sudo commands use pty -| 1.3.3 | | 1.3.3 | Ensure sudo log file exists -| 1.4.0 | 1.3.0 | 1.4.0 | Install and configure filesystem integrity checking w/AIDE -| 1.4.1 | 1.3.1 | 1.4.1 | Ensure aide is installed -| 1.4.2 | 1.3.2 | 1.4.2 | Ensure File integrity is regularly checked -| 1.5.1 | 1.4.2 | 1.5.1 | Set permissions on grub bootloader files -| 1.5.2 | 1.4.1 | 1.5.2 | Ensure bootloader password is set |skipped: environment dependent| -| 1.5.3 | 1.4.3 | 1.5.3 | Set single user password | if root password is not set, sets root password before setting up secure single user mode. Uses root_password variable. See [Using vaults with playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html#playbooks-vault) for information on how to secure it| -| 1.6.1 | 1.5.1 | 1.6.4 | Ensure core dumps are restricted -| | 1.5.2 | 1.6.1 | Ensure XD/NX support is enabled -| 1.6.2 | 1.5.3 | 1.6.2 | Ensure address space layout reandomization (ASLR) is enabled -| | 1.5.4 | | Ensure prelink is not installed -| 1.6.0 | 1.6.0 \(SLES: skipped\) | | | Configure SELinux | SLES supports SELinux, but does not provide a policy, so enabling it will crash the system, so we are skipping it on SLES) -| 1.7.1.1 | 1.6.1.1 | | Ensure SELinux is installed -| 1.7.1.2 | 1.6.1.2 | | Ensure SELinux is not disabled in bootloader configuration -| 1.7.1.3 | 1.6.1.3 | | Set SELinux policy -| 1.7.1.4 | 1.6.1.2 | | Set SELinux state -| 1.7.1.5 | 1.6.1.6 | | Ensure no unconfined processes exist -| 1.7.1.6 | 1.6.1.7 | | Remove setroubleshoot -| 1.7.1.7 | 1.6.1.8 | | Remove MCS Translation Service -| | | 1.7.0 | Install and Configure AppArmor -| | | 1.7.1.1 | Ensure AppArmor is installed -| | | 1.7.1.2 | Ensure AppArmor is not disabled in bootloader configuration -| | | 1.7.1.3 | Ensure AppArmor profiles are in inforce or complain mode -| | \(SLES 1.6.2.2\) | 1.7.1.4 | Ensure AppArmor profiles are enforcing | SLES only control in the RHEL 7 file, use 1.6.2.2 for the tag number -| | \(SLES 1.6.3\) | | Ensure SELinux or AppArmor are installed | SLES only control in the RHEL 7 file, use 1.6.3 for the tag number -| 1.8.1.1 | 1.7.1.1 | 1.8.1.1 | Install motd banners -| 1.8.1.2 | 1.7.2 | 1.8.1.2 | Install issue banners -| 1.8.1.3 | 1.7.3 | 1.8.1.3 | Install issue.net banners -| 1.8.1.4 | 1.7.4 | 1.8.1.4 | Ensure permissions on /etc/motd are configured -| 1.8.1.5 | 1.7.5 | 1.8.1.5 | Ensure permissions on /etc/issue are configured -| 1.8.1.6 | 1.7.6 | 1.8.1.6 | Ensure permissions on /etc/issue.net are configured -| | 1.8.1 | | Ensure GNOME Display Manager is removed -| 1.8.2 | 1.8.2 | 1.8.2 | Ensure GDM banner set up -| | 1.8.3 | | Ensure last logged in user display is disabled -| 1.9.0 | | 1.9.0 | Ensure updated system | First control to be run chronologically in order to make sure things are in the same state for the rest of the play -| 1.10.0 | | | Ensure crypto policy is not legacy | set to what the crypto_policy variable is set to| -| 1.11.0 | | | Ensure crypto policy is FUTURE or FIPS | covered if crypto_policy variable is set to FUTURE or FIPS| -| 2.1.1 | 2.1.7 \(SLES 2.1.11\) | 2.1.1 | Remove xinetd service -| | 2.1.1 | | Ensure chagen services are not enabled -| | 2.1.2 | | Ensure daytime services are not enabled -| | | 2.1.2 | Ensure openbsd-inetd is not installed -| | 2.1.3 | | Ensure discard services are not enabled -| | 2.1.4 | | Ensure echo services are not enabled -| | 2.1.5 | | Ensure time services are not enabled -| | 2.1.6 | | Ensure tftp server is not enabled -| 2.2.1.1 | 2.2.1.1 | 2.2.1.1 | Verify Time synchronization is in use -| 2.2.1.2 | 2.2.1.3 | 2.2.1.3 | Configure chrony -| | | 2.2.1.2 | Configure systemd-timesyncd -| | 2.2.1.2 | 2.2.1.4 | Configure ntp |skipped in Ubuntu| -| 2.2.2 | 2.2.2 | 2.2.2 | Disable display manager -| 2.2.3 | 2.2.21 \(SLES 2.2.18\) | 2.2.16 | Remove rsync -| 2.2.4 | 2.2.3 | 2.2.3 | Remove avahi -| 2.2.5 | 2.2.14 | 2.2.14 | Remove snmp -| 2.2.6 | 2.2.13 | 2.2.13 | Remove Web proxy -| 2.2.7 | 2.2.12 | 2.2.12 | Remove samba -| 2.2.8 | 2.2.11 | 2.2.11 | Remove Mail Relay Server -| 2.2.9 | 2.2.10 | 2.2.10 | Remove http Server -| 2.2.10 | 2.2.9 | 2.2.9 | Remove FTP Server -| 2.2.11 | 2.2.8 | 2.2.8 | Remove DNS server -| 2.2.12 | 2.2.7 | 2.2.7 | Remove nfs server -| 2.2.13 | 2.2.7 | 2.2.7 | Remove rpcbind -| 2.2.14 | 2.2.6 | 2.2.6 | Remove LDAP server |skipped in RHEL 8 as it is part of SSSD| -| 2.2.15 | 2.2.5 | 2.2.5 | Remove DHCP server -| | 2.2.17 | 2.3.2 | Remove rsh Server/Client -| | 2.2.18 | 2.3.3 | Remove talk -| | 2.2.20 \(SLES 2.2.17\) | | Remove tftp -| 2.2.16 | 2.2.4 | 2.2.4 | Disable cups as we my not be able to uninstall it -| 2.2.17 | 2.2.16 | 2.2.17 | Remove NIS Server -| 2.2.18 | 2.2.15 | 2.2.15 | Configure mail MTA agent for local-only mode -| 2.3.1 | | 2.3.1 | Remove NIS Client -| 2.3.2 | 2.2.19 \(SLES 2.2.8\) | 2.3.4 | Remove telnet -| 2.3.3 | | 2.3.5 | Remove openldap-clients -| | | 3.1.0 | Set host network parameters | host with single interface, or multiple interfaces but not routing between them -| 3.1.1 | 3.1.1 | 3.1.2 | Ensure IP forwarding is disabled | included in 3.1.0 -| 3.1.2 | 3.1.2 | 3.1.1 | Ensure packet redirect sending is disabled | included in 3.1.0 -| | | 3.2.0 | Set host with router network parameters | to be used when using 3.1.0 above as well as hosts with two interfaces configured to perform routing between them -| 3.2.1 | 3.2.1 | 3.2.1 | Ensure source routed packets are not accepted | included in 3.2.0 -| 3.2.2 | 3.2.2,3.3.2| 3.2.2 | Ensure ICMP redirects are not accepted | included in 3.2.0 -| 3.2.3 | 3.2.3 | 3.2.3 | Ensure secure ICMP redirects are not accepted | included in 3.2.0 -| 3.2.4 | 3.2.4 | 3.2.4 | Ensure suspicious packets are logged | included in 3.2.0 -| 3.2.5 | 3.2.5 | 3.2.5 | Ensure broadcast ICMP requests are ignored | included in 3.2.0 -| 3.2.6 | 3.2.6 | 3.2.6 | Ensure bogus ICMP responses are ignored | included in 3.2.0 -| 3.2.7 | 3.2.7 | 3.2.7 | Ensure Reverse Path Filtering is enabled | included in 3.2.0 -| 3.2.8 | 3.2.8 | 3.2.8 | Ensure TCP SYN Cookies is enabled | included in 3.2.0 -| | 3.3.0 | | IPv6 controls and settings | in RHEL 7 these are in their own area -| 3.2.9 | 3.3.1 | 3.2.9 | Ensure IPv6 router advertisements are not accepted -| | 3.3.2 | | Ensure IPv6 redirects are not accepted | included in 3.2.2 in RHEL8 and Ubuntu | -| | 3.4.1 | 3.3.1 | Install TCPwrappers -| | 3.4.2 | 3.3.2 | Ensure /etc/hosts.allow is configured -| | 3.4.3 | 3.3.3 | Ensure /etc/hosts.deny is configured -| | 3.4.4 | 3.3.4 | Ensure permissions on /etc/hosts.allow -| | 3.4.5 | 3.3.5 | Ensure permissions on /etc/hosts.deny -| 3.3.1 | 3.5.1 | 3.4.1 | Ensure DCCP is disabled -| 3.3.2 | 3.5.2 | 3.4.2 | Ensure SCTP is disabled -| 3.3.3 | 3.5.3 | 3.4.3 | Ensure RDS is disabled -| 3.3.4 | 3.5.4 | 3.4.4 | Ensure TIPC is disabled -| 3.4.1 | 3.6.1 | 3.5.1.1 | Ensure a firewall package is installed -| 3.4.2 | 3.6.2-5 | | Ensure firewall is configured -| 3.4.2.1 | | | Ensure firewalld is enabled and running -| 3.4.2.2 | | | Disable iptables service -| 3.4.2.3 | | | Disable netfilters service -| 3.4.2.4 | | | Ensure default zone is set for firewalld -| 3.4.2.4 | | | Set default zone in firewalld -| 3.4.2.5 | | | Ensure network interfaces are assigned to appropriate zone |skipped: machine dependent| -| | | 3.5.2.1 | Ensure ufw service is enabled -| | | 3.5.2.2 | Ensure default deny firewall policy |skipped: machine dependent| -| | | 3.5.2.3 | Ensure loopback traffic is configured -| | | 3.5.2.4 | Ensure outbound connections are configured |skipped: machine dependent| -| | | 3.5.2.5 | Ensure firewall rules exist for all open ports |skipped: machine dependent| -| 3.4.3.* | | 3.5.3 | Configure nftables (skipped: seldom used) -| 3.4.4 | 3.6.1 | | Ensure iptables are flushed -| 3.4.4.1-2| 3.6.2-5 | 3.5.4 | Configure iptables (skipped: machine dependent) -| 3.5 | 3.7 | 3.6 | Ensure wireless interfaces are disabled |skipped: machine dependent| -| 3.6.0 | 3.3.3 | 3.7 | Disable IPv6 -| 4.1.1.1 | | 4.1.1.1 | Install audit package -| 4.1.1.2 | 4.1.2 | 4.1.1.2 | Enable auditd service -| 4.1.1.3 | 4.1.3 | 4.1.1.3 | Ensure auditing for processes that sart prior to auditd -| 4.1.1.4 | | 4.1.1.4 | Ensure audit_backlog_limit is sufficient -| 4.1.2.1 | 4.1.1.1 | 4.1.2.1 | Configure audit log storage size -| 4.1.2.2 | 4.1.1.3 | 4.1.2.2 | Ensure audit logs are not automatically deleted -| 4.1.2.3 | 4.1.1.2 | 4.1.2.3 | Ensure system is disabled when audit logs are full -| 4.1.3 | 4.1.15 | 4.1.14 | Ensure changes to sudoers is collected -| 4.1.4 | 4.1.8 | 4.1.7 | Ensure system logins are collected -| 4.1.5 | 4.1.9 | 4.1.8 | Ensure session initiation information is collected -| 4.1.6 | 4.1.4 | 4.1.3 | Ensure to collect events that modify date/time -| 4.1.7 | 4.1.7 | 4.1.6 | Ensure modifications to Mandatory Access Controls are collected -| 4.1.8 | 4.1.6 | 4.1.5 | Ensure modifications to network environment are collected -| 4.1.9 | 4.1.10 | 4.1.9 | Ensure modifications to discretionary access controls are collected -| 4.1.10 | 4.1.11 | 4.1.10 | Ensure unsuccessful unauthorized file access attempts are collected -| 4.1.11 | 4.1.5 | 4.1.4 | Ensure events that modify user/group information are collected -| 4.1.12 | 4.1.13 | 4.1.12 | Ensure successful file system mounts are collected -| 4.1.13 | 4.1.12 | 4.1.11 | Ensure use of privileged commands is collected |skipped: machine dependent| -| 4.1.14 | 4.1.14 | 4.1.13 | Ensure file deletion events by users are collected -| 4.1.15 | 4.1.17 | 4.1.16 | Ensure kernel module loading and unloading is collected -| 4.1.16 | 4.1.16 | 4.1.15 | Ensure sysadmin actions (sudolog) are collected -| 4.1.17 | 4.1.18 | 4.1.17 | Ensure audit configuration is immutable -| 4.2.1.1 | 4.2.3 | 4.2.1.1 | Ensure rsyslog is installed -| 4.2.1.2 | | 4.2.1.2 | Enable rsyslog -| 4.2.1.3 | 4.2.1.3 | 4.2.1.4 | Ensure rsyslog default file permissions are configured -| 4.2.1.4 | 4.2.1.2 | 4.2.1.3 | Ensure logging is configured |Only runs if the variable rsyslog_file is set, which it is not set by default| -| 4.2.1.5 | 4.2.1.4 | 4.2.1.5 | Ensure logging is configured to send logs to remote host |skipped: environment dependent| -| 4.2.1.6 | 4.1.2.5 | 4.2.1.6 | Ensure remote rsyslog messages are only accepted on designated log hosts -| | 4.2.2.1 | | Ensure syslog-ng is configured |skipped: not a Red Hat package| -| 4.2.2 | | 4.2.2 | Configure journald -| 4.2.2.1 | | 4.2.2.1 | Forward journald logs to rsyslog IF rsyslog is sending logs to a log host -| 4.2.2.2 | | 4.2.2.2 | Ensure journald compresses large files -| 4.2.2.3 | | 4.2.2.3 | Ensure journald writes to peristent disk -| 4.3.0 | 4.3.0 | 4.3 | Ensure logrotate is installed and configured -| 5.1.1 | 5.1.1 | 5.1.1 | Ensure cron is enabled -| 5.1.2 | 5.1.2 | 5.1.2 | Ensure permissions on /etc/crontab -| 5.1.3 | 5.1.3 | 5.1.3 | Ensure permissions on /etc/cron.hourly -| 5.1.4 | 5.1.4 | 5.1.4 | Ensure permissions on /etc/cron.daily -| 5.1.5 | 5.1.5 | 5.1.5 | Ensure permissions on /etc/cron.weekly -| 5.1.6 | 5.1.6 | 5.1.6 | Ensure permissions on /etc/cron.monthly -| 5.1.7 | 5.1.7 | 5.1.7 | Ensure permissions on /etc/cron.d -| 5.1.8 | 5.1.8 | 5.1.8 | Ensure at/cron restricted to authorized users |skipped: environment dependent| -| 5.2.0 | 5.2.0 | 5.2.0 | SSH File configurations -| 5.2.1 | 5.2.1 | 5.2.1 | Set permissions on SSH file -| 5.2.2 | 5.2.14 | 5.2.18 | Ensure SSH access is limited |skipped: environment dependent| -| | 5.2.2 | 5.2.4 | Ensure SSH Protocol is set to 2 |skipped on Ubuntu| -| 5.2.3 | | 5.2.2 | Set Permissions on ssh private host keys -| 5.2.4 | | 5.2.3 | Set Permissions on ssh public host keys -| 5.2.5 | 5.2.3 | 5.2.5 | Set LogLevel to INFO -| 5.2.6 | 5.2.4 | 5.2.6 | Disable X11 forwarding -| 5.2.7 | 5.2.5 | 5.2.7 | Ensure SSH MaxAuthTires is set -| 5.2.8 | 5.2.6 | 5.2.8 | Ensure IgnoreRhosts is set -| 5.2.9 | 5.2.7 | 5.2.9 | Ensure HostbasedAuthentication is disabled -| 5.2.10 | 5.2.8 | 5.2.10 | Ensure PermitRootLogin is disbled -| 5.2.11 | 5.2.9 | 5.2.11 | Ensure SSH PermitEmptyPasswords is disabled -| 5.2.12 | 5.2.10 | 5.2.12 | Ensure PermitUserEnvironment is disabled -| | | 5.2.13 | Ensure only strong Ciphers are used -| | 5.2.11 | 5.2.14 | Ensure only approved MAC alogrithms are used -| | | 5.2.15 | Ensure only strong Key Exchange alogrithms are used -| 5.2.13 | 5.2.12 | 5.2.16 | Ensure SSH Idle Timeout is configured -| 5.2.14 | 5.2.13 | 5.2.17 | Ensure SSH LoginGraceTime is set -| 5.2.15 | 5.2.15 | 5.2.19 | Ensure SSH Banner is configured -| 5.2.16 | | 5.2.20 | Ensure SSH is configured to use PAM -| 5.2.17 | | 5.2.21 | Disable SSH Forwarding -| 5.2.18 | | 5.2.22 | Limit max unauthenticated startups -| 5.2.19 | | 5.2.23 | Limit max sessions -| 5.2.20 | | | Ensure system crypto policy isn't overriden in SSH -| 5.3 | | | Configure authselect |skipped: machine dependent| -| 5.4.1 | 5.3.1 | 5.3.1 | Configure PAM files and password requirements -| 5.4.2 | 5.3.2 | 5.3.2 | Ensure lockout for failed password attempts |skipped: environment dependent| -| 5.4.3 | 5.3.3 | 5.3.3 | Ensure password reuse is limited (skipped; environment dependent) -| 5.4.4 | 5.3.4 | 5.3.4 | Configure password hashing algorithm is SHA-512 |skipped, various reasons| -| 5.5.1.1 | 5.4.1.1 | 5.4.1.1 | Ensure password expiration is 365 days or less -| 5.5.1.2 | 5.4.1.2 | 5.4.1.2 | Ensure password change days is set to 7 -| 5.5.1.3 | 5.4.1.3 | 5.4.1.3 | Ensure password warning days is set to 7 -| 5.5.1.4 | 5.4.1.4 | 5.4.1.4 | Disable accounts that are inactive for 30 days after password expiration -| 5.5.1.5 | 5.4.1.5 | 5.4.1.5 | Ensure all users last password change date in the past |skipped: environment dependent| -| 5.5.2 | 5.4.2 | 5.4.2 | Ensure system accounts are secured |skipped: environment dependent| -| 5.5.3 | 5.4.5 | 5.4.5 | Ensure default shell timeout is 900 seconds or less -| 5.5.4 | 5.4.3 | 5.4.3 | Ensure default group for root is GID 0 -| 5.5.5 | 5.4.4 | 5.4.4 | Ensure umask is set -| 5.6 | | | Ensure root login is restricted to system console |skipped: system dependent| -| 5.7.0 | 5.6.0 | 5.6 | Restrict su to wheel group | on Ubuntu control says to any one group, but for simplicity we are using wheel | -| 6.1.1 | 6.1.1 | 6.1.1 | Audit system file permissions |skipped: manual intervention needed| -| 6.1.2,6.1.4| 6.1.2,6.1.4| 6.1.2,6.1.4 | Ensure permissions on /etc/passwd /etc/group -| 6.1.3,6.1.5| 6.1.3,6.1.5| 6.1.3,6.1.5 | Ensure permissions on /etc/shadow /etc/gshadow -| 6.1.6,7,8,9| 6.1.6,7,8,9| 6.1.6,7,8,9 | Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- -| 6.1.10 | 6.1.10 | 6.1.10 | Report if any world writable files exist -| 6.1.11 | 6.1.11 | 6.1.11 | Report if any unowned files exist -| 6.1.12 | 6.1.12 | 6.1.12 | Report if any ungrouped files or directories exist -| 6.1.13 | 6.1.13 | 6.1.13 | Audit SUID executables |skipped: system dependent| -| 6.1.14 | 6.1.14 | 6.1.14 | Audit SGID executables |skipped: system dependent| -| 6.2.1 | 6.2.1 | 6.2.1 | Report on password fields that are empty -| 6.2.2 | 6.2.2 | 6.2.2 | Ensure no legacy "+" entries exist in /etc/passwd -| 6.2.3 | 6.2.6 | 6.2.7 | Ensure root PATH integrity -| 6.2.4 | 6.2.3 | 6.2.4 | Ensure no legacy "+" entries exist in /etc/shadow -| 6.2.5 | 6.2.4 | 6.2.5 | Ensure no legacy "+" entries exist in /etc/group -| 6.2.6 | 6.2.5 | 6.2.6 | Report on multiple accounts with UID of 0 -| 6.2.7 | 6.2.7 | 6.2.9 | Ensure home directories exist |skipped: environment dependent| -| 6.2.8 | 6.2.8 | 6.2.8 | Ensure home directory permissions are 750 or more restrictive (skipped: environment dependent| -| 6.2.9-13,20| 6.2.10-14| 6.2.10-14 | Various controls recommended to be run by monitoring software -| 6.2.14 | 6.2.15 | 6.2.15 | Report on groups in /etc/passwd with a GID not in /etc/group -| 6.2.15 | 6.2.16 | 6.2.16 | Report on duplicate UIDs in /etc/passwd -| 6.2.16 | 6.2.17 | 6.2.17 | Report on duplicate GIDs in /etc/group -| 6.2.17 | 6.2.18 | 6.2.18 | Report on duplicate users in /etc/passwd -| 6.2.18 | 6.2.19 | 6.2.19 | Report on duplicate groups in /etc/group -| 6.2.19 | | 6.2.20 | Report if shadow group exists in /etc/group -| 6.2.20 | | | Ensure all users' home directories exist \ No newline at end of file +## Below are the tags used in the CIS roles on Linux Machines + +| RHEL 9 | RHEL 8 / Fedora 31 / CentOS 8 / Oracle 8 | RHEL 7 / Centos 7 / Oracle 7 / SLES 15 | Ubuntu 22.04 | Ubuntu 18.04 / 20.04 | Control Description | Notes | +| ----------------- | -----------------|--------------------- | -----------------|--------------------- | --------------------- | --------------| +| | 1.1.1.1 | 1.1.1.1 | 1.1.1.1 | 1.1.1.1 | Remove cramfs | +| | | 1.1.1.2 | | 1.1.1.2 | Remove freevxfs +| | | 1.1.1.3 | | 1.1.1.3 | Remove jffs2 +| | | 1.1.1.4 | | 1.1.1.4 | Remove hfs +| | | 1.1.1.5 | | 1.1.1.5 | Remove hfsplus +| | 1.1.1.2 | 1.1.1.8 | | 1.1.1.8 | Remove vfat +| 1.1.1.1 | 1.1.1.3 | 1.1.1.6 | 1.1.1.2 | 1.1.1.6 | Remove squashfs +| 1.1.1.2 | 1.1.1.4 | 1.1.1.7 | 1.1.1.3 | 1.1.1.7 | Remove udf +| 1.1.2 | 1.1.2 | | 1.1.2 | 1.1.2 | Ensure tmpfs is configured +| 1.1.2.1 | | 1.1.2 | 1.1.2.1 | | Report if /tmp is not on a separate partition | a) If report comes back saying partitions are not separate, the no\[dev,suid,exec\] checks aren't run b) On a failed control, simply prints a notification to the user | +| 1.1.2.2 | 1.1.3 | 1.1.4 | 1.1.2.2 | 1.1.3 | Ensure nodev option on /tmp partition +| 1.1.2.3 | 1.1.4 | 1.1.5 | 1.1.2.4 | 1.1.4 | Ensure nosuid option set on /tmp partition +| 1.1.2.4 | 1.1.5 | 1.1.3 | 1.1.2.3 | 1.1.5 | Ensure noexec option set on /tmp partition +| 1.1.3 | 1.1.3 | | 1.1.3 | | Configure /var (includes all the /var partition controls) +| 1.1.3.1 | 1.1.3.1 | 1.1.10 | 1.1.3.1 | 1.1.6 | Report if /var is not on a separate partition +| 1.1.3.2 | 1.1.3.2 | | 1.1.3.2 | | Report if /var is missing nodev option +| 1.1.3.3 | 1.1.3.3 | | 1.1.3.3 | | Report if /var is missing noexec option +| 1.1.4 | 1.1.4 | | 1.1.4 | | Configure /var/tmp +| 1.1.4.1 | 1.1.4.1 | 1.1.11 | 1.1.4.1 | 1.1.7 | Report if /var/tmp is not on a separate partition a) If report comes back saying partitions are not separate, the no\[dev,suid,exec\] checks aren't run b) On a failed control, simply prints a notification to the user | +| 1.1.4.2 | 1.1.4.2 | 1.1.13 | 1.1.4.4 | 1.1.8 | Ensure nodev option on /var/tmp partition +| 1.1.4.3 | 1.1.4.3 | 1.1.14 | 1.1.4.3 | 1.1.9 | Ensure nosuid option set on /var/tmp partition +| 1.1.4.4 | 1.1.4.4 | 1.1.12 | 1.1.4.2 | 1.1.10 | Ensure noexec option set on /var/tmp partition +| 1.1.5 | 1.1.5 | | 1.1.5 | | Configure /var/log +| 1.1.5.1 | 1.1.5.1 | 1.1.15 | 1.1.5.1 | 1.1.11 | Report if /var/log is not on a separate partition | On a failed control, simply prints a notification to the user | +| 1.1.5.2 | 1.1.5.2 | | 1.1.5.2 | | Ensure nodev option on /var/log partition +| 1.1.5.3 | 1.1.5.3 | | 1.1.5.4 | | Ensure nosuid option set on /var/log partition +| 1.1.5.4 | 1.1.5.4 | | 1.1.5.3 | | Ensure noexec option set on /var/log partition +| 1.1.6 | 1.1.6 | | 1.1.6 | | Configure /var/log/audit +| 1.1.6.1 | 1.1.6.1 | 1.1.16 | 1.1.6.1 | 1.1.12 | Report if /var/log/audit is not on a separate partition | On a failed control, simply prints a notification to the user | +| 1.1.6.2 | 1.1.6.2 | | 1.1.6.3 | | Ensure nodev option on /var/log/audit partition +| 1.1.6.3 | 1.1.6.3 | | 1.1.6.4 | | Ensure nosuid option set on /var/log/audit partition +| 1.1.6.4 | 1.1.6.4 | | 1.1.6.2 | | Ensure noexec option set on /var/log/audit partition +| 1.1.7 | 1.1.7 | | 1.1.7 | | Configure /home +| 1.1.7.1 | 1.1.7.1 | 1.1.17 | 1.1.7.1 | 1.1.13 | Report if /home is not on a separate partition | On a failed control, simply prints a notification to the user | +| 1.1.7.2 | 1.1.7.2 | | 1.1.7.2 | | Ensure nodev option on /home partition +| 1.1.7.3 | 1.1.7.3 | | 1.1.7.3 | | Ensure nosuid option set on /home partition +| 1.1.8.2 | 1.1.8.2 | 1.1.8 | 1.1.8.1 | 1.1.15 | Report if /dev/shm does not have nodev set +| | | | 1.1.8.2 | | Report if /dev/shm does not have noexec set +| 1.1.8.2 | 1.1.8.3 | 1.1.9 | 1.1.8.3 | 1.1.16 | Report if /dev/shm does not have nosuid set +| 1.1.8.4 | 1.1.8.4 | 1.1.20 | | 1.1.18 | Ensure nodev option set on removable media | skipped: environment dependent| +| | 1.1.22 | 1.1.23 | 1.1.9 | 1.1.22 | Disable automounting +| | 1.1.23 | 1.1.24 | 1.1.10 | 1.1.23 | Disable USB storage module +| 1.2.0 | 1.2.0 | | 1.9.0 | | Update System to latest +| | 1.2.1 | 1.2.2 | 1.2.1 | 1.2.1 | Ensure system is configured for updates |skipped: environment dependent| +| | 1.2.2 | 1.2.5 | | | Disable the rhnsd Daemon | RHEL control only | +| 1.2.1 | 1.2.3 | 1.2.1,3 | 1.2.2 | 1.2.2 | Ensure gpg keys are configured | gpgcheck set to yes. 1.2.2 on SLES 15, but skipped | +| 1.2.2 | 1.2.4 | 1.2.2 | 1.2.2 | 1.2.2 | Ensure gpgcheck is globally activated. Missing on SLES 15 benchmark +| 1.2.3 | 1.2.5 | 1.2.4 | | | Ensure machine is registerd with Red Hat |skipped: environment dependent| +| 5.3.1 | 5.3.1 | | 5.3.1 | 1.3.1 | Ensure sudo is installed +| 5.3.2 | 5.3.2 | | 5.3.2 | 1.3.2 | Ensure sudo commands use pty +| 5.3.3 | 5.3.3 | | 5.3.3 | 1.3.3 | Ensure sudo log file exists +| 1.3.0 | 1.3.0 | 1.3.0 | 1.3.0 | 1.4.0 | Install and configure filesystem integrity checking w/AIDE +| 1.3.1 | 1.3.1 | 1.3.1 | 1.3.1 | 1.4.1 | Ensure aide is installed +| 1.3.2 | 1.3.2 | 1.3.2 | 1.3.2 | 1.4.2 | Ensure File integrity is regularly checked +| 1.4.1 | 1.4.1 | 1.4.1 | 1.4.1 | 1.5.2 | Ensure bootloader password is set |skipped: environment dependent| +| 1.4.2 | 1.4.2 | 1.4.2 | 1.4.2 | 1.5.1 | Set permissions on grub bootloader files +| | | 1.4.3 | 1.4.3 | 1.5.3 | Set single user password | if root password is not set, sets root password before setting up secure single user mode. Uses root_password variable. See [Using vaults with playbooks](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html#playbooks-vault) for information on how to secure it| +| 1.5.1 | 1.5.1 | 1.5.1 | 1.5.4 | 1.6.4 | Ensure core dumps are restricted +| 1.5.2 | 1.5.2 | | | | Ensure core dump backtraces are disabled +| | | 1.5.2 | | 1.6.1 | Ensure XD/NX support is enabled +| 1.5.3 | 1.5.3 | 1.5.3 | 1.5.1 | 1.6.2 | Ensure address space layout reandomization (ASLR) is enabled +| | | 1.5.4 | 1.5.2 | | Ensure prelink is not installed +| | | | 1.5.3 | | Ensure Apport Error Reporting Service is disabled +| 1.6.0 | 1.6.0 | 1.6.0 \(SLES: skipped\) | | | Configure SELinux | SLES supports SELinux, but does not provide a policy, so enabling it will crash the system, so we are skipping it on SLES) +| 1.6.1.1 | 1.6.1.1 | 1.6.1.1 | | | Ensure SELinux is installed +| 1.6.1.2 | 1.6.1.2 | 1.6.1.2 | | | Ensure SELinux is not disabled in bootloader configuration +| 1.6.1.3 | 1.6.1.3 | 1.6.1.3 | | | Set SELinux policy +| 1.6.1.4 | 1.6.1.4 | 1.6.1.2 | | | Set SELinux to not disabled +| 1.6.1.5 | 1.6.1.5 | 1.6.1.2 | | | Set SELinux state to enforcing +| 1.6.1.6 | 1.6.1.5 | 1.6.1.6 | | | Ensure no unconfined services exist +| 1.6.1.7 | 1.6.1.7 | 1.6.1.7 | | | Remove setroubleshoot +| 1.6.1.8 | 1.6.1.8 | 1.6.1.8 | | | Remove MCS Translation Service +| | | | 1.6.1.0 | 1.7.0 | Install and Configure AppArmor +| | | | 1.6.1.1 | 1.7.1.1 | Ensure AppArmor is installed +| | | | 1.6.1.2 | 1.7.1.2 | Ensure AppArmor is not disabled in bootloader configuration +| | | | 1.6.1.3 | 1.7.1.3 | Ensure AppArmor profiles are in enforce or complain mode +| | | \(SLES 1.|6.2.2\) | 1.6.1.4 | 1.7.1.4 | Ensure AppArmor profiles are enforcing | SLES only control in the RHEL 7 file, use 1.6.2.2 for the tag number +| | | \(SLES 1.6.3\) | | | Ensure SELinux or AppArmor are installed | SLES only control in the RHEL 7 file, use 1.6.3 for the tag number +| 1.7.1 | 1.7.1 | 1.7.1.1 | 1.7.1 | 1.8.1.1 | Install motd banners +| 1.7.2 | 1.7.2 | 1.7.2 | 1.7.2 | 1.8.1.2 | Install issue banners +| 1.7.3 | 1.7.3 | 1.7.3 | 1.7.3 | 1.8.1.3 | Install issue.net banners +| 1.7.4 | 1.7.4 | 1.7.4 | 1.7.4 | 1.8.1.4 | Ensure permissions on /etc/motd are configured +| 1.7.5 | 1.7.5 | 1.7.5 | 1.7.5 | 1.8.1.5 | Ensure permissions on /etc/issue are configured +| 1.7.6 | 1.7.6 | 1.7.6 | 1.7.6 | 1.8.1.6 | Ensure permissions on /etc/issue.net are configured +| 1.8.1 | 1.8.1 | 1.8.1 | 1.8.1 | | Ensure GNOME Display Manager is removed +| 1.8.2 | 1.8.2 | 1.8.2 | 1.8.2 | 1.8.2 | Ensure GDM banner set up +| 1.8.3 | | 1.8.3 | 1.8.3 | | Ensure last logged in user display is disabled +| 1.8.4 | | | 1.8.4 | | Ensure GDM screen locks when user is idle +| 1.9.0 | 1.9.0 | | 1.9.0 | 1.9.0 | Ensure updated system | First control to be run chronologically in order to make sure things are in the same state for the rest of the play +| 1.10.0 | 1.10.0 | | | | Ensure crypto policy is not legacy | set to what the crypto_policy variable is set to| +| | 2.1.1 | 2.1.7 \(SL ES 2.1.11 || 2.1.1 | Remove xinetd service +| | | 2.1.1 | | | Ensure chagen services are not enabled +| 2.2.1 | 2.2.2 | | | | Ensure xorg-x11-server-common is not installed +| | | 2.1.2 | | | Ensure daytime services are not enabled +| | | | | 2.1.2 | Ensure openbsd-inetd is not installed +| | | 2.1.3 | | | Ensure discard services are not enabled +| | | 2.1.4 | | | Ensure echo services are not enabled +| | | 2.1.5 | | | Ensure time services are not enabled +| 2.2.7 | 2.2.9 | 2.1.6 | | | Ensure tftp server is not enabled +| 2.1.1 | 2.1.1 | 2.2.1.1 | 2.1.1 | 2.2.1.1 | Verify Time synchronization is in use +| | | | 2.1.1.1 | | Ensure a single time synchronization daemon is in use +| 2.1.2 | 2.1.2 | 2.2.1.3 | 2.1.2 | 2.2.1.3 | Configure chrony +| | | | 2.1.2.2 | | Ensure chrony is running as user _chrony (configurable via variable) +| | | | 2.1.2.3 | | Ensure chrony is enabled and running +| | | | 2.1.3 | 2.2.1.2 | Configure systemd-timesyncd +| | | | 2.1.3.2 | | Ensure systemd_tiemsyncd is enbled and running +| | | 2.2.1.2 | | | Configure ntp (skipped on ubuntu as not recommended ) +| | | 2.2.2 | 2.2.1 | 2.2.2 | Disable display manager +| 2.2.18 | 2.2.20 | 2.2.21 \(SLES 2.2.18\) | 2.2.16 | 2.2.16 | Remove rsync +| 2.2.2 | 2.2.3 | 2.2.3 | 2.2.2 | 2.2.3 | Remove avahi +| 2.2.12 | 2.2.14 | 2.2.14 | 2.2.13 | 2.2.14 | Remove snmp +| 2.2.11 | 2.2.13 | 2.2.13 | 2.2.12 | 2.2.13 | Remove Web proxy +| 2.2.10 | 2.2.12 | 2.2.12 | 2.2.11 | 2.2.12 | Remove Samba server +| 2.2.9 | 2.2.11 | 2.2.11 | 2.2.15 | 2.2.11 | Remove Mail Relay Server +| 2.2.8 | 2.2.10 | 2.2.10 | 2.2.9 | 2.2.10 | Remove http Server +| 2.2.6 | 2.2.\[7,8\] | 2.2.9 | 2.2.8 | 2.2.9 | Remove FTP Server +| 2.2.5 | 2.2.6 | 2.2.8 | 2.2.7 | 2.2.8 | Remove DNS server +| 2.2.\[12,16\] | 2.2.18 | 2.2.7 | 2.2.6 | 2.2.7 | Remove nfs server +| 2.2.16 | 2.2.19 | 2.2.7 | 2.2.6 | 2.2.7 | Remove rpcbind +| | 2.2.14 | 2.2.6 | 2.2.5 | 2.2.6 | Remove LDAP server |skipped in RHEL 8 as it is part of SSSD| +| 2.2.4 | 2.2.5 | 2.2.5 | 2.2.4 | 2.2.5 | Remove DHCP server +| | | 2.2.17 | 2.3.2 | 2.3.2 | Remove rsh Server/Client +| | | 2.2.18 | 2.3.3 | 2.3.3 | Remove talk +| 2.3.3 | 2.2.9 | 2.2.20 \(SLES 2.2.17\) | | | Remove tftp client +| 2.3.4 | | | | | Remove ftp client +| 2.2.3 | 2.2.16 | 2.2.4 | 2.2.3 | 2.2.4 | Disable cups as we my not be able to uninstall it +| | 2.2.15 | 2.2.16 | 2.2.14 | 2.2.17 | Remove NIS Server +| | 2.2.16 | | | | Remove telnet-server +| 2.2.15 | 2.2.17 | 2.2.15 | 2.2.15 | 2.2.15 | Configure mail MTA agent for local-only mode +| | 2.3.1 | | 2.3.1 | 2.3.1 | Remove NIS Client +| 2.3.3 | | | | | Remove NFS Client +| 2.3.1 | 2.3.2 | 2.2.19 \(SLES 2.2.8\) | 2.3.4 | 2.3.4 | Remove telnet client +| 2.3.2 | 2.3.3 | | 2.3.5 | 2.3.5 | Remove openldap-clients +| | | | 3.2.0 | 3.1.0 | Set host network parameters | host with single interface, or multiple interfaces but not routing between them +| 3.2.1 | 3.1.1 | 3.1.1 | 3.2.2 | 3.1.2 | Ensure IP forwarding is disabled | included in 3.1.0 +| 3.2.2 | 3.1.2 | 3.1.2 | 3.2.1 | 3.1.1 | Ensure packet redirect sending is disabled | included in 3.1.0 +| | | | | 3.2.0 | Set host with router network parameters | to be used when using 3.1.0 above as well as hosts with two interfaces configured to perform routing between them +| 3.3.1 | 3.2.1 | 3.2.1 | 3.3.1 | 3.2.1 | Ensure source routed packets are not accepted | included in 3.2.0 +| 3.3.2 | 3.2.2 | 3.2.2,3.3.2 | 3.3.2 | 3.2.2 | Ensure ICMP redirects are not accepted | included in 3.2.0 +| 3.3.3 | 3.2.3 | 3.2.3 | 3.3.3 | 3.2.3 | Ensure secure ICMP redirects are not accepted | included in 3.2.0 +| 3.3.4 | 3.2.4 | 3.2.4 | 3.3.4 | 3.2.4 | Ensure suspicious packets are logged | included in 3.2.0 +| 3.3.5 | 3.2.5 | 3.2.5 | 3.3.5 | 3.2.5 | Ensure broadcast ICMP requests are ignored | included in 3.2.0 +| 3.3.6 | 3.2.6 | 3.2.6 | 3.3.6 | 3.2.6 | Ensure bogus ICMP responses are ignored | included in 3.2.0 +| 3.3.7 | 3.2.7 | 3.2.7 | 3.3.7 | 3.2.7 | Ensure Reverse Path Filtering is enabled | included in 3.2.0 +| 3.3.8 | 3.2.8 | 3.2.8 | 3.3.8 | 3.2.8 | Ensure TCP SYN Cookies is enabled | included in 3.2.0 +| | | 3.3.0 | | | IPv6 controls and settings | in RHEL 7 these are in their own area +| 3.3.9 | 3.2.9 | 3.3.1 | 3.3.9 | 3.2.9 | Ensure IPv6 router advertisements are not accepted +| | | 3.3.2 | | | Ensure IPv6 redirects are not accepted | included in 3.2.2 in RHEL8 and Ubuntu | +| | | 3.4.1 | | 3.3.1 | Install TCPwrappers +| | | 3.4.2 | | 3.3.2 | Ensure /etc/hosts.allow is configured +| | | 3.4.3 | | 3.3.3 | Ensure /etc/hosts.deny is configured +| | | 3.4.4 | | 3.3.4 | Ensure permissions on /etc/hosts.allow +| | | 3.4.5 | | 3.3.5 | Ensure permissions on /etc/hosts.deny +| 3.1.2 | 3.1.2 | 3.5.2 | 3.4.2 | 3.4.2 | Ensure SCTP is disabled +| 3.1.3 | 3.1.3 | 3.5.1 | 3.4.1 | 3.4.1 | Ensure DCCP is disabled +| 3.1.4 | 3.1.4 | 3.5.4 | 3.4.4 | 3.4.4 | Ensure TIPC is disabled +| | | 3.5.3 | 3.4.3 | 3.4.3 | Ensure RDS is disabled +| 3.4.1.2 | | 3.6.1 | | 3.5.1.1 | Ensure a firewall package is installed (firewalld or iptables) +| | | 3.6.2-5 | | | Ensure firewall is configured +| | | | | | firewalld: Ensure firewalld is enabled and running +| | | | | | firewalld: Disable iptables service +| | | | | | firewalld: Disable netfilters service +| | | | | | firewalld: Ensure default zone is set for firewalld +| 3.4.2.1 | | | | | firewalld: Set default zone in firewalld +| | | | | | firewalld: Ensure network interfaces are assigned to appropriate zone |skipped: machine dependent| +| 3.4.2 | 3.4.1 | | | | firewalld: Configure firewalld (block tag) +| | 3.4.1.1 | | | | firewalld: Ensure firewalld is installed +| | 3.4.1.2 | | | | firewalld: Ensure iptables-services is not installed +| | 3.4.1.3 | | | | firewalld: Ensure nftables either not installed or masked with firewalld +| | 3.4.1.4 | | | | firewalld: Ensure firewalld is enabled and running +| 3.4.2.1 | 3.4.1.5 | | | | firewalld: Ensure firewalld default zone is set +| 3.4.2 | 3.4.2 | | 3.5.2 | | nftables: Configure nftables (block tag) +| 3.4.1.1 | 3.4.2.1 | | 3.5.2.1 | 3.5.3 | nftables: Ensure nftables is installed +| 3.4.1.2 | | | | | nftables: Ensure a single firewall package package is installed +| | 3.4.2.2 | | | | nftables: Ensure firewalld is either uninstalled or masked with nftables +| | 3.4.2.3 | | | | nftables: Ensure iptables-services not installed with nftables +| | | | 3.5.2.2 | | nftables: Ensure ufw is uninstalled with nftables +| | | 3.6.1 | 3.5.2.3 | | nftables: Ensure iptables are flushed +| 3.4.2.2 | 3.4.2.5 | | 3.5.2.4 | | nftables: Ensure an nftables table is installed +| 3.4.2.3 | | | 3.5.2.5 | | nftables: Ensure nftables base chains exist +| 3.4.2.4 | | | 3.5.2.6 | | nftables: Ensure nftables loopback traffic is configured +| | | | 3.5.2.7 | | nftables: Ensure establshed connections are configured +| 3.4.1.1 | 3.4.2.10 | | 3.5.2.9 | | nftables: Ensure nftables is started +| | 3.4.3 | | 3.5.3.1 | | Configure iptables (block tag) +| | 3.4.3.1.1| | 3.5.3.2 | | Ensure iptables is installed +| | 3.4.3.1.2| | 3.5.3.3 | | Ensure nftables is not installed with iptables +| | 3.4.3.1.3| | 3.5.3.4 | | Ensure firewald is not installed with iptables +| | | | 3.5.1.1 | 3.5.2.1 | Ensure ufw service is enabled +| | | | 3.5.1.2 | | Ensure iptables-persistent is not installed with ufw +| | | | 3.5.1.3 | | Ensure ufw service is enabled +| | | | 3.5.1.4 | | Ensure ufw loopback traffic is configured +| | | 3.6.2-5 | | 3.5.4 | Configure iptables (skipped: machine dependent) +| | 3.6.0 | 3.3.3 | | 3.7 | Disable IPv6 +| 4.1.0 | 4.1.0 | | 4.1.0 | | Configure Auditing (block tag) +| 4.1.1.1 | 4.1.1.1 | | 4.1.1.1 | 4.1.1.1 | Install audit package +| 4.1.1.4 | 4.1.1.2 | 4.1.2 | 4.1.1.2 | 4.1.1.2 | Enable auditd service +| 4.1.1.2 | 4.1.1.3 | 4.1.3 | 4.1.1.3 | 4.1.1.3 | Ensure auditing for processes that sart prior to auditd +| 4.1.1.3 | 4.1.1.4 | | 4.1.1.4 | 4.1.1.4 | Ensure audit_backlog_limit is sufficient +| 4.1.2.1 | 4.1.2.1 | 4.1.1.1 | 4.1.2.1 | 4.1.2.1 | Configure audit log storage size +| 4.1.2.2 | 4.1.2.2 | 4.1.1.3 | 4.1.2.2 | 4.1.2.2 | Ensure audit logs are not automatically deleted +| 4.1.2.3 | 4.1.2.3 | 4.1.1.2 | 4.1.2.3 | 4.1.2.3 | Ensure system is disabled when audit logs are full +| 4.1.3.1 | 4.1.3.1 | 4.1.15 | 4.1.3.1 | 4.1.14 | Ensure changes to system administration scope \(sudoers\) is collected +| 4.1.3.2 | 4.1.3.2 | | 4.1.3.2 | | Ensure actions on behalf of another user are collected +| 4.1.3.3 | 4.1.3.3 | 4.1.16 | 4.1.3.3 | 4.1.15 | Ensure sysadmin actions (sudolog) are collected +| 4.1.3.4 | 4.1.3.4 | 4.1.4 | 4.1.3.4 | 4.1.3 | Ensure to collect events that modify date/time +| 4.1.3.5 | 4.1.3.5 | 4.1.6 | 4.1.3.5 | 4.1.5 | Ensure modifications to network environment are collected +| 4.1.3.6 | 4.1.3.6 | 4.1.12 | 4.1.3.6 | 4.1.11 | Ensure use of privileged commands is collected |skipped: machine dependent| +| 4.1.3.7 | 4.1.3.7 | 4.1.11 | 4.1.3.7 | 4.1.10 | Ensure unsuccessful unauthorized file access attempts are collected +| 4.1.3.8 | 4.1.3.8 | 4.1.5 | 4.1.3.8 | 4.1.4 | Ensure events that modify user/group information are collected +| 4.1.3.9 | 4.1.3.9 | 4.1.10 | 4.1.3.9 | 4.1.9 | Ensure modifications to discretionary access controls are collected +| 4.1.3.10 | 4.1.3.10 | 4.1.13 | 4.1.3.10| 4.1.12 | Ensure successful file system mounts are collected +| 4.1.3.11 | 4.1.3.11 | 4.1.9 | 4.1.3.11| 4.1.8 | Ensure session initiation information is collected +| 4.1.3.12 | 4.1.3.12 | 4.1.8 | 4.1.3.12| 4.1.7 | Ensure system logins and logouts are collected +| 4.1.3.13 | 4.1.3.13 | 4.1.14 | 4.1.3.13| 4.1.13 | Ensure file deletion events by users are collected +| 4.1.3.14 | 4.1.3.14 | 4.1.7 | 4.1.3.14| 4.1.6 | Ensure modifications to Mandatory Access Controls are collected +| 4.1.3.15 | 4.1.3.15 | | 4.1.3.15| | Ensure all attempts to use the chcon command are collected +| 4.1.3.16 | 4.1.3.16 | | 4.1.3.16| | Ensure all attempts to use the setfacl command are collected +| 4.1.3.17 | 4.1.3.17 | | 4.1.3.17| | Ensure all attempts to use the chacl command are collected +| 4.1.3.18 | 4.1.3.18 | | 4.1.3.18| | Ensure all attempts to use the usermod command are collected +| 4.1.3.19 | 4.1.3.19 | 4.1.17 | 4.1.3.19| 4.1.16 | Ensure kernel module loading and unloading is collected +| 4.1.3.20 | 4.1.3.20 | 4.1.18 | 4.1.3.20| 4.1.17 | Ensure audit configuration is immutable +| 4.1.4.1 | | | 4.1.4.1 | | Ensure audit log files are mode 0640 more more restrictive +| 4.1.4.2 | | | 4.1.4.2 | | Ensure only authorized users own audit log files +| 4.1.4.3 | | | 4.1.4.3 | | Ensure only authorized groups own audit log flies +| | | | 4.1.4.4 | | Ensure audit log directory is 0750 or more restrictive +| 4.1.4.5 | | | 4.1.4.5 | | Ensure audit config files are mode 640 or more restrictive +| 4.1.4.6 | | | 4.1.4.6 | | Ensure audit config files are owned by user root +| | | | 4.1.4.7 | | Ensure audit config files are owned by group root +| 4.1.4.7 | | | 4.1.4.8 | | Ensure audit tools are mode 0755 or more restrictive +| 4.1.4.9 | | | 4.1.4.9 | | Ensure audit tools are owned by user root +| 4.1.4.10 | | | 4.1.4.10| | Ensure audit tools belong to group root +| 4.2.1.1 | 4.2.1.1 | 4.2.3 | 4.2.2.1 | 4.2.1.1 | rsyslog: Ensure rsyslog is installed +| 4.2.1.2 | 4.2.1.2 | | 4.2.2.2 | 4.2.1.2 | rsyslog: Enable rsyslog +| 4.2.1.3 | 4.2.1.3 | | 4.2.2.3 | | rsyslog: Ensure journald is configured to send messages to rsyslog +| 4.2.1.4 | 4.2.1.4 | 4.2.1.3 | 4.2.2.4 | 4.2.1.4 | rsyslog: Ensure rsyslog default file permissions are configured +| 4.2.1.5 | 4.2.1.5 | 4.2.1.2 | 4.2.2.5 | 4.2.1.3 | rsyslog: Ensure logging is configured |Only runs if the variable rsyslog_file is set, which it is not set by default| +| 4.2.1.6 | 4.2.1.6 | 4.2.1.4 | 4.2.2.6 | 4.2.1.5 | rsyslog: Ensure logging is configured to send logs to remote host |skipped: environment dependent| +| 4.2.1.7 | 4.2.1.7 | 4.1.2.5 | 4.2.2.7 | 4.2.1.6 | rsyslog: Ensure remote rsyslog messages are only accepted on designated log hosts +| | | 4.2.2.1 | | | Ensure syslog-ng is configured |skipped: not a Red Hat package| +| | 4.2.2 | | 4.2.1 | 4.2.2 | journald: Configure journald +| 4.2.2.1 | 4.2.2.1 | | 4.2.1.1 | | journald: Configure journald to send logs to a remote log host +| 4.2.2.1.1| 4.2.2.1.1| | 4.2.1.1.1 | | journald: Ensure systemd-journal-remote is installed +| 4.2.2.1.2| 4.2.2.1.2| | 4.2.1.1.2 | | journald: Ensure systemd-journal-remote is configured +| 4.2.2.1.3| 4.2.2.1.3| | 4.2.1.1.3 | | journald: Ensure systmd-journal-remote is enabled +| 4.2.2.1.4| 4.2.2.1.4| | 4.2.1.1.4 | | journald: Ensure journald is not configured to recieve logs from a remote client +| 4.2.2.2 | 4.2.2.2 | | 4.2.1.2 | | journald: Ensure journald service is enabled +| 4.2.2.3 | 4.2.2.3 | | 4.2.1.3 | 4.2.2.2 | journald: Ensure journald compresses large files +| 4.2.2.4 | 4.2.2.4 | | 4.2.1.4 | 4.2.2.3 | journald: Ensure journald writes to peristent disk +| 4.2.2.5 | 4.2.2.5 | | 4.2.1.5 | 4.2.2.1 | journald: Forward journald logs to rsyslog IF rsyslog is sending logs to a log host +| 4.2.3 | 4.2.3 | | 4.2.3 | | Ensure all log files have appropriate permissions and ownership +| 4.3.0 | 4.3.0 | 4.3.0 || 4.3 | Ensure logrotate is installed and configured +| 5.1.0 | 5.1.0 | | 5.1.0 | | Configure Cron/At +| 5.1.1 | 5.1.1 | 5.1.1 | 5.1.1 | 5.1.1 | Ensure cron is enabled +| 5.1.2 | 5.1.2 | 5.1.2 | 5.1.2 | 5.1.2 | Ensure permissions on /etc/crontab +| 5.1.3 | 5.1.3 | 5.1.3 | 5.1.3 | 5.1.3 | Ensure permissions on /etc/cron.hourly +| 5.1.4 | 5.1.4 | 5.1.4 | 5.1.4 | 5.1.4 | Ensure permissions on /etc/cron.daily +| 5.1.5 | 5.1.5 | 5.1.5 | 5.1.5 | 5.1.5 | Ensure permissions on /etc/cron.weekly +| 5.1.6 | 5.1.6 | 5.1.6 | 5.1.6 | 5.1.6 | Ensure permissions on /etc/cron.monthly +| 5.1.7 | 5.1.7 | 5.1.7 | 5.1.7 | 5.1.7 | Ensure permissions on /etc/cron.d +| 5.1.8 | 5.1.8 | | 5.1.8 | | Ensure cron restricted to authorized users +| 5.1.9 | 5.1.9 | | 5.1.9 | | Ensure at restricted to authorized users +| 5.2.1.0 | 5.2.0 | 5.2.0 | 5.2.0 | 5.2.0 | SSH File configurations +| 5.2.1.1 | 5.2.1 | 5.2.1 | 5.2.1 | 5.2.1 | Set permissions on SSH file +| 5.2.1.2 | 5.2.2 | | 5.2.2 | 5.2.2 | Set Permissions on ssh private host keys +| 5.2.1.3 | 5.2.3 | | 5.2.3 | 5.2.3 | Set Permissions on ssh public host keys +| 5.2.1.4 | 5.2.4 | 5.2.14 | 5.2.4 | 5.2.18 | Ensure SSH access is limited |skipped: environment dependent| +| | | 5.2.2 | | 5.2.4 | Ensure SSH Protocol is set to 2 +| 5.2.1.5 | 5.2.5 | 5.2.3 | 5.2.5 | 5.2.5 | Set LogLevel to INFO +| 5.2.1.6 | 5.2.6 | 5.2.6 | 5.2.6 | 5.2.20 | Ensure SSH PAM is enabled +| 5.2.1.7 | 5.2.7 | 5.2.8 | 5.2.7 | 5.2.10 | Ensure PermitRootLogin is disbled +| 5.2.1.8 | 5.2.8 | 5.2.7 | 5.2.8 | 5.2.9 | Ensure HostbasedAuthentication is disabled +| 5.2.1.9 | 5.2.9 | 5.2.9 | 5.2.9 | 5.2.11 | Ensure SSH PermitEmptyPasswords is disabled +| 5.2.1.10 | 5.2.10 | 5.2.10 | 5.2.10 | 5.2.12 | Ensure PermitUserEnvironment is disabled +| 5.2.1.11 | 5.2.11 | | 5.2.11 | | Ensure IgnoreRhosts is enabled +| 5.2.1.12 | 5.2.12 | 5.2.4 | 5.2.12 | 5.2.6 | Disable X11 forwarding +| 5.2.1.13 | 5.2.13 | | 5.2.16 | 5.2.21 | Disable SSH Forwarding +| 5.2.1.14 | 5.2.14 | | | | Ensure system crypto policy isn't overriden in SSH +| 5.2.1.15 | 5.2.15 | 5.2.15 | 5.2.17 | 5.2.19 | Ensure SSH Banner is configured +| 5.2.1.16 | 5.2.16 | 5.2.5 | 5.2.18 | 5.2.7 | Ensure SSH MaxAuthTires is set +| 5.2.1.17 | 5.2.17 | | 5.2.19 | 5.2.22 | Ensure MaxStartups is configured +| 5.2.1.18 | 5.2.18 | | 5.2.20 | 5.2.23 | Ensure MaxSessions is set to 10 or less +| 5.2.1.19 | 5.2.19 | 5.2.13 | 5.2.21 | 5.2.17 | Ensure SSH LoginGraceTime is set +| 5.2.1.20 | 5.2.20 | 5.2.12 | 5.2.22 | 5.2.16 | Ensure SSH Idle Timeout is configured +| | | | 5.2.13 | 5.2.13 | Ensure only strong Ciphers are used +| | | 5.2.11 | 5.2.14 | 5.2.14 | Ensure only approved MAC alogrithms are used +| 5.3.0 | 5.3.0 | | 5.3.0 | | Configure privilege escalation +| 5.3.1 | 5.3.1 | | 5.3.1 | | Ensure sudo is installed +| 5.3.2 | 5.3.2 | | 5.3.2 | | Ensure sudo commands use pty +| 5.3.3 | 5.3.3 | | 5.3.3 | | Ensure sudo log file exists +| 5.3.4 | 5.3.4 | | 5.3.4 | | Ensure users must provide password for escalation +| 5.3.5 | 5.3.5 | | 5.3.5 | | Ensure re-authentication for privlege ecalation is not disabled globally +| | | | 5.3.6 | | Ensure audo authentication timeout is configured correctly +| 5.3.7 | 5.3.7 | 5.6.0 | 5.3.7 | 5.6 | Restrict su to wheel group | on Ubuntu control says to any one group, but for simplicity we are using wheel | +| 5.5.1 | 5.5.1 | 5.3.1 | 5.4.1 | 5.3.1 | Configure PAM files and password requirements +| 5.5.2 | 5.5.2 | | 5.4.2 | | Ensure lockout for failed password attempts +| 5.5.4 | 5.5.4 | | 5.4.4 | | Configure password hashing algorithm is SHA-512 or YESCRIPT +| 5.6.1.1 | 5.6.1.1 | 5.4.1.1 | 5.5.1.1 | 5.4.1.1 | Ensure password expiration is 365 days or less +| 5.6.1.2 | 5.6.1.2 | 5.4.1.3 | 5.5.1.3 | 5.4.1.3 | Ensure password warning days is set to 7 +| 5.6.1.3 | 5.6.1.3 | 5.4.1.2 | 5.5.1.2 | 5.4.1.2 | Ensure password change days is set to 7 +| 5.6.1.4 | 5.6.1.4 | 5.4.1.4 | 5.5.1.5 | 5.4.1.4 | Disable accounts that are inactive for 30 days after password expiration +| 5.6.3 | 5.6.3 | 5.4.5 | 5.5.5 | 5.4.5 | Ensure default shell timeout is 900 seconds or less +| 5.6.4 | 5.6.4 | 5.4.3 | 5.5.3 | 5.4.3 | Ensure default group for root is GID 0 +| 5.6.5 | 5.6.5 | 5.4.4 | 5.5.4 | 5.4.4 | Ensure default umask is set +| 6.1.1 | 6.1.3 | 6.1.2 | 6.1.1 | 6.1.2 | Ensure permissions on /etc/passwd +| 6.1.2 | 6.1.7 | 6.1.6 | 6.1.2 | 6.1.6 | Ensure permissions on /etc/passwd- +| 6.1.3 | 6.1.5 | 6.1.4 | 6.1.3 | 6.1.4 | Ensure permissions on /etc/group +| 6.1.4 | 6.1.9 | 6.1.8 | 6.1.4 | 6.1.8 | Ensure permissions on /etc/group- +| 6.1.5 | 6.1.4 | 6.1.3 | 6.1.5 | 6.1.3 | Ensure permissions on /etc/shadow +| 6.1.6 | 6.1.8 | 6.1.7 | 6.1.6 | 6.1.7 | Ensure permissions on /etc/shadow- +| 6.1.7 | 6.1.6 | 6.1.5 | 6.1.7 | 6.1.5 | Ensure permissions on /etc/gshadow +| 6.1.8 | 6.1.10 | 6.1.9 | 6.1.8 | 6.1.9 | Ensure permissions on /etc/gshadow- +| 6.1.9 | 6.1.11 | 6.1.10 | 6.1.9 | 6.1.10 | Report if any world writable files exist +| 6.1.10 | 6.1.12 | 6.1.11 | 6.1.10 | 6.1.11 | Report if any unowned files or directories exist +| 6.1.11 | 6.1.13 | 6.1.12 | 6.1.11 | 6.1.12 | Report if any ungrouped files or directories exist +| 6.1.12 | 6.1.2 | 1.1.22 | | 1.1.21 | Ensure sticky bit on world writable directories +| 6.2.1 | 6.2.2 | | 6.2.1 | | Ensure accounts in /etc/passwd use shadowed passwords +| 6.2.2 | 6.2.1 | 6.2.1 | 6.2.2 | 6.2.1 | Report on password fields that are empty +| 6.2.3 | | 6.2.15 | 6.2.3 | 6.2.15 | Ensure al groups in /etc/passwd exist in /etc/group +| | | | 6.2.4 | | Ensure shadow group is empty +| 6.2.4 | 6.2.3 | 6.2.16 | 6.2.5 | 6.2.16 | Report on duplicate UIDs in /etc/passwd +| 6.2.5 | 6.2.4 | 6.2.17 | 6.2.6 | 6.2.17 | Report on duplicate GIDs in /etc/group +| 6.2.6 | 6.2.5 | 6.2.18 | 6.2.7 | 6.2.18 | Report on duplicate users in /etc/passwd +| 6.2.7 | 6.2.6 | 6.2.19 | 6.2.8 | 6.2.19 | Report on duplicate groups in /etc/group +| 6.2.8 | 6.2.7 | 6.2.6 | 6.2.9 | 6.2.7 | Ensure root PATH integrity +| 6.2.9 | 6.2.8 | 6.2.5 | 6.2.10 | 6.2.6 | Report on multiple accounts with UID of 0 +| 6.2.10 | 6.2.10 | 6.2.9 | 6.2.11 | 6.2.9 | Ensure home directories exist |skipped: environment dependent| +| 6.2.11 | 6.2.11 | 6.2.10 | | 6.2.8 | Ensure home directory permissions are 750 or more restrictive (skipped: environment dependent| +| | | 6.2.2 | | 6.2.2 | Ensure no legacy "+" entries exist in /etc/passwd +| | | 6.2.3 | | 6.2.4 | Ensure no legacy "+" entries exist in /etc/shadow +| | | 6.2.4 | | 6.2.5 | Ensure no legacy "+" entries exist in /etc/group +| | | 6.2.10-14| | 6.2.10-14 | Various controls recommended to be run by monitoring software +| | | | | 6.2.20 | Report if shadow group exists in /etc/group diff --git a/docs/controls_list_win.md b/docs/controls_list_win.md index 40d4685..d484e42 100644 --- a/docs/controls_list_win.md +++ b/docs/controls_list_win.md @@ -1,5 +1,6 @@ -Below are the tags used in the CIS roles on Windows Machines. +# cis-security +Below are the tags used in the CIS roles on Windows Machines. | Windows Server 2019 | Control Description |Applies to DC|Applies to Member Server|Applies to Standalone Server| Notes | | -----------------|--------------------- | -----------------|--------------------- | --------------------- |-----------------| @@ -32,8 +33,8 @@ Below are the tags used in the CIS roles on Windows Machines. | 2.2.17 | Ensure symbolic link creation is restricted (domain controller)|X|X|X| | 2.2.18 | Ensure symbolic link creation is restricted (member or standalone server )|X|X|X| | 2.2.19 | Ensure 'Debug programs' is set to 'Administrators'|X|X|X| -| 2.2.20 Deny accesss to this computer from network (domain computer)|X|X|X| -| 2.2.20 Deny accesss to this computer from network (domain computer)|X|X|X| +| 2.2.20 | Deny accesss to this computer from network (domain computer)|X|X|X| +| 2.2.20 | Deny accesss to this computer from network (domain computer)|X|X|X| | 2.2.[22-25] | Ensure deny local, remote, and batch logons includes 'Guests'|X|X|X| | 2.2.28 | Ensure 'Enable computer and user accounts to be trusted for delegation' is set to 'No One'| |X| | | 2.2.29 | Ensure 'Debug programs' is set to 'Administrators'|X|X|X| diff --git a/galaxy.yml b/galaxy.yml index f574047..ff0f399 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,4 +1,4 @@ -### REQUIRED +# REQUIRED # The namespace of the collection. This can be a company/brand/organization or product namespace under which all # content lives. May only contain alphanumeric characters and underscores. Additionally namespaces cannot start with @@ -9,7 +9,7 @@ namespace: dsglaser name: cis_security # The version of the collection. Must be compatible with semantic versioning -version: 1.3.3 +version: 1.5.4 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -17,13 +17,15 @@ readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) # @nicks:irc/im.site#channel' authors: -- David Glaser + - David Glaser ### OPTIONAL but strongly recommended # A short summary description of the collection -description: A collection to implement Center for Internet Security (CIS) controls for RHEL (7-8) and RHEL clones (Oracle, CentOS), SLES 15, Ubuntu 18.04 LTS, Ubuntu 20.04 LTS, Microsoft Windows Server 2019, and Microsoft Windows 10. +description: | + A collection to implement Center for Internet Security (CIS) controls for RHEL (7-9) and RHEL clones + (Oracle, CentOS), SLES 15, Ubuntu [18,20,22].04 LTS, Microsoft Windows Server 2019, and Microsoft Windows 10. # Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only # accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' @@ -42,18 +44,18 @@ tags: [system, security] # collection label 'namespace.name'. The value is a version range # L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version # range specifiers can be set and are separated by ',' -dependencies: - "community.general": "*" - "community.windows": "*" +dependencies: + "community.general": "*" + "community.windows": "*" # The URL of the originating SCM repository repository: https://github.com/dsglaser/cis-security # The URL to any online docs -#documentation: http://docs.example.com +# documentation: http://docs.example.com # The URL to the homepage of the collection/project -#homepage: http://example.com +# homepage: http://example.com # The URL to the collection issue tracker issues: https://github.com/dsglaser/cis-security/issues diff --git a/roles/cis_security/README.md b/roles/cis_security/README.md index 43e79c0..8bfc9b6 100644 --- a/roles/cis_security/README.md +++ b/roles/cis_security/README.md @@ -1,8 +1,8 @@ # cis_security -A collection to implement Center for Internet Security (CIS) controls for RHEL (7-8) and RHEL clones (Oracle, CentOS), SLES 15, and Ubuntu 18.04 LTS and certain Windows servers. +A collection to implement Center for Internet Security (CIS) controls for RHEL (7-9) and RHEL clones (Oracle, CentOS), SLES 15, and Ubuntu (18,22).04 LTS and certain Windows servers. -### Introduction +## Introduction The [Center for Internet Security](https://www.cisecurity.org/) provides a set of security benchmarks for operating systems designed to decrease the vulnerability vectors of a system. @@ -20,7 +20,8 @@ Benchmark Versions: | Operating System | OS Benchmark version | | -----------------|--------------------- | | RHEL 7 | v2.2.0 | -| RHEL 8 | v1.0.0 | +| RHEL 8 | v2.0.0 | +| RHEL 9 | v1.0.0 | CentOS 7 | v2.2.0 | | CentOS 8 | v1.0.0 | | Fedora 31 | \(Fedora 28\) v1.1.0 | @@ -28,6 +29,7 @@ Benchmark Versions: | Oracle Linux 8 | v1.0.0 | | SUSE Linux Enterprise 15 SP1 | \(SUSE Linux Enterprise 12\) v2.1.0 | | Ubuntu 18.04 LTS | v2.0.1 | +| Ubuntu 22.04 LTS | 1.0.0 | | Windows Server 2019 | v1.8.1 | - Some distributions use older CIS benchmarks that were the most recent at the time of creation. Efforts have @@ -35,19 +37,23 @@ been made to update the controls to work with the newer operating systems. Older - SUSE Linux Enterprise 15 SP1 uses the RHEL 7 task file since their controls are so similar. If you want to exclude a SUSE tag, make sure you use the associated RHEL 7 tag number if they are different. Tags can be found in the appropriate controls_list file found in the docs directory. ### Requirements + To implement the collection correctly, you will require the following Control machine: -- Ansible 2.9+ + +- Ansible-core 2.11+ - Machine connected to a package repository source (Satellite or yum repo) Target machine: + - SSH connection with prviiledge escalation on Linux machines. - Python interpreter - WinRM connection with user with admin priviledge for Windows. Alternatively you can use an SSH connection. - PowerShell v3 or higher Collection Requirements: + - ansible.builtin - community.general - ansible.windows @@ -58,12 +64,13 @@ For most of the collection to work, you will need to have a package repo where y the target machine. Registering with Satellite, a package repository, SCM, or a local package collection is recommended before using this, unless you exclude any tags that install packages. ### Use and Care + The collection is designed to run on the machines in the chart above. It may run on other Red Hat and Ubuntu deriviatives, but it has not been tested on them. Upon initiation, the collection will automatically detect the OS and run the appropriate task list. As the role runs, you will see an output listing the control number and a brief description of the task being performed (or skipped): -``` +```text TASK [security-rollup : 1.7.1.3 - Set SELinux policy to targeted] ****************************** ok: [192.168.122.252] ``` @@ -71,16 +78,19 @@ ok: [192.168.122.252] The controls are implemented as Ansible tags. By default all tags are run on a given system. To disable a tag from running, run the playbook with the tag excluded (--skip-tags "x.y.z"): -``` +```text ansible-playbook -i --skip-tags "x.y.z" ``` + Multiple tags can be listed, separated by commas: -``` + +```text ansible-playbook -i --skip-tags "x.y.z,a.b.c" ``` + Note: Some automation tasks handle multiple controls. In the role you may see something like this: -``` +```yaml - name: 6.1.[2,4] - Ensure permissions on /etc/passwd /etc/group file: path: /etc/{{item}} @@ -94,12 +104,13 @@ Note: Some automation tasks handle multiple controls. In the role you may see so - 6.1.2 - 6.1.4 ``` -* In this control, two tags are being processed, '6.1.2' and '6.1.4' if you want this control to not -run, you must exclude both tags: -``` +In this control, two tags are being processed, '6.1.2' and '6.1.4' if you want this control to not run, you must exclude both tags: + +```text ansible-playbook -i --skip-tags "6.1.2,6.1.4" ``` + Some controls are surrouned by Ansible blocks that themselves have tags. Excluding the tag that applies to the block will exclude all of the tasks inside of the block. If the block's tag is **not** excluded, then individual tasks inside of the block can be excluded by excluding their tags. @@ -113,17 +124,19 @@ file. Do not set these values in that file, but create and include your own vari defaults or set them as host variables. ### Idempotency + Every effort has been made to make the controls idempotent, however some Ansible modules do not have the ability to measure every need as currently written and shell or command has been utilized to implement controls. This has the effect of bringing down the quality score on Ansible Galaxy, but the roles can be run multiple times without fear of breaking. ### Learning Tool + A secondary purpose of this collection is to show numerous ways that Ansible can be used to manage systems with various modules. The first time a module is used it is commented on many times to explain what the module is doing. Other times you may see something like the following: -``` +```yaml - name: 5.4.4 - Ensure umask is set replace: path: "{{ item }}" @@ -148,16 +161,19 @@ to explain what the module is doing. Other times you may see something like the tags: - 5.4.5 ``` + Both of these tasks manipulate the same file in the same way. They could have been written with the same module, even in the same task with a loop, but here it illustrates different ways files can be manipuldated with modules. - ### Change Log + - 1/20/2020 - dsglaser@gmail.com - Initial creation - 1/22/2020 - dsglaser@gmail.com - Added enhanced selinux controls - 2/18/2020 - dsglaser@gmail.com - Added support for Ubuntu 18.04 LTS, added RHEL clone links - 2/20/2020 - dsglaser@gmail.com - Fixed numerous tests and rearranged network controls - 2/25/2020 - dsglaser@gmail.com - Added SLES 15 SP 1 support - 3/17/2020 - dsglaser@gmail.com - Added Windows 2019 support -- 7/24/2022 - dsglaser@gmail.com - Coversion to full collection status (Namespace: dsglaser) \ No newline at end of file +- 7/24/2022 - dsglaser@gmail.com - Coversion to full collection status (Namespace: dsglaser) +- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0o +- 5/2/2023 - dsglaser@gmail.com - Added support for Ubuntu 22.04 LTS diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index 8f7d067..f3de6d6 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -11,16 +11,22 @@ crypto_policy: "DEFAULT" # Options are DEFAULT, FIPS, or LEGACY apparmor_level: "enforce" # Set all profiles in /etc/apparmor.d/* to this level. Options are enforce or complain # Network Time Services -time_service: "chrony" # Linux: Option for RHEL7/Ubuntu only. RHEL8 only ships with chrony and this variable is not used - # Linux: Choices are 'ntp' or chrony'. For Ubuntu, this can also accept 'none' as an option to use only systemd timedatectl +time_service: "chrony" # Linux: Option for RHEL7/Ubuntu only; options are 'chrony' or 'timesyncd'. + # RHEL8/9 only ships with chrony and this variable is not used + # Linux: Choices are 'ntp' or chrony'. For Ubuntu, this can also accept 'none' as an option to use only systemd timedatectl time_server: "2.rhel.pool.ntp.org" # Linux: Time server for ntp, chrony, or timedatectl time_operators: # Windows: Users that can set the system time - "Administrators" - "LOCAL SERVICE" +time_user: "_chrony" # Linux: Ubuntu 22 only, user to run time daemon as # Sudo Configuration sudo_log: "/var/log/sudoers" # Linux: log file for sudo +# GDM Timeout Configuration +idle_delay: 900 # Seconds of inactivity before locking screen +lock_delay: 5 # Seconds of blank screen before locking + # AIDE configuration settings aide_db_name: "/var/lib/aide/aide.db.gz" # Linux: Database name that AIDE will look for for comparison aide_new_db_name: "/var/lib/aide/aide.db.new.gz" # Linux: Database name created during an --init run @@ -48,33 +54,56 @@ tftp_server: false # Linux: TFTPd Server. Option for RHEL7 only ypbind: false graphical_interface: false # Whether to disable the GDM greeter service. The service will disabled on 'false' -# Rsyslog service +# Services +service_bluetooth: # Linux: Ubuntu 22 only. Set to 'false' to disable bluetooth service + +# Logging services variables +log_service: "journald" # journald or rsyslog for logging. Choose one. Currently only implemented in RHEL 8/9! +remote_log_service: false # Whether to configure journald to start systemd-journal-remote.service log_host: false # Linux: Whether this machine will host rsyslog messages for other machines log_port: 514 # Linux: Port to listen to RSYSLOG messages on (if log_host is true) +log_file: /var/log/audit/auditd.log # Linux: path and location of audit log log_file_size: 8 # Linux: log file size. RHEL default is 8MB, control has no default -# rsyslog_file: # Linux: Uncomment to copy file listed to /etc/rsyslog.d +log_group: root # Group that owns the auditd log files (root or adm) RHEL 8/9 +rsyslog_file: # Linux: Copy file listed for configuring rsyslog +logrotate_file: # Linux: RHEL 8/9, Copy file listed for logrotate # network security settings tcpwrappers: false # Linux: Configure tcpwrappers controls. RHEL 7 control only tcpwrappers_pkg: "tcp_wrappers" # Linux: Name of tcp wrappers package in repository -enable_firewall: firewalld # Linux: supported values are firewalld or iptables +enable_firewall: firewalld # Linux: supported values are firewalld (RHEL), nftables (RHEL, Ubuntu) or iptables (Ubuntu) firewalld_default_zone: public # Linux: default firwall zone +motd_use: true # Linux: RHEL 9: set to 'true' to use motd file, 'false' to not use motd_file: "banner" # Linux: File location by default in 'files' directory +issue_use: true # Linux: RHEL 9: set to 'true' to use issue file, 'false' to not use issue_file: "issue" # Linux: File location by default in 'files' directory -issue.net_file: "issue" # Linux: File location by default in 'files' diretory +issue_net_use: true # Linux: RHEL 9: set to 'true' to use issue.net file, 'false' to not use +issue_net_file: "issue" # Linux: File location by default in 'files' diretory ipv6_disable: true # Common: Set to true to disable ipv6 support on host +# Cron/at variables +cron_allow: [] # Linux: RHEL9, list of users to add to cron.allow +at_allow: [] # Linux: RHEL9, list of users to add to at_allow.allow + # SSH Server settings ssh_log_level: INFO # Linux: Control is INFO or VERBOSE. Stricter is not approved and more verbose exposes user info ssh_max_auth_tries: 4 # Linux: Control is 4 retries -ssh_mac_list: "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com" # Linux: RHEL7 only control -ssh_ciphers_list: "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" # Linux: Ubuntu control only -ssh_kex_list: "curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" # Linux: Ubuntu control only +ssh_mac_list: "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com" + # Linux: RHEL7 only control +ssh_ciphers_list: "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" + # Linux: Ubuntu control only +ssh_kex_list: + - 'curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1' + # Linux: Ubuntu control only ssh_alive_interval: 300 # Linux: Control is 5 minutes ssh_alive_count_max: 0 # Linux: Control is 0 count ssh_grace_time: 60 # Linux: Control is for 60 seconds or less ssh_max_sessions: 4 # Linux: Control is 4 sessions from a single host ssh_login_banner: issue.net # Linux: a file with no path will exist in the role's files directory, an absolute path will exist on the control host +ssh_allowed_users: "" # Linux: RHEL9, space separated list of users to add to AllowUsers list +ssh_allowed_groups: "" # Linux: RHEL9, space separated list of users to add to DenyUsers list +ssh_denied_users: "root" # Linux: RHEL9, space separated list of users to add to AllowGroups list +ssh_denied_groups: "" # Linux: RHEL9, space separated list of users to add to DneyGroups list # Password and account settings, all settings below match controls password_min_length: 14 # Common @@ -82,19 +111,23 @@ password_req_digit: true # Linux password_req_upper: true # Linux password_req_lower: true # Linux password_req_special: true # Linux +password_hash_alg: "yescrypt" # Linux (RHEL 8/9), set password hashing algorithm, set to 'sha512' or 'yescrypt' password_min_days: 7 # Common: Windows has this control listed as 1 day password_expire_days: 365 # Common: Windows has this control listed as 24 days password_warning_days: 7 # Common: Windows has this control listed as 'between 5 and 14 days' password_inactive_lock_days: 30 # Linux +password_failed_attempts: 5 # RHEL 9: Number of attempts before locking account +password_failed_time: 900 # RHEL 9: amount of time to lock an account that has exceeded failed attemps password_history: 24 # Windows: number of passwords to remember. +password_max_repeat: 3 # Linux: Ubuntu 22 only, number of same characters in a passwd account_lockout_duration: 15 # Windows: account_lockout_threshold: 10 # Windows: account_reset_duration: 15 # Windows: cached_logins: 4 # Windows: Number of login credentials to cache enable_guest: false # Windows: enable guest account rename_guest: "Guest" # Windows: name of guest account. Will have no effect if enable_gues variable is set to false -enable_Administrator: true # Windows: enable Administrator account -rename_Administrator: "Administrator" # Windows: name of Administrator account. Will have no effect if enable_Administrator variable is set to false +enable_administrator: true # Windows: enable Administrator account +rename_administrator: "Administrator" # Windows: name of Administrator account. Will have no effect if enable_Administrator variable is set to false dc_network_logon_right: # Windows: Users that have access to log on from the network (domain controllers) - "Administrators" - "Authenticated Users" diff --git a/roles/cis_security/files/banner b/roles/cis_security/files/banner index 435d9a4..eb110ba 100644 --- a/roles/cis_security/files/banner +++ b/roles/cis_security/files/banner @@ -1,2 +1 @@ Authorized uses only. All activity may be monitored and reported. - diff --git a/roles/cis_security/files/duplicate_groups.sh b/roles/cis_security/files/duplicate_groups.sh index 04d340d..062d927 100755 --- a/roles/cis_security/files/duplicate_groups.sh +++ b/roles/cis_security/files/duplicate_groups.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat /etc/group | cut -f1 -d":" | sort -n | uniq -c | while read x ; do [ -z "${x}" ] && break set - $x diff --git a/roles/cis_security/files/duplicate_guids.sh b/roles/cis_security/files/duplicate_guids.sh index 2c2e006..0566899 100755 --- a/roles/cis_security/files/duplicate_guids.sh +++ b/roles/cis_security/files/duplicate_guids.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat /etc/group | cut -f3 -d":" | sort -n | uniq -c | while read x ; do [ -z "${x}" ] && break set - $x diff --git a/roles/cis_security/files/duplicate_uids.sh b/roles/cis_security/files/duplicate_uids.sh index d690abf..4f51b8b 100755 --- a/roles/cis_security/files/duplicate_uids.sh +++ b/roles/cis_security/files/duplicate_uids.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat /etc/passwd | cut -f3 -d":" | sort -n | uniq -c | while read x ; do [ -z "${x}" ] && break set - $x diff --git a/roles/cis_security/files/duplicate_users.sh b/roles/cis_security/files/duplicate_users.sh index a69c978..d909cd4 100755 --- a/roles/cis_security/files/duplicate_users.sh +++ b/roles/cis_security/files/duplicate_users.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat /etc/passwd | cut -f1 -d":" | sort -n | uniq -c | while read x ; do [ -z "${x}" ] && break set - $x diff --git a/roles/cis_security/files/issue b/roles/cis_security/files/issue index 435d9a4..eb110ba 100644 --- a/roles/cis_security/files/issue +++ b/roles/cis_security/files/issue @@ -1,2 +1 @@ Authorized uses only. All activity may be monitored and reported. - diff --git a/roles/cis_security/files/non_existant_homedirs.sh b/roles/cis_security/files/non_existant_homedirs.sh index 8e1c69c..6d47451 100644 --- a/roles/cis_security/files/non_existant_homedirs.sh +++ b/roles/cis_security/files/non_existant_homedirs.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash awk -F: '($1!~/(halt|sync|shutdown|nfsnobody)/ && $7!~/^(\/usr)?\/sbin\/nologin(\/)?$/ && $7!~/(\/usr)?\/bin\/false(\/)?$/) { print $1 " " $6 }' /etc/passwd | while read -r user dir; do if [ ! -d "$dir" ]; then echo "User: \"$user\" home directory: \"$dir\" does not exist." fi -done \ No newline at end of file +done diff --git a/roles/cis_security/files/path_check.sh b/roles/cis_security/files/path_check.sh index 71a75e6..9247af7 100755 --- a/roles/cis_security/files/path_check.sh +++ b/roles/cis_security/files/path_check.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ## Find issues in user's PATH variable if [ "`echo $PATH | grep ::`" != "" ]; then diff --git a/roles/cis_security/files/rsyslog.conf b/roles/cis_security/files/rsyslog.conf new file mode 100644 index 0000000..56217be --- /dev/null +++ b/roles/cis_security/files/rsyslog.conf @@ -0,0 +1,48 @@ +# Default rules for rsyslog. +# +# For more information see rsyslog.conf(5) and /etc/rsyslog.conf + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +*.*;auth,authpriv.none -/var/log/syslog +#cron.* /var/log/cron.log +#daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +#lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +#user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +#mail.info -/var/log/mail.info +#mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Some "catch-all" log files. +# +#*.=debug;\ +# auth,authpriv.none;\ +# news.none;mail.none -/var/log/debug +#*.=info;*.=notice;*.=warn;\ +# auth,authpriv.none;\ +# cron,daemon.none;\ +# mail,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg :omusrmsg:* + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 diff --git a/roles/cis_security/files/undefined_groups.sh b/roles/cis_security/files/undefined_groups.sh index 6eec41f..b7987c9 100755 --- a/roles/cis_security/files/undefined_groups.sh +++ b/roles/cis_security/files/undefined_groups.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash for i in $(cut -s -d: -f4 /etc/passwd | sort -u ); do grep -q -P "^.*?:[^:]*:$i:" /etc/group if [ $? -ne 0 ]; then diff --git a/roles/cis_security/handlers/main.yml b/roles/cis_security/handlers/main.yml index 92602ac..0060769 100644 --- a/roles/cis_security/handlers/main.yml +++ b/roles/cis_security/handlers/main.yml @@ -5,104 +5,107 @@ # command rather than the service module (which uses systemd) # https://access.redhat.com/solutions/2664811 -- name: restart auditd - ansible.builtin.service: - name: auditd - state: restarted - use: service +- name: Restart auditd + ansible.builtin.command: service auditd restart when: ansible_os_family != "windows" - listen: "restart auditd" + listen: "Restart auditd" # reboot a machine. Give it 10 minutes to come up in case it # needs to do a selinux relabel -- name: reboot +- name: Reboot ansible.builtin.reboot: - reboot_timeout: 600 +# reboot_timeout: 600 -- name: restart ntpd +- name: Restart ntpd ansible.builtin.service: name: ntpd state: restarted -- name: restart sshd +- name: Restart sshd ansible.builtin.service: name: sshd state: restarted -- name: restart chronyd +- name: Restart chronyd ansible.builtin.service: name: chronyd state: restarted -- name: restart rsyslog +- name: Restart rsyslog ansible.builtin.service: name: rsyslog state: restarted -- name: restart postfix +- name: Restart journald + ansible.builtin.service: + name: systemd-journald + state: restarted + +- name: Restart postfix ansible.builtin.service: name: postfix state: restarted -- name: start firewalld +- name: Start firewalld ansible.builtin.service: name: firewalld state: started -- name: restart firewalld +- name: Restart firewalld ansible.builtin.service: name: firewalld state: restarted -- name: start iptables +- name: Start iptables ansible.builtin.service: name: iptables state: started -- name: restart tmpfs +- name: Restart tmpfs ansible.builtin.systemd: name: tmp.mount state: restarted - enabled: True - masked: False - daemon_reload: True + enabled: true + masked: false + daemon_reload: true # Call the grub config file rebuilding program # There is no grub module, so we have to do it with shell -- name: rebuild grub +- name: Rebuild grub ansible.builtin.command: /usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg # Call the grub config file rebuilding program on ubuntu # There is no grub module, so we have to do it with shell -- name: rebuild ubuntu-grub +- name: Rebuild ubuntu-grub ansible.builtin.command: /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg -- name: update crypto_policy +- name: Update crypto_policy ansible.builtin.command: /usr/bin/update-crypto-policies -- name: restart aidecheck +- name: Restart aidecheck ansible.builtin.systemd: name: aidecheck - enabled: True + enabled: true + daemon_reload: true state: restarted -- name: flush network routes - ansible.builtin.sysctl: +- name: Flush network routes + ansible.posix.sysctl: name: "{{ item }}" value: "1" - reload: True + reload: true state: present - sysctl_set: True + sysctl_set: true loop: - net.ipv4.route.flush - net.ipv6.route.flush -- name: restart timesyncd +- name: Restart timesyncd ansible.builtin.systemd: name: systemd.timesyncd - enabled: True + enabled: true state: restarted -- name: restart ufw +- name: Restart ufw community.general.ufw: state: enabled diff --git a/roles/cis_security/tasks/CIS-Oracle-8.yml b/roles/cis_security/tasks/CIS-OracleLinux-8.yml similarity index 100% rename from roles/cis_security/tasks/CIS-Oracle-8.yml rename to roles/cis_security/tasks/CIS-OracleLinux-8.yml diff --git a/roles/cis_security/tasks/CIS-RedHat-9.yml b/roles/cis_security/tasks/CIS-RedHat-9.yml new file mode 100644 index 0000000..6237eca --- /dev/null +++ b/roles/cis_security/tasks/CIS-RedHat-9.yml @@ -0,0 +1,4 @@ +--- +# indclude the type file for RHEL 8 type machines + +- include: type-files/redhat-9-type.yml diff --git a/roles/cis_security/tasks/CIS-Ubuntu-22.yml b/roles/cis_security/tasks/CIS-Ubuntu-22.yml new file mode 100644 index 0000000..ffb41d3 --- /dev/null +++ b/roles/cis_security/tasks/CIS-Ubuntu-22.yml @@ -0,0 +1,4 @@ +--- +# indclude the type file for Ubuntu 18 type machines + +- include: type-files/ubuntu-22-type.yml diff --git a/roles/cis_security/tasks/main.yml b/roles/cis_security/tasks/main.yml index 5d356cd..e091178 100644 --- a/roles/cis_security/tasks/main.yml +++ b/roles/cis_security/tasks/main.yml @@ -1,4 +1,5 @@ --- # tasks file for cis-security -- include: CIS-{{ ansible_distribution }}-{{ ansible_distribution_major_version | replace("Evaluation", "") }}.yml +- name: include appropriate type file + ansible.builtin.include_tasks: CIS-{{ ansible_distribution }}-{{ ansible_distribution_major_version | replace("Evaluation", "") }}.yml diff --git a/roles/cis_security/tasks/type-files/MS-Server-type.yml b/roles/cis_security/tasks/type-files/MS-Server-type.yml index 9b3c61e..a905ec2 100644 --- a/roles/cis_security/tasks/type-files/MS-Server-type.yml +++ b/roles/cis_security/tasks/type-files/MS-Server-type.yml @@ -568,8 +568,8 @@ community.windows.win_security_policy: section: System Access key: NewAdministratorName - value: '{{ rename_Administrator }}' - when: (enable_Administrator | lower) == "true" + value: '{{ rename_administrator }}' + when: (enable_administrator | lower) == "true" tags: - 2.3.1.5 @@ -577,7 +577,7 @@ community.windows.win_security_policy: section: System Access key: NewGuestName - value: '{{ rename_Administrator }}' + value: '{{ rename_administrator }}' when: (enable_guest | lower) == "true" tags: - 2.3.1.6 diff --git a/roles/cis_security/tasks/type-files/SLES-addons.yml b/roles/cis_security/tasks/type-files/SLES-addons.yml index 392a0fa..ec197c4 100644 --- a/roles/cis_security/tasks/type-files/SLES-addons.yml +++ b/roles/cis_security/tasks/type-files/SLES-addons.yml @@ -24,7 +24,7 @@ replace: "" with_items: - apparmor=0 - notify: rebuild grub + notify: Rebuild grub tags: - 1.6.2.1 @@ -137,7 +137,7 @@ tags: - 5.4.4 -- name: 5.4.5 - Ensure shell timeout is {{ shell_timeout }} seconds or less (SLES) +- name: 5.4.5 - Ensure shell timeout is set (SLES) ansible.builtin.blockinfile: path: "{{ item }}" block: "TMOUT={{ shell_timeout }}" diff --git a/roles/cis_security/tasks/type-files/redhat-7-type.yml b/roles/cis_security/tasks/type-files/redhat-7-type.yml index fbe0b68..92ef6fc 100644 --- a/roles/cis_security/tasks/type-files/redhat-7-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-7-type.yml @@ -11,1977 +11,1973 @@ # Comments about how the modules are used will become more infrequent as # the file goes along to avoid repeating oneself. - # Let the user know what version of the controls file is running - # Use a variable so it prints out the correct version. - - name: Print Header - ansible.builtin.debug: msg="CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }} type systems" - - # Collect the packages installed on the system so we can check agains them later - - name: Collect package list - ansible.builtin.package_facts: - manager: auto - tags: - - always - - # Find the minimum UID of the machine for normal acocunts. This varies - # between machines and environments, so we pull it from the file it - # is supposed to exist in. - - name: Determine the Minimum UID for new, non-system, accounts - ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" - register: min_uid - changed_when: min_uid.rc == "2" - check_mode: false - tags: - - always - - # Update the system with security packages using the system's package manager - # Only update the system if the 'update_system' variable is set to true - - name: 1.8 - Ensure updated system - ansible.builtin.package: - name: "*" - state: latest - security: true - when: update_system - tags: - - 1.8.0 - - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: 1.1 Disable unused filesystems - ansible.builtin.set_fact: - unused_filesystems: [] - - - name: 1.1.1.1 - Add cramfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'cramfs' ] }}" - tags: - - 1.1.1.1 - - - name: 1.1.1.2 - Add freevxfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'freevxfs' ] }}" - tags: - - 1.1.1.2 - - - name: 1.1.1.3 - Add jffs2 to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'jffs2' ] }}" - tags: - - 1.1.1.3 - - - name: 1.1.1.4 - Add hfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'hfs' ] }}" - tags: - - 1.1.1.4 - - - name: 1.1.1.5 - Add hfsplus to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'hfsplus' ] }}" - tags: - - 1.1.1.5 - - - name: 1.1.1.6 - Add squashfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'squashfs' ] }}" - tags: - - 1.1.1.6 - - - name: 1.1.1.7 - Add udf to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'udf' ] }}" - tags: - - 1.1.1.7 - - - name: 1.1.1.8 - Add vfat to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'vfat' ] }}" - tags: - - 1.1.1.8 - - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: Remove unused_filesystem list - ansible.builtin.package: - name: unused_filesystems - state: absent - - - name: Add unused_filesystems to /etc/modprobe.d/CIS.conf - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" - state: present - create: true - owner: root - group: root +# Let the user know what version of the controls file is running +# Use a variable so it prints out the correct version. +- name: Print Header + ansible.builtin.debug: + msg: "CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }} type systems" + +# Collect the packages installed on the system so we can check agains them later +- name: Collect package list + ansible.builtin.package_facts: + manager: auto + tags: + - always + +# Find the minimum UID of the machine for normal acocunts. This varies +# between machines and environments, so we pull it from the file it +# is supposed to exist in. +- name: Determine the Minimum UID for new, non-system, accounts + ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" + register: min_uid + changed_when: min_uid.rc == "2" + check_mode: false + tags: + - always + +# Update the system with security packages using the system's package manager +# Only update the system if the 'update_system' variable is set to true +- name: 1.8 - Ensure updated system + ansible.builtin.package: + name: "*" + state: latest + security: true + when: update_system + tags: + - 1.8.0 + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: 1.1 Disable unused filesystems + ansible.builtin.set_fact: + unused_filesystems: [] + +- name: 1.1.1.1 - Add cramfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['cramfs'] }}" + tags: + - 1.1.1.1 + +- name: 1.1.1.2 - Add freevxfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['freevxfs'] }}" + tags: + - 1.1.1.2 + +- name: 1.1.1.3 - Add jffs2 to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['jffs2'] }}" + tags: + - 1.1.1.3 + +- name: 1.1.1.4 - Add hfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['hfs'] }}" + tags: + - 1.1.1.4 + +- name: 1.1.1.5 - Add hfsplus to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['hfsplus'] }}" + tags: + - 1.1.1.5 + +- name: 1.1.1.6 - Add squashfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['squashfs'] }}" + tags: + - 1.1.1.6 + +- name: 1.1.1.7 - Add udf to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['udf'] }}" + tags: + - 1.1.1.7 + +- name: 1.1.1.8 - Add vfat to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + ['vfat'] }}" + tags: + - 1.1.1.8 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: Remove unused_filesystem list + ansible.builtin.package: + name: unused_filesystems + state: absent + +- name: Add unused_filesystems to /etc/modprobe.d/CIS.conf + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install {{ item }} /bin/true" + state: present + create: true mode: 0644 - with_items: - - "{{ unused_filesystems }}" - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - - - name: 1.1.2 - /tmp partition and mount options - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.2 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - tags: - - 1.1.2 - - 1.1.3 - - 1.1.4 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.2 - Determine if /tmp is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - mount_options: "{{ item.options }}" - when: item.mount == "/tmp" - with_items: - - "{{ ansible_mounts }}" - tags: - - 1.1.2 - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.2 - Report to user if not on separate partition - ansible.builtin.debug: - msg: "FAILED CONTROL: /tmp is not on a separate partition. Skipping mount option checks" - when: mount_count == 0 - changed_when: true - tags: - - 1.1.2 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.3 - Report to user if /tmp does not have noexec set - ansible.builtin.debug: - msg: "FAILED CONTROL: /tmp/ does not have noexec set" - when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.3 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.4 - Report to user if /tmp does not have nodev set - ansible.builtin.debug: - msg: "FAILED CONTROL: /tmp/ does not have nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.4 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.5 - Report to user if tmp does not have nosuid set - ansible.builtin.debug: - msg: "FAILED CONTROL: /tmp does not have nosuid set" - when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.5 - - # This whole block can be turned off by excluding the following tag(s) - tags: - 1.1.2 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.6 - Report if /var is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.6 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.6 - Determine if /var is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.6 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.6 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.7 - /var/tmp partition and mount options - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.7 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - tags: - - 1.1.7 - - 1.1.8 - - 1.1.9 - - 1.1.10 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.7 - Determine if /var/tmp is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - mount_options: "{{ item.options }}" - when: item.mount == "/var/tmp" - with_items: - - "{{ ansible_mounts }}" - tags: - - 1.1.7 - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.7 - Report to user if not on separate partition - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" - when: mount_count == 0 - changed_when: true - tags: - - 1.1.7 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.8 - Report to user if /var/tmp does not have nodev set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.8 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.9 - Report to user if /var/tmp does not have nosuid set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" - when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.9 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.10 - Report to user if /var/tmp does not have noexec set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" - when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.10 - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.7 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.11 - Report if /var/log is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.11 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.11 - Determine if /var/log is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var/log" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.11 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/log is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.11 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.12 - Report if /var/log/audit is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.12 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.12 - Determine if /var/log/audit is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var/log/audit" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.12 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.12 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.13 - Report if /home is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.13 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.13 - Determine if /home is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - mount_options: "{{ item.options }}" - when: item.mount == "/home" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.13 - Report to user if /home is not on a separate partition - ansible.builtin.debug: - msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" - when: mount_count == 0 - changed_when: true - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: Report to user if /home does not have nodev set - ansible.builtin.debug: - msg: "FAILED CONTROL: /home does not have nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.13 - - # /dev/shm does not exist in ansible_mounts so we have to check the - # mount ansible.builtin.command directly. This requires the use of the shell ansible.builtin.command which - # is not ideal. - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.15 - Report if /dev/shm does not have nodev set - block: - - name: Determine if /dev/shm has nodev set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nodev - register: devshm_nodev_out - failed_when: devshm_nodev_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.15 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have nodev set" - when: devshm_nodev_out is defined and devshm_nodev_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.15 - - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.16 - Report if /dev/shm does not have nosuid set - block: - - name: Determine if /dev/shm has nosuid set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nosuid - register: devshm_nosuid_out - failed_when: devshm_nosuid_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.16 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have nosuid set" - when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.16 - - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.17 - Report if /dev/shm does not have noexec set - block: - - name: 1.1.17 - Determine if /dev/shm has noexec set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v noexec - register: devshm_noexec_out - failed_when: devshm_noexec_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.17 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have noexec set" - when: devexec_nosuid_out is defined and devshm_noexec_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.17 + with_items: + - "{{ unused_filesystems }}" + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem + + +- name: 1.1.2 - /tmp partition and mount options + # This whole block can be turned off by excluding the following tag(s) + tags: + 1.1.2 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.2 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + tags: + - 1.1.2 + - 1.1.3 + - 1.1.4 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.2 - Determine if /tmp is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/tmp" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.2 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.2 - Report to user if not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /tmp is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.3 - Report to user if /tmp does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /tmp/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4 - Report to user if /tmp does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /tmp/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.4 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.5 - Report to user if tmp does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /tmp does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.5 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.6 - Report if /var is not on a separate partition + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.6 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.6 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.6 - Determine if /var is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var" + with_items: + - "{{ ansible_mounts }}" + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.6 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /var is not on a separate partition" + when: mount_count == 0 + changed_when: true + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.7 - /var/tmp partition and mount options + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.7 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.7 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + tags: + - 1.1.7 + - 1.1.8 + - 1.1.9 + - 1.1.10 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.7 - Determine if /var/tmp is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/tmp" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.7 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.7 - Report to user if not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.7 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.8 - Report to user if /var/tmp does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.8 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.9 - Report to user if /var/tmp does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.9 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.10 - Report to user if /var/tmp does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.10 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.11 - Report if /var/log is not on a separate partition + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.11 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.11 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.11 - Determine if /var/log is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var/log" + with_items: + - "{{ ansible_mounts }}" + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.11 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log is not on a separate partition" + when: mount_count == 0 + changed_when: true + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.12 - Report if /var/log/audit is not on a separate partition + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.12 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.12 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.12 - Determine if /var/log/audit is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var/log/audit" + with_items: + - "{{ ansible_mounts }}" + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.12 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" + when: mount_count == 0 + changed_when: true + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.13 - Report if /home is not on a separate partition + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.13 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.13 - Set/reset mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.13 - Determine if /home is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/home" + with_items: + - "{{ ansible_mounts }}" + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.13 - Report to user if /home is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: Report to user if /home does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + +# /dev/shm does not exist in ansible_mounts so we have to check the +# mount ansible.builtin.command directly. This requires the use of the shell ansible.builtin.command which +# is not ideal. +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.15 - Report if /dev/shm does not have nodev set + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.15 + block: + - name: Determine if /dev/shm has nodev set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nodev + register: devshm_nodev_out + failed_when: devshm_nodev_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.15 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nodev set" + when: devshm_nodev_out is defined and devshm_nodev_out.stdout + changed_when: true + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.16 - Report if /dev/shm does not have nosuid set + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.16 + block: + - name: Determine if /dev/shm has nosuid set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nosuid + register: devshm_nosuid_out + failed_when: devshm_nosuid_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.16 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nosuid set" + when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout + changed_when: true + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.17 - Report if /dev/shm does not have noexec set + # This whole block can be turned off by excluding the following tag(s) + tags: + - 1.1.17 + block: + - name: 1.1.17 - Determine if /dev/shm has noexec set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v noexec + register: devshm_noexec_out + failed_when: devshm_noexec_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.17 - Report to user + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have noexec set" + when: devexec_nosuid_out is defined and devshm_noexec_out.stdout + changed_when: true # Control 1.1.18, 1.1.19, 1.1.20 are for removable media -# REDO TO REPORT AND ACTION - # Finda all local filesystem directories and set the sticky bit on world writable ones -# - name: 1.1.21 - Ensure sticky bit is set on world-writeable directories -# ansible.builtin.shell: set -o pipefail ; /usr/bin/df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null | xargs -I '{}' chmod a+t '{}' -# changed_when: false -# tags: -# - 1.1.21 - - # Turn off and disable the autofs service using the service module. - # We check to see if the package that autofs belongs to (convienently called autofs) - # exists in the ansible_facts.packages list we gathered early in the play - - name: 1.1.22 - disable automounting - ansible.builtin.service: - name: autofs - enabled: false - state: stopped - when: "'autofs' in ansible_facts.packages" - tags: - - 1.1.22 +# Control 1.1.21 is a large find, omitting + +# Turn off and disable the autofs service using the service module. +# We check to see if the package that autofs belongs to (convienently called autofs) +# exists in the ansible_facts.packages list we gathered early in the play +- name: 1.1.22 - disable automounting + ansible.builtin.service: + name: autofs + enabled: false + state: stopped + when: "'autofs' in ansible_facts.packages" + tags: + - 1.1.22 # Control 1.2.1 and 1.2.4 are system updating. Make sure system is set for some kind of system software update - # Use the service module to disable the rhnsd service. If you want the machine - # to respond to queued services from Satellite, do not disable this. - - name: 1.2.5 Disable rhnsd - ansible.builtin.service: - name: rhnsd - enabled: false - state: stopped - ignore_errors: true # Remove for RHEL - when: ansible_distribution == "RedHat" - tags: - - 1.2.5 - - # GPGKeys are used to sign packages. enabling them will mean that all packages - # from a given repo must be signed with the appropriate key - - name: 1.2.[2,3] - Ensure GPG keys are configured - block: - # Replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' - - name: 1.2.2 - set master yum.conf gpgcheck to '1' - ansible.builtin.replace: - dest: /etc/yum.conf - regexp: '^gpgcheck\s*=\s*[^1]*$' - replace: "gpgcheck = 1" - when: gpgcheck is defined and gpgcheck - - # Find all files in /etc/yum.repos.d and add them to a list variable - - name: 1.2.3 - find all repo files in /etc/yum.repos.d/ - ansible.builtin.find: - paths: "/etc/yum.repos.d" - patterns: "*.repo" - register: yumrepos - when: gpgcheck is defined and gpgcheck - - # parse the list variable and replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' - - name: 1.2.3 - Set all repos gpgchecks to '1' - ansible.builtin.replace: - dest: "{{ item.path }}" - regexp: '^gpgcheck\s*=\s*[^1]*$' - replace: gpgcheck = 1 - with_items: "{{ yumrepos.files }}" - when: gpgcheck is defined and gpgcheck - when: ansible_distribution != "SLES" - tags: - - 1.2.2 - - 1.2.3 - - # - name: 1.2.1 - Ensure package manager repositories are configured, site dependent - - - # AIDE is a file system integrity checker which will document all - # filesystem changes. It's very noisy on busy systems and should be - # enabled when you have the sapce and need for it. - - name: 1.3 - Filesystem integrity checking w/AIDE - block: - # use the system package manager to install AIDE - - name: 1.3.1 Ensure aide is installed - ansible.builtin.package: - name: aide - state: present - tags: - - 1.3.1 - - # AIDE requires initialization the first time and it takes time on a large system. - # DUse stat module on the file that should be there if it is set up. - - name: 1.3.1 - Determine if AIDE has already been initialized - ansible.builtin.stat: - path: /var/lib/aide/aide.db.gz - register: aide_path - tags: - - 1.3.1 - - - name: 1.3.1 - Set up database file location - ansible.builtin.replace: - dest: /etc/aide.conf - regexp: "^database=ansible.builtin.file:((?!{{ aide_db_name }}).)*$" - replace: "database=ansible.builtin.file:{{ aide_db_name }}" - tags: - - 1.3.1 - - - name: 1.3.1 - Set up database_out file location - ansible.builtin.replace: - dest: /etc/aide.conf - regexp: "^database_out=ansible.builtin.file:((?!{{ aide_new_db_name }}).)*$" - replace: "database_out=ansible.builtin.file:{{ aide_new_db_name }}" - tags: - - 1.3.1 - - - name: 1.3.1 - enable gzip compression for database - ansible.builtin.lineinfile: - dest: /etc/aide.conf - regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' - line: "gzip_dbout={{ aide_gzip }}" - state: present - tags: - - 1.3.1 - - # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' - # is true if the file is a regular file. If either of these are not true, then - # run the initializatoin again. - - name: 1.3.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aide) - ansible.builtin.command: /usr/sbin/aide --init - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - register: aide - async: 1200 # 20 minutes until timeout - poll: 0 # run concurrently - tags: - - 1.3.1 - - - name: Wait for AIDE initialization to complete - ansible.builtin.async_status: jid={{ aide.ansible_job_id }} - register: aide_status - until: aide_status.finished - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - retries: 300 - - # AIDE creates the new database as a different name. Use the copy module with - # the remote_src argument to copy the file on the remote machine to another location - # on the remote machine. - - name: 1.3.1 - Move the newly created database into place - ansible.builtin.copy: - src: /var/lib/aide/aide.db.new.gz - remote_src: true - dest: /var/lib/aide/aide.db.gz - mode: preserve - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - changed_when: false - tags: - - 1.3.1 - - # Copy in the already configured systemd service file using the copy module. - # Be sure to set the selinux context. - # Notify systemd to reload its daemons and start the service - - name: 1.3.2 - Ensure File integrity is regularly checked (aidecheck service) - ansible.builtin.template: - src: aidecheck.service - dest: /etc/systemd/system/aidecheck.service - owner: root - group: root - mode: 0644 - setype: systemd_unit_file_t - notify: restart aidecheck - tags: - - 1.3.2 - - - name: 1.3.2 - Enable aidecheck.service - ansible.builtin.systemd: - name: aidecheck.service - enabled: true - tags: - - 1.3.2 - - # Copy in the already configured systemd timer file using the copy module. - # Be sure to set the selinux context. - # Notify systemd to reload its daemons and start the timer - - name: 1.3.2 - Ensure File integrity is regulary checked (aidecheck timer) - ansible.builtin.template: - src: aidecheck.timer - dest: /etc/systemd/system/aidecheck.timer - owner: root - group: root - mode: 0644 - setype: systemd_unit_file_t - notify: restart aidecheck - tags: - - 1.3.2 - tags: - - 1.3.0 - - # 1.4 Secure Boot settings - # Determine if we are using LILO or EFI - - name: 1.4.0 - Check if the EFI directory exists - ansible.builtin.stat: - path: "/boot/efi/EFI/{{ ansible_distribution | lower }}/grub.cfg" - register: efidir - tags: - 1.4.1 - - - name: 1.4.1 - set variable for grub.cfg in EFI location - ansible.builtin.set_fact: - grub_cfg_path: "{{ efidir.stat.path }}" - when: efidir.stat.path is defined - tags: - 1.4.1 - - - name: 1.4.0 - Check if the LILO path exists - ansible.builtin.stat: - path: "/boot/grub2/grub.cfg" - register: grubdir - tags: - 1.4.1 - - - name: 1.4.1 - set variable for grub.cfg in LILO location - ansible.builtin.set_fact: - grub_cfg_path: "{{ grubdir.stat.path }}" - when: grubdir.stat.path is defined - tags: - 1.4.1 - - # Use file module to set permissions on grub files - - name: 1.4.1 - Set permissions on grub.cfg - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0600 - loop: - - "{{ grub_cfg_path }}" - - /boot/grub2/grubenv - tags: - - 1.4.1 - - # Control 1.4.2, Grub bootloader password - skipped - - # Use replace module to add the requirement to enter password on single user startup - - name: 1.4.3 - Set single user password - ansible.builtin.replace: - dest: /usr/lib/systemd/system/{{ item }} - regexp: '^ExecStart=-((?!/bin/sh\s+-c\s+\"\s+/sbin/sulogin).)*' - replace: "ExecStart=-/bin/sh -c \"/sbin/sulogin; /usr/bin/systemctl --fail --no-block default\"" - with_items: - - rescue.service - - emergency.service - tags: - - 1.4.3 - - # 1.5 Additional Process Hardening - - - name: 1.5.1 - Ensure core dumps are restricted - block: - # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl - # to reload them immediately if 'reload' is set to 'true'. - - name: 1.5.1 - Ensure core dumps are restricted - ansible.builtin.sysctl: - name: fs.suid_dumpable - value: "0" - state: present - reload: true - - # The pam_limits module will configure the lines in the limits files. - - name: 1.5.1 - Ensure core limits are set - community.general.pam_limits: - dest: /etc/security/limits.d/CIS.conf - domain: "*" - limit_type: hard - limit_item: core - value: "0" - tags: - - 1.5.1 - - # Enable the No Execute / Execute Disable functionality on processors - - name: 1.5.2 Ensure XD/NX support is enabled - block: - # To see if it is set already, we poll the joural with a grep and register a variable - - name: search journal to see if protection was active at boot - ansible.builtin.shell: "/usr/bin/journalctl | /usr/bin/grep 'protection: active' " - register: nx_protection - changed_when: false - check_mode: false - - # If we can't verify that it is active, we notify the user. This has to be set - # in the BIOS or with a special kernel (32 bit kernels only) - - name: Verify XD/NX support is active - ansible.builtin.debug: - msg: "XD/NX support is active" - when: nx_protection.stdout is search("active") - tags: - - 1.5.2 - - - name: 1.5.3 - Ensure address space layout reandomization (ASLR) is enabled - # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl - # to reload them immediately if 'reload' is set to 'true'. - ansible.builtin.sysctl: - name: kernel.randomize_va_space - value: "2" - reload: true - state: present - sysctl_set: true - tags: - - 1.5.3 - - # Use system package manager to remove the prelink package - - name: 1.5.4 - Remove prelink package - ansible.builtin.package: - name: prelink - state: absent - tags: - - 1.5.4 - - # 1.6 Mandatory Access Control - - # SLES does not provide a policy for selinux, so enabling it will freeze a system. For SLES skip - # This section - - name: 1.6.0 - Mandatory Access Control - block: - # Use system package manager to install libselinux (RHEL CLones) - - name: 1.6.2 - Ensure SELinux is installed - ansible.builtin.package: - name: libselinux - state: present - when: (selinux is defined and selinux != "Disabled") and ansible_distribution == "RedHat" - tags: - - 1.6.2 - - # Use the replace module to remove any disablment of selinux in grub if - # it isn't expressly disabled from a variable - - name: 1.6.1.1 - Ensure SELinux is not disabled in bootloader configuration - ansible.builtin.replace: - dest: /etc/default/grub - regexp: "{{ item }}" - replace: "" - with_items: - - selinux=0 - - enforcing=0 - when: selinux is defined and selinux != "Disabled" - notify: rebuild grub - tags: - - 1.6.1.1 - - # re-gather system facts if we installed selinux packages. - # If selinux wasn't installed, it will not populate ansible_selinux fact correctly, regathering - # will pull it with the right information - - name: Regather facts - ansible.builtin.setup: - tags: - - 1.6.1.1 - - # Replace the current selinux policy with whatever the variable is set for - - name: 1.6.1.2 - Set SELinux policy to {{ selinux_policy }} - ansible.builtin.replace: - dest: /etc/selinux/config - regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" - replace: "SELINUXTYPE={{ selinux_policy }}" - when: ( selinux is defined and selinux_policy is defined ) and selinux != "Disabled" - tags: - - 1.6.1.2 - - # If we are going to be enabling selinux in passive or enforcing mode, - # set the autorelabel and notify the machine to reboot - - name: 1.6.1.2 - If disabled and we are enabling it, autorelabel - ansible.builtin.file: - path: /.autorelabel - owner: root - group: root - mode: 0644 - state: touch - when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" - notify: reboot - tags: - - 1.6.1.2 - - # Replace the current selinux mode with what the variable is set to - - name: 1.6.1.3 - Set SELinux to {{ selinux | lower }} - ansible.builtin.replace: - dest: /etc/selinux/config - regexp: "^SELINUX=((?!{{ selinux }}).)*$" - replace: "SELINUX={{ selinux | lower }}" - when: selinux is defined and ( selinux | lower == "enforcing" or selinux | lower == "permissive" or selinux | lower == "disabled" ) - tags: - - 1.6.1.3 - - # Use system package manager to remove package - - name: 1.6.1.4 - Remove setroubleshoot - ansible.builtin.package: - name: "{{ item }}" - state: absent - loop: - - setroubleshoot - - setroubleshoot-server - - setroubleshoot-plugins - tags: - - 1.6.1.4 - - # Use system package manager to remove package - - name: 1.6.1.5 - Remove MCS Translation Service - ansible.builtin.package: - name: mcstrans - state: absent - tags: - - 1.6.1.5 - - # Let the user know if there are any processes that are not running under the - # a selinux context - - name: 1.6.1.6 - Report on unconfined running services - block: - # use ps and grep to find services running under initrc context - - name: 1.6.1.5 - Generate report on unconfined running services - ansible.builtin.shell: /usr/bin/ps -eZ | egrep "initrc" - register: unconfined_services_out - failed_when: unconfined_services_out.rc == "2" - changed_when: false - check_mode: false - - # Print any findings to the user - - name: 1.6.1.5 - Report on unconfined running services to user - ansible.builtin.debug: - msg: - - "Unconfined processes found:" - - "{{ unconfined_services_out.stdout_lines }}" - changed_when: true - when: unconfined_services_out.stdout - tags: - - 1.6.1.5 - when: ansible_distribution != "SLES" - tags: - - 1.6.0 - - # 1.7 Warning Banners - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.7.1.1 - Install motd banners - ansible.builtin.copy: - src: "{{ motd_file }}" - dest: /etc/motd - owner: root - group: root - mode: 0644 - tags: - - 1.7.1.1 - - 1.7.1.4 - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.7.1.2 - Install issue banners - ansible.builtin.copy: - src: "{{ issue_file }}" - dest: /etc/issue - owner: root - group: root - mode: 0644 - tags: - - 1.7.1.2 - - 1.7.1.5 - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.7.1.3 - Install issue.net banners - ansible.builtin.copy: - src: "{{ issue_file }}" - dest: /etc/issue.net - owner: root - group: root - mode: 0644 - tags: - - 1.7.1.3 - - 1.7.1.6 - - # add a banner to the login screen if the graphical_interface variable is set to true - - name: 1.7.2 Ensure GDM banner set up - block: - # Add our required pieces to the dconf file for GDM - - name: 1.7.2 - Set up the dconf profile for GDM - ansible.builtin.file: - path: /etc/dconf/profile/gdm - owner: root - group: root - mode: 0644 - block: | - user-db:user - system-db:gdm - file-db:/usr/share/gdm/greeter-dconf-defaults - - # Set the greeter message - - name: 1.7.2 - Set GDM to use banner message - ansible.builtin.file: - path: /etc/dconf/db/gdm.d/01-banner-message - owern: root - group: root - mode: 0644 - block: | - [org/gnome/login-screen] - banner-message-enable=true - banner-message-text='Authorized uses only. All activity may be monitored and reported.' - when: graphical_inteface is defined and graphical_interface - tags: - - 1.7.2 - - ### Part 2, Services ### - # Remove old, unused, insecure services - - name: 2.1.7 - Remove xinetd service [controlled by host variable tftp_server] - ansible.builtin.package: - name: xinetd - state: absent - when: tftp_server is defined and not tftp_server - tags: - - 2.1.7 - - # use replace to make sure that listed services are disabled - - name: Disable non-tftp services (if we are a tftp server) - ansible.builtin.replace: regexp="disable\s+=\s+[^yes]" replace=" disable = yes" dest=/etc/xinetd.d/{{ item }} - with_items: - - chargen-dgram - - chargen-stream - - chargen-dgram - - daytime-dgram - - daytime-stream - - discard-dgram - - discard-stream - - echo-dgram - - tcpmux-server - - time-dgram - - time-stream - when: tftp_server is defined and tftp_server - ignore_errors: true - tags: - - 2.1.2 - - # Set up the time server listed in the time_service variable - - name: 2.2.1.1 - Verify {{ time_service }} is installed - ansible.builtin.package: - name: "{{ time_service }}" - state: present - when: time_service == "chrony" or time_service == "ntp" - tags: - - 2.2.1.1 - - # Use the template module to deploy the config file for the time sync program - # The default file does not have any template variables, but it's there so - # they can be added in the future. - - name: 2.2.1.2 - Configure {{ time_service }} - ansible.builtin.template: - src: "{{ time_service }}.conf" - dest: /etc/{{ time_service }}.conf - owner: root - group: root - mode: 0644 - when: time_service == "chrony" or time_service == "ntp" - notify: restart {{ time_service }}d - tags: - - 2.2.1.2 - - 2.2.1.3 - - - name: 2.2.1.3 - configure sysconfig time_server options - ansible.builtin.template: - src: "{{ time_service }}d" - dest: /etc/sysconfig/{{ time_service }}d - owner: root - group: root - mode: 0644 - when: time_service == "chrony" or time_service == "ntp" - notify: restart {{ time_service }}d - tags: - - 2.2.1.3 - - # Chrony and ntp argue if they are both active at the same time. Disable - # the other service. - - name: 2.2.1.2 - disable chronyd if time_service is set to ntp - ansible.builtin.systemd: - name: chronyd - state: stopped - enabled: false - when: time_server is defined and time_service == "ntp" - tags: - - 2.2.1.2 - - 2.2.1.3 - - - name: 2.2.1.2 - disable ntpd if time_service is set to chrony - ansible.builtin.systemd: - name: ntpd - state: stopped - enabled: false - when: time_service is defined and time_service == "chrony" - ignore_errors: true - tags: - - 2.2.1.2 - - 2.2.1.3 - - # Disable the display manager by changing the default boot target to multi-user - - name: 2.2.2 - disable display manager if graphical desktop not needed - block: - # Find the current default run level. The systemctl module does not handle the - # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target - - name: 2.2.2 - get default runlevel - ansible.builtin.stat: - path: /etc/systemd/system/default.target - register: default_runlevel_out - tags: - - 2.2.2 - - # Use systemd module to stop the GDM service - - name: 2.2.2 - Disable the gdm display manager - ansible.builtin.systemd: - name: gdm - enabled: false - masked: true - state: stopped - daemon-reload: true - when: "'gdm' in ansible_facts.packages and not graphical_interface" - tags: - - 2.2.2 - - # Set the current run level. The systemctl module does not handle the - # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target - - name: 2.2.2 - Set current runlevel (non graphical) - ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target - register: isolate_out - changed_when: isolate_out.changed - when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface - tags: - - 2.2.2 - - - name: 2.2.2 - Set current runlevel (graphical) - ansible.builtin.command: /usr/bin/systemctl isolate graphical.target - register: isolate_out - changed_when: isolate_out.changed - when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface - tags: - - 2.2.2 - - # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default - - name: 2.2.2 - Set default runlevel (non graphical) - ansible.builtin.file: - src: /lib/systemd/system/multi-user.target - dest: /etc/systemd/system/default.target - owner: root - group: root - when: not graphical_interface and ansible_distribution != "SLES" - - - name: 2.2.2 - Set default runlevel (graphical) - ansible.builtin.file: - src: /lib/systemd/system/graphical.target - dest: /etc/systemd/system/default.target - owner: root - group: root - when: graphical_interface and ansible_distribution != "SLES" - tags: - - 2.2.2 - - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: create empty list for unneeded packages - ansible.builtin.set_fact: - unneeded_packages: [] - tags: - - always - - - name: 2.2.3 - add avahi to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'avahi' ] }}" - tags: - - 2.2.3 - - - name: 2.2.5 - Disable dhcpd server [controlled by host variable dhcp_server] - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'dhcp' ] }}" - when: dhcp_server is defined and not dhcp_server - tags: - - 2.2.5 - - - name: 2.2.6 - add openldap-servers to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'openldap-servers' ] }}" - tags: - - 2.2.6 - - - name: 2.2.7 - Remove nfs server; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'nfs-utils' ] }}" - when: nfs_server is defined and not nfs_server - tags: - - 2.2.7 - - - name: 2.2.8 - Remove bind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'bind' ] + [ 'unbound' ] }}" - when: dns_server is defined and not dns_server - tags: - - 2.2.8 - - - name: 2.2.9 - Remove vsftpd; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + ['vsftpd'] }}" - when: ftp_server is defined and not ftp_server - tags: - - 2.2.9 - - - name: 2.2.10 - Remove httpd; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'httpd' ] + [ 'httpd-tools' ] + [ 'mod_ssl' ] }}" - when: http_server is defined and not http_server - tags: - - 2.2.10 - - - name: 2.2.11 - add dovecot to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'dovecot' ] }}" - tags: - - 2.2.11 - - - name: 2.2.12 - Remove samba; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" - when: smb_server is defined and not smb_server - tags: - - 2.2.12 - - - name: 2.2.13 - add squid to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" - when: not proxy_server - tags: - - 2.2.13 - - - name: 2.2.14 - add net-snmp to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'net-snmp', 'net-snmp-libs' ] }}" - tags: - - 2.2.14 - - - name: 2.2.16 - add ypserv to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypserv' ] }}" - tags: - - 2.2.16 - - - name: 2.3.1 - add ypbind to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypbind' ] }}" - when: not ypbind - tags: - - 2.3.1 - - - name: 2.2.17 - add rsh to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'rsh' ] }}" - tags: - - 2.2.17 - - - name: 2.2.18 - add talk to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'talk-server' ] + [ 'talk' ] }}" - tags: - - 2.2.18 - - 2.3.3 - - - name: 2.2.19 - add telnet to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'telnet-server' ] + [ 'telnet'] }}" - tags: - - 2.2.19 - - 2.3.4 - - - name: 2.2.20 - add tftp to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'tftp' ] + [ 'tftp-server'] }}" - when: not tftp_server - tags: - - 2.2.20 - - - name: 2.2.21 - add rsync to unneeded package list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'rsync' ] }}" - tags: - - 2.2.21 - - - name: 2.2.21 - list of packages to remove - ansible.builtin.debug: - var: unneeded_packages - - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: Process removal list - ansible.builtin.package: - name: "{{ unneeded_packages }}" - state: absent - tags: - - always - - # Cups should be remove per control 2.2.4, but it may not be able to due to - # dependencies, so disable the service instead - - name: 2.2.4 - Disable cups as we my not be able to uninstall it - ansible.builtin.service: - name: "{{ item }}" - enabled: false - state: stopped - when: "'cups' in ansible_facts.packages" - loop: - - cups.service - - cups.socket - - cups-browsed.service - tags: - - 2.2.4 - - # Use the stat module to determine if the mail server config file exists. - # If it does and we are to be a mail server, then modify it per the control. - - name: 2.2.15 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) - block: - - name: 2.2.15 - Find if we have a mail agent config file - ansible.builtin.stat: - path: /etc/postfix/main.cf - register: postfix_out - changed_when: false - - - name: 2.2.15 - If the file exists and not a mail server, then set loopback only - ansible.builtin.replace: - dest: /etc/postfix/main.cf - regexp: "^inet_interfaces = ((?!localhost).)*$" - replace: "inet_interfaces = loopback-only" - when: postfix_out.stat.exists and not email_server - notify: restart postfix - tags: - - 2.2.15 - - # openldap clients skipped (2.3.5) - - # Section 3, Network parameters - - # The sysctl module will configure certain sysctl parameters. They are - # collected into a loop here to speed the implementation - # Once complete, notify the system to flush the network routes - - name: 3.1 - Set networking parameters for host only communications - block: - - name: 3.1 - Set ipv4 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.ip_forward # (3.1.1) - - net.ipv4.conf.all.send_redirects # (3.1.2) - - net.ipv4.conf.default.send_redirects # (3.1.2) - notify: flush network routes - - - name: 3.1 - Set ipv6 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv6.conf.all.forwarding # (3.1.1) - when: not ipv6_disable - notify: flush network routes - tags: - - 3.1.0 - - - name: 3.2 - Set networking parameters for host as router communications - block: - - name: 3.2 - Set ipv4 network parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.conf.all.accept_source_route # (3.2.1) - - net.ipv4.conf.default.accept_source_route # (3.2.1) - - net.ipv4.conf.all.accept_redirects # (3.2.2) - - net.ipv4.conf.default.accept_redirects # (3.2.2) - - net.ipv4.conf.all.secure_redirects # (3.2.3) - - net.ipv4.conf.default.secure_redirects # (3.2.3) - notify: flush network routes - - - name: 3.2.[4-8] - Set ipv4 networking parameters (ON) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "1" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.conf.all.log_martians # (3.2.4) - - net.ipv4.conf.default.log_martians # (3.2.4) - - net.ipv4.icmp_echo_ignore_broadcasts # (3.2.5) - - net.ipv4.icmp_ignore_bogus_error_responses # (3.2.6) - - net.ipv4.conf.all.rp_filter # (3.2.7) - - net.ipv4.conf.default.rp_filter # (3.2.7) - - net.ipv4.tcp_syncookies # ( 3.2.8) - notify: flush network routes - - - name: 3.2 - Set ipv6 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv6.conf.all.accept_source_route # (3.2.1) - - net.ipv6.conf.default.accept_source_route # (3.2.1) - - net.ipv6.conf.all.accept_redirects # (3.2.2) - - net.ipv6.conf.default.accept_redirects # (3.2.2) - - net.ipv6.conf.all.accept_ra # (3.2.9) - - net.ipv6.conf.default.accept_ra # (3.2.9) - notify: flush network routes - when: not ipv6_disable - tags: - - 3.2.0 - - - name: 3.3 - IPv6 configuration and/or disablement - block: - - name: 3.3 - Set ipv6 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv6.conf.all.accept_redirects # (3.3.2) - - net.ipv6.conf.default.accept_redirects # (3.3.2) - - net.ipv6.conf.all.accept_ra # (3.3.1) - - net.ipv6.conf.default.accept_ra # (3.3.1) - notify: flush network routes - when: not ipv6_disable - tags: - - 3.3.1 - - 3.3.2 - - # We check here because we don't know what position the ipv6.disable is in - # order to simply do the replace, so we are instead looking for the match in the file first. - # If it doesn't exist, then we can just insert it - - name: 3.3 - Find if IPv6 is currently in the grub file, shows changed when it is in the file - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX.*ipv6.disable=1' - state: absent - check_mode: true - changed_when: false - register: ipv6_disable_grub - failed_when: false - tags: - - 3.3.3 - - # use the replace module to add it to grub bootloader and then notify - # grub to rebuild - - name: 3.3 - Disable IPv6 in grub - ansible.builtin.replace: - path: /etc/default/grub - regexp: '^GRUB_CMDLINE_LINUX="' - replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' - notify: rebuild grub - when: not ipv6_disable_grub.found and ipv6_disable - tags: - - 3.3.3 - when: ipv6_disable - tags: - 3.3.0 - - # TCP Wrappers is not distributed by RHEL, but was distributed via EPEL until the end - # of 2018. It is being left in the RHEL7 controls because it may still be in some security - # requirements. It is not included in the RHEL8 controls - - name: 3.4 - TCP Wrappers - block: - - name: 3.4.1 - Install tcpwrappers - ansible.builtin.package: - name: "{{ tcpwrappers_pkg }}" - state: present - - # use lineinfile to make sure everyone has access to tcpwrappers - # from the local machine - - name: 3.4.2 - create basic hosts.allow - ansible.builtin.lineinfile: - dest: /etc/hosts.allow - owner: root - group: root - mode: 0644 - line: "ALL: 127.0.0.1" - - # 3.4.3 is heavy handed so we aren't implementing it - - name: Refuse to set all on hosts.deny (3.4.3) because it's too heavy handed - ansible.builtin.debug: - msg: "not setting all on hosts.deny (3.4.3)" - - - name: 3.4.[4-5] - Set permissions on tcpwrappers files - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0644 - loop: - - /etc/hosts.allow # (3.4.4) - - /etc/hosts.deny # (3.4.5) - when: tcpwrappers - tags: - - 3.4.0 - - - name: 3.5 - Disable uncommon network protocols - block: - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: 3.5.0 - Create empty list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: [] - - - name: 3.5.1 - Add dccp to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'dccp' ] }}" - tags: - - 3.5.1 - - - name: 3.5.2 - Add sctp to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'sctp' ] }}" - tags: - - 3.5.2 - - - name: 3.5.3 - Add rds to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'rds' ] }}" - tags: - - 3.5.3 - - - name: 3.5.4 - Add tipc to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'tipc' ] }}" - tags: - - 3.5.4 - - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: 3.5.0 - Process uncommon network list - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" - state: present - create: true - with_items: - - "{{ uncommon_network }}" - tags: - - 3.5.0 - - # Section 3 - Firewall - - - name: 3.6 - Install firewalld - block: - - name: 3.6.1 - Install firewalld - ansible.builtin.package: - name: "firewalld" - state: present - notify: start firewalld - - - name: 3.6.1 - Disable iptables - ansible.builtin.service: - name: iptables - state: stopped - enabled: false - masked: true +# Use the service module to disable the rhnsd service. If you want the machine +# to respond to queued services from Satellite, do not disable this. +- name: 1.2.5 Disable rhnsd + ansible.builtin.service: + name: rhnsd + enabled: false + state: stopped + failed_when: false + when: ansible_distribution == "RedHat" + tags: + - 1.2.5 + +# GPGKeys are used to sign packages. enabling them will mean that all packages +# from a given repo must be signed with the appropriate key +- name: 1.2.[2,3] - Ensure GPG keys are configured + when: ansible_distribution != "SLES" + tags: + - 1.2.2 + - 1.2.3 + block: + # Replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.2 - set master yum.conf gpgcheck to '1' + ansible.builtin.replace: + dest: /etc/yum.conf + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: "gpgcheck = 1" + when: gpgcheck is defined and gpgcheck + + # Find all files in /etc/yum.repos.d and add them to a list variable + - name: 1.2.3 - find all repo files in /etc/yum.repos.d/ + ansible.builtin.find: + paths: "/etc/yum.repos.d" + patterns: "*.repo" + register: yumrepos + when: gpgcheck is defined and gpgcheck + + # parse the list variable and replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.3 - Set all repos gpgchecks to '1' + ansible.builtin.replace: + dest: "{{ item.path }}" + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: gpgcheck = 1 + with_items: "{{ yumrepos.files }}" + when: gpgcheck is defined and gpgcheck + +# - name: 1.2.1 - Ensure package manager repositories are configured, site dependent + + +# AIDE is a file system integrity checker which will document all +# filesystem changes. It's very noisy on busy systems and should be +# enabled when you have the sapce and need for it. +- name: 1.3 - Filesystem integrity checking w/AIDE + tags: + - 1.3.0 + block: + # use the system package manager to install AIDE + - name: 1.3.1 Ensure aide is installed + ansible.builtin.package: + name: aide + state: present + tags: + - 1.3.1 + + # AIDE requires initialization the first time and it takes time on a large system. + # DUse stat module on the file that should be there if it is set up. + - name: 1.3.1 - Determine if AIDE has already been initialized + ansible.builtin.stat: + path: /var/lib/aide/aide.db.gz + register: aide_path + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database=ansible.builtin.file:((?!{{ aide_db_name }}).)*$" + replace: "database=ansible.builtin.file:{{ aide_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database_out file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database_out=ansible.builtin.file:((?!{{ aide_new_db_name }}).)*$" + replace: "database_out=ansible.builtin.file:{{ aide_new_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - enable gzip compression for database + ansible.builtin.lineinfile: + dest: /etc/aide.conf + regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' + line: "gzip_dbout={{ aide_gzip }}" + state: present + tags: + - 1.3.1 + + # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' + # is true if the file is a regular file. If either of these are not true, then + # run the initializatoin again. + - name: 1.3.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aide) + ansible.builtin.command: /usr/sbin/aide --init + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + register: aide + async: 1200 # 20 minutes until timeout + poll: 0 # run concurrently + tags: + - 1.3.1 + + - name: Wait for AIDE initialization to complete + ansible.builtin.async_status: + jid: '{{ aide.ansible_job_id }}' + register: aide_status + until: aide_status.finished + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + retries: 300 + + # AIDE creates the new database as a different name. Use the copy module with + # the remote_src argument to copy the file on the remote machine to another location + # on the remote machine. + - name: 1.3.1 - Move the newly created database into place + ansible.builtin.copy: + src: /var/lib/aide/aide.db.new.gz + remote_src: true + dest: /var/lib/aide/aide.db.gz + mode: preserve + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + changed_when: false + tags: + - 1.3.1 + + # Copy in the already configured systemd service file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the service + - name: 1.3.2 - Ensure File integrity is regularly checked (aidecheck service) + ansible.builtin.template: + src: aidecheck.service + dest: /etc/systemd/system/aidecheck.service + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + notify: Restart aidecheck + tags: + - 1.3.2 + + - name: 1.3.2 - Enable aidecheck.service + ansible.builtin.systemd: + name: aidecheck.service + enabled: true + tags: + - 1.3.2 + + # Copy in the already configured systemd timer file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the timer + - name: 1.3.2 - Ensure File integrity is regulary checked (aidecheck timer) + ansible.builtin.template: + src: aidecheck.timer + dest: /etc/systemd/system/aidecheck.timer + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + notify: Restart aidecheck + tags: + - 1.3.2 + +# 1.4 Secure Boot settings +# Determine if we are using LILO or EFI +- name: 1.4.0 - Check if the EFI directory exists + ansible.builtin.stat: + path: "/boot/efi/EFI/{{ ansible_distribution | lower }}/grub.cfg" + register: efidir + tags: + 1.4.1 + +- name: 1.4.1 - set variable for grub.cfg in EFI location + ansible.builtin.set_fact: + grub_cfg_path: "{{ efidir.stat.path }}" + when: efidir.stat.path is defined + tags: + 1.4.1 + +- name: 1.4.0 - Check if the LILO path exists + ansible.builtin.stat: + path: "/boot/grub2/grub.cfg" + register: grubdir + tags: + 1.4.1 + +- name: 1.4.1 - set variable for grub.cfg in LILO location + ansible.builtin.set_fact: + grub_cfg_path: "{{ grubdir.stat.path }}" + when: grubdir.stat.path is defined + tags: + 1.4.1 + +# Use file module to set permissions on grub files +- name: 1.4.1 - Set permissions on grub.cfg + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - "{{ grub_cfg_path }}" + - /boot/grub2/grubenv + tags: + - 1.4.1 + +# Control 1.4.2, Grub bootloader password - skipped + +# Use replace module to add the requirement to enter password on single user startup +- name: 1.4.3 - Set single user password + ansible.builtin.replace: + dest: /usr/lib/systemd/system/{{ item }} + regexp: '^ExecStart=-((?!/bin/sh\s+-c\s+\"\s+/sbin/sulogin).)*' + replace: "ExecStart=-/bin/sh -c \"/sbin/sulogin; /usr/bin/systemctl --fail --no-block default\"" + with_items: + - rescue.service + - emergency.service + tags: + - 1.4.3 + +# 1.5 Additional Process Hardening + +- name: 1.5.1 - Ensure core dumps are restricted + tags: + - 1.5.1 + block: + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'true'. + - name: 1.5.1 - Ensure core dumps are restricted + ansible.builtin.sysctl: + name: fs.suid_dumpable + value: "0" + state: present + reload: true + + # The pam_limits module will configure the lines in the limits files. + - name: 1.5.1 - Ensure core limits are set + community.general.pam_limits: + dest: /etc/security/limits.d/CIS.conf + domain: "*" + limit_type: hard + limit_item: core + value: "0" + +# Enable the No Execute / Execute Disable functionality on processors +- name: 1.5.2 Ensure XD/NX support is enabled + tags: + - 1.5.2 + block: + # To see if it is set already, we poll the joural with a grep and register a variable + - name: Search journal to see if protection was active at boot + ansible.builtin.shell: "/usr/bin/journalctl | /usr/bin/grep 'protection: active' " + register: nx_protection + changed_when: false + check_mode: false + + # If we can't verify that it is active, we notify the user. This has to be set + # in the BIOS or with a special kernel (32 bit kernels only) + - name: Verify XD/NX support is active + ansible.builtin.debug: + msg: "XD/NX support is active" + when: nx_protection.stdout is search("active") + +- name: 1.5.3 - Ensure address space layout reandomization (ASLR) is enabled + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'true'. + ansible.builtin.sysctl: + name: kernel.randomize_va_space + value: "2" + reload: true + state: present + sysctl_set: true + tags: + - 1.5.3 + +# Use system package manager to remove the prelink package +- name: 1.5.4 - Remove prelink package + ansible.builtin.package: + name: prelink + state: absent + tags: + - 1.5.4 + +# 1.6 Mandatory Access Control + +# SLES does not provide a policy for selinux, so enabling it will freeze a system. For SLES skip +# This section +- name: 1.6.0 - Mandatory Access Control + when: ansible_distribution != "SLES" + tags: + - 1.6.0 + block: + # Use system package manager to install libselinux (RHEL CLones) + - name: 1.6.2 - Ensure SELinux is installed + ansible.builtin.package: + name: libselinux + state: present + when: (selinux is defined and selinux != "Disabled") and ansible_distribution == "RedHat" + tags: + - 1.6.2 + + # Use the replace module to remove any disablment of selinux in grub if + # it isn't expressly disabled from a variable + - name: 1.6.1.1 - Ensure SELinux is not disabled in bootloader configuration + ansible.builtin.replace: + dest: /etc/default/grub + regexp: "{{ item }}" + replace: "" + with_items: + - selinux=0 + - enforcing=0 + when: selinux is defined and selinux != "Disabled" + notify: Rebuild grub + tags: + - 1.6.1.1 + + # re-gather system facts if we installed selinux packages. + # If selinux wasn't installed, it will not populate ansible_selinux fact correctly, regathering + # will pull it with the right information + - name: Regather facts + ansible.builtin.setup: + tags: + - 1.6.1.1 + + # Replace the current selinux policy with whatever the variable is set for + - name: 1.6.1.2 - Set SELinux policy to {{ selinux_policy }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" + replace: "SELINUXTYPE={{ selinux_policy }}" + when: ( selinux is defined and selinux_policy is defined ) and selinux != "Disabled" + tags: + - 1.6.1.2 + + # If we are going to be enabling selinux in passive or enforcing mode, + # set the autorelabel and notify the machine to reboot + - name: 1.6.1.2 - If disabled and we are enabling it, autorelabel + ansible.builtin.file: + path: /.autorelabel + owner: root + group: root + mode: 0644 + state: touch + when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" + notify: Reboot + tags: + - 1.6.1.2 + + # Replace the current selinux mode with what the variable is set to + - name: 1.6.1.3 - Set SELinux to {{ selinux | lower }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUX=((?!{{ selinux }}).)*$" + replace: "SELINUX={{ selinux | lower }}" + when: selinux is defined and ( selinux | lower == "enforcing" or selinux | lower == "permissive" or selinux | lower == "disabled" ) + tags: + - 1.6.1.3 + + # Use system package manager to remove package + - name: 1.6.1.4 - Remove setroubleshoot + ansible.builtin.package: + name: "{{ item }}" + state: absent + loop: + - setroubleshoot + - setroubleshoot-server + - setroubleshoot-plugins + tags: + - 1.6.1.4 + + # Use system package manager to remove package + - name: 1.6.1.5 - Remove MCS Translation Service + ansible.builtin.package: + name: mcstrans + state: absent + tags: + - 1.6.1.5 + + # Let the user know if there are any processes that are not running under the + # a selinux context + - name: 1.6.1.6 - Report on unconfined running services + tags: + - 1.6.1.5 + block: + # use ps and grep to find services running under initrc context + - name: 1.6.1.5 - Generate report on unconfined running services + ansible.builtin.shell: /usr/bin/ps -eZ | egrep "initrc" + register: unconfined_services_out + failed_when: unconfined_services_out.rc == "2" + changed_when: false + check_mode: false + + # Print any findings to the user + - name: 1.6.1.5 - Report on unconfined running services to user + ansible.builtin.debug: + msg: + - "Unconfined processes found:" + - "{{ unconfined_services_out.stdout_lines }}" + changed_when: true + when: unconfined_services_out.stdout + +# 1.7 Warning Banners + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1.1 - Install motd banners + ansible.builtin.copy: + src: "{{ motd_file }}" + dest: /etc/motd + owner: root + group: root + mode: 0644 + tags: + - 1.7.1.1 + - 1.7.1.4 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1.2 - Install issue banners + ansible.builtin.copy: + src: "{{ issue_file }}" + dest: /etc/issue + owner: root + group: root + mode: 0644 + tags: + - 1.7.1.2 + - 1.7.1.5 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1.3 - Install issue.net banners + ansible.builtin.copy: + src: "{{ issue_net_file }}" + dest: /etc/issue.net + owner: root + group: root + mode: 0644 + tags: + - 1.7.1.3 + - 1.7.1.6 + +# add a banner to the login screen if the graphical_interface variable is set to true +- name: 1.7.2 Ensure GDM banner set up + when: graphical_inteface is defined and graphical_interface + tags: + - 1.7.2 + block: + # Add our required pieces to the dconf file for GDM + - name: 1.7.2 - Set up the dconf profile for GDM + ansible.builtin.file: + path: /etc/dconf/profile/gdm + owner: root + group: root + mode: 0644 + block: | + user-db:user + system-db:gdm + file-db:/usr/share/gdm/greeter-dconf-defaults + + # Set the greeter message + - name: 1.7.2 - Set GDM to use banner message + ansible.builtin.file: + path: /etc/dconf/db/gdm.d/01-banner-message + owern: root + group: root + mode: 0644 + block: | + [org/gnome/login-screen] + banner-message-enable=true + banner-message-text='Authorized uses only. All activity may be monitored and reported.' + +## Part 2, Services ### +# Remove old, unused, insecure services +- name: 2.1.7 - Remove xinetd service [controlled by host variable tftp_server] + ansible.builtin.package: + name: xinetd + state: absent + when: tftp_server is defined and not tftp_server + tags: + - 2.1.7 + +# use replace to make sure that listed services are disabled +- name: Disable non-tftp services (if we are a tftp server) + ansible.builtin.replace: + regexp: 'disable\s+=\s+[^yes]' + replace: " disable = yes" + dest: "/etc/xinetd.d/{{ item }}" + with_items: + - chargen-dgram + - chargen-stream + - chargen-dgram + - daytime-dgram + - daytime-stream + - discard-dgram + - discard-stream + - echo-dgram + - tcpmux-server + - time-dgram + - time-stream + when: tftp_server is defined and tftp_server + failed_when: false + tags: + - 2.1.2 + +# Set up the time server listed in the time_service variable +- name: 2.2.1.1 - Install {{ time_service }} + ansible.builtin.package: + name: "{{ time_service }}" + state: present + when: time_service == "chrony" or time_service == "ntp" + tags: + - 2.2.1.1 + +# Use the template module to deploy the config file for the time sync program +# The default file does not have any template variables, but it's there so +# they can be added in the future. +- name: 2.2.1.2 - Configure {{ time_service }} + ansible.builtin.template: + src: "{{ time_service }}.conf" + dest: /etc/{{ time_service }}.conf + owner: root + group: root + mode: 0644 + when: time_service == "chrony" or time_service == "ntp" + notify: Restart {{ time_service }}d + tags: + - 2.2.1.2 + - 2.2.1.3 + +- name: 2.2.1.3 - configure sysconfig time_server options + ansible.builtin.template: + src: "{{ time_service }}d" + dest: /etc/sysconfig/{{ time_service }}d + owner: root + group: root + mode: 0644 + when: time_service == "chrony" or time_service == "ntp" + notify: Restart {{ time_service }}d + tags: + - 2.2.1.3 + +# Chrony and ntp argue if they are both active at the same time. Disable +# the other service. +- name: 2.2.1.2 - disable chronyd if time_service is set to ntp + ansible.builtin.systemd: + name: chronyd + state: stopped + enabled: false + when: time_server is defined and time_service == "ntp" + tags: + - 2.2.1.2 + - 2.2.1.3 + +- name: 2.2.1.2 - disable ntpd if time_service is set to chrony + ansible.builtin.systemd: + name: ntpd + state: stopped + enabled: false + when: time_service is defined and time_service == "chrony" + failed_when: false + tags: + - 2.2.1.2 + - 2.2.1.3 + +# Disable the display manager by changing the default boot target to multi-user +- name: 2.2.2 - disable display manager if graphical desktop not needed + tags: + - 2.2.2 + block: + # Find the current default run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 2.2.2 - get default runlevel + ansible.builtin.stat: + path: /etc/systemd/system/default.target + register: default_runlevel_out + tags: + - 2.2.2 + + # Use systemd module to stop the GDM service + - name: 2.2.2 - Disable the gdm display manager + ansible.builtin.systemd: + name: gdm + enabled: false + masked: true + state: stopped + daemon-reload: true + when: "'gdm' in ansible_facts.packages and not graphical_interface" + tags: + - 2.2.2 + + # Set the current run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 2.2.2 - Set current runlevel (non graphical) + ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface + tags: + - 2.2.2 + + - name: 2.2.2 - Set current runlevel (graphical) + ansible.builtin.command: /usr/bin/systemctl isolate graphical.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface + tags: + - 2.2.2 + + # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default + - name: 2.2.2 - Set default runlevel (non graphical) + ansible.builtin.file: + src: /lib/systemd/system/multi-user.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: not graphical_interface and ansible_distribution != "SLES" + + - name: 2.2.2 - Set default runlevel (graphical) + ansible.builtin.file: + src: /lib/systemd/system/graphical.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: graphical_interface and ansible_distribution != "SLES" + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: Create empty list for unneeded packages + ansible.builtin.set_fact: + unneeded_packages: [] + tags: + - always + +- name: 2.2.3 - add avahi to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['avahi'] }}" + tags: + - 2.2.3 + +- name: 2.2.5 - Disable dhcpd server [controlled by host variable dhcp_server] + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['dhcp'] }}" + when: dhcp_server is defined and not dhcp_server + tags: + - 2.2.5 + +- name: 2.2.6 - add openldap-servers to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['openldap-servers'] }}" + tags: + - 2.2.6 + +- name: 2.2.7 - Remove nfs server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['nfs-utils'] }}" + when: nfs_server is defined and not nfs_server + tags: + - 2.2.7 + +- name: 2.2.8 - Remove bind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['bind'] + ['unbound'] }}" + when: dns_server is defined and not dns_server + tags: + - 2.2.8 + +- name: 2.2.9 - Remove vsftpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['vsftpd'] }}" + when: ftp_server is defined and not ftp_server + tags: + - 2.2.9 + +- name: 2.2.10 - Remove httpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['httpd'] + ['httpd-tools'] + ['mod_ssl'] }}" + when: http_server is defined and not http_server + tags: + - 2.2.10 + +- name: 2.2.11 - add dovecot to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['dovecot'] }}" + tags: + - 2.2.11 + +- name: 2.2.12 - Remove samba; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['samba'] }}" + when: smb_server is defined and not smb_server + tags: + - 2.2.12 + +- name: 2.2.13 - add squid to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['squid'] }}" + when: not proxy_server + tags: + - 2.2.13 + +- name: 2.2.14 - add net-snmp to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['net-snmp', 'net-snmp-libs'] }}" + tags: + - 2.2.14 + +- name: 2.2.16 - add ypserv to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['ypserv'] }}" + tags: + - 2.2.16 + +- name: 2.3.1 - add ypbind to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['ypbind'] }}" + when: not ypbind + tags: + - 2.3.1 + +- name: 2.2.17 - add rsh to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['rsh'] }}" + tags: + - 2.2.17 + +- name: 2.2.18 - add talk to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['talk-server'] + ['talk'] }}" + tags: + - 2.2.18 + - 2.3.3 + +- name: 2.2.19 - add telnet to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['telnet-server'] + ['telnet'] }}" + tags: + - 2.2.19 + - 2.3.4 + +- name: 2.2.20 - add tftp to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['tftp'] + ['tftp-server'] }}" + when: not tftp_server + tags: + - 2.2.20 + +- name: 2.2.21 - add rsync to unneeded package list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['rsync'] }}" + tags: + - 2.2.21 + +- name: 2.2.21 - list of packages to remove + ansible.builtin.debug: + var: unneeded_packages + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: Process removal list + ansible.builtin.package: + name: "{{ unneeded_packages }}" + state: absent + tags: + - always + +# Cups should be remove per control 2.2.4, but it may not be able to due to +# dependencies, so disable the service instead +- name: 2.2.4 - Disable cups as we my not be able to uninstall it + ansible.builtin.service: + name: "{{ item }}" + enabled: false + state: stopped + when: "'cups' in ansible_facts.packages" + loop: + - cups.service + - cups.socket + - cups-browsed.service + tags: + - 2.2.4 + +# Use the stat module to determine if the mail server config file exists. +# If it does and we are to be a mail server, then modify it per the control. +- name: 2.2.15 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) + tags: + - 2.2.15 + block: + - name: 2.2.15 - Find if we have a mail agent config file + ansible.builtin.stat: + path: /etc/postfix/main.cf + register: postfix_out + changed_when: false + + - name: 2.2.15 - If the file exists and not a mail server, then set loopback only + ansible.builtin.replace: + dest: /etc/postfix/main.cf + regexp: "^inet_interfaces = ((?!localhost).)*$" + replace: "inet_interfaces = loopback-only" + when: postfix_out.stat.exists and not email_server + notify: Restart postfix + + # openldap clients skipped (2.3.5) + +# Section 3, Network parameters + +# The sysctl module will configure certain sysctl parameters. They are +# collected into a loop here to speed the implementation +# Once complete, notify the system to flush the network routes +- name: 3.1 - Set networking parameters for host only communications + tags: + - 3.1.0 + block: + - name: 3.1 - Set ipv4 networking parameters (OFF) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "0" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv4.ip_forward # (3.1.1) + - net.ipv4.conf.all.send_redirects # (3.1.2) + - net.ipv4.conf.default.send_redirects # (3.1.2) + notify: Flush network routes + + - name: 3.1 - Set ipv6 networking parameters (OFF) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "0" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv6.conf.all.forwarding # (3.1.1) + when: not ipv6_disable + notify: Flush network routes + +- name: 3.2 - Set networking parameters for host as router communications + tags: + - 3.2.0 + block: + - name: 3.2 - Set ipv4 network parameters (OFF) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "0" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv4.conf.all.accept_source_route # (3.2.1) + - net.ipv4.conf.default.accept_source_route # (3.2.1) + - net.ipv4.conf.all.accept_redirects # (3.2.2) + - net.ipv4.conf.default.accept_redirects # (3.2.2) + - net.ipv4.conf.all.secure_redirects # (3.2.3) + - net.ipv4.conf.default.secure_redirects # (3.2.3) + notify: Flush network routes + + - name: 3.2.[4-8] - Set ipv4 networking parameters (ON) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "1" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv4.conf.all.log_martians # (3.2.4) + - net.ipv4.conf.default.log_martians # (3.2.4) + - net.ipv4.icmp_echo_ignore_broadcasts # (3.2.5) + - net.ipv4.icmp_ignore_bogus_error_responses # (3.2.6) + - net.ipv4.conf.all.rp_filter # (3.2.7) + - net.ipv4.conf.default.rp_filter # (3.2.7) + - net.ipv4.tcp_syncookies # ( 3.2.8) + notify: Flush network routes + + - name: 3.2 - Set ipv6 networking parameters (OFF) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "0" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv6.conf.all.accept_source_route # (3.2.1) + - net.ipv6.conf.default.accept_source_route # (3.2.1) + - net.ipv6.conf.all.accept_redirects # (3.2.2) + - net.ipv6.conf.default.accept_redirects # (3.2.2) + - net.ipv6.conf.all.accept_ra # (3.2.9) + - net.ipv6.conf.default.accept_ra # (3.2.9) + notify: Flush network routes + when: not ipv6_disable + +- name: 3.3 - IPv6 configuration and/or disablement + when: ipv6_disable + tags: + 3.3.0 + block: + - name: 3.3 - Set ipv6 networking parameters (OFF) + ansible.builtin.sysctl: + name: "{{ item }}" + value: "0" + reload: true + state: present + sysctl_set: true + loop: + - net.ipv6.conf.all.accept_redirects # (3.3.2) + - net.ipv6.conf.default.accept_redirects # (3.3.2) + - net.ipv6.conf.all.accept_ra # (3.3.1) + - net.ipv6.conf.default.accept_ra # (3.3.1) + notify: Flush network routes + when: not ipv6_disable + tags: + - 3.3.1 + - 3.3.2 + + # We check here because we don't know what position the ipv6.disable is in + # order to simply do the replace, so we are instead looking for the match in the file first. + # If it doesn't exist, then we can just insert it + - name: 3.3 - Find if IPv6 is currently in the grub file, shows changed when it is in the file + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*ipv6.disable=1' + state: absent + check_mode: true + changed_when: false + register: ipv6_disable_grub + failed_when: false + tags: + - 3.3.3 + + # use the replace module to add it to grub bootloader and then notify + # grub to rebuild + - name: 3.3 - Disable IPv6 in grub + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' + notify: Rebuild grub + when: not ipv6_disable_grub.found and ipv6_disable + tags: + - 3.3.3 + +# TCP Wrappers is not distributed by RHEL, but was distributed via EPEL until the end +# of 2018. It is being left in the RHEL7 controls because it may still be in some security +# requirements. It is not included in the RHEL8 controls +- name: 3.4 - TCP Wrappers + when: tcpwrappers + tags: + - 3.4.0 + block: + - name: 3.4.1 - Install tcpwrappers + ansible.builtin.package: + name: "{{ tcpwrappers_pkg }}" + state: present + + # use lineinfile to make sure everyone has access to tcpwrappers + # from the local machine + - name: 3.4.2 - create basic hosts.allow + ansible.builtin.lineinfile: + dest: /etc/hosts.allow + owner: root + group: root + mode: 0644 + line: "ALL: 127.0.0.1" + + # 3.4.3 is heavy handed so we aren't implementing it + - name: Refuse to set all on hosts.deny (3.4.3) because it's too heavy handed + ansible.builtin.debug: + msg: "not setting all on hosts.deny (3.4.3)" + + - name: 3.4.[4-5] - Set permissions on tcpwrappers files + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0644 + loop: + - /etc/hosts.allow # (3.4.4) + - /etc/hosts.deny # (3.4.5) + +- name: 3.5 - Disable uncommon network protocols + tags: + - 3.5.0 + block: + # This collection of tasks creates a empty list and save it as a fact. + # For every item that is encountered (without the tag being skipped), + # add a string to the list. + - name: 3.5.0 - Create empty list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: [] + + - name: 3.5.1 - Add dccp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['dccp'] }}" + tags: + - 3.5.1 + + - name: 3.5.2 - Add sctp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['sctp'] }}" + tags: + - 3.5.2 + + - name: 3.5.3 - Add rds to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['rds'] }}" + tags: + - 3.5.3 + + - name: 3.5.4 - Add tipc to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['tipc'] }}" + tags: + - 3.5.4 + + # With the list complete, use it with the system's package manager + # to remove packages from the system that are not needed. + - name: 3.5.0 - Process uncommon network list + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install {{ item }} /bin/true" + state: present + create: true + mode: 0644 + with_items: + - "{{ uncommon_network }}" + +# Section 3 - Firewall + +- name: 3.6 - Configure firewalld + when: enable_firewall is defined and enable_firewall == "firewalld" + tags: + - 3.6.0 + block: + - name: 3.6.1 - Install firewalld + ansible.builtin.package: + name: "firewalld" + state: present + notify: Start firewalld + + - name: 3.6.1 - Disable iptables + ansible.builtin.service: + name: iptables + state: stopped + enabled: false + masked: true + failed_when: false + + - name: 3.6.1 - Remove iptables-services + ansible.builtin.package: + name: "iptables-services" + state: absent + + - name: 3.6.1 - Set default zone + ansible.builtin.lineinfile: + path: "/etc/firewalld/firewalld.conf" + regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' + line: "DefaultZone={{ firewalld_default_zone }}" + when: firewalld_default_zone is defined + notify: Restart firewalld + +- name: 3.6.1 - Configure iptables + when: enable_firewall is defined and enable_firewall == "iptables" + tags: + - 3.6.0 + block: + - name: 3.6.1 Install iptables + ansible.builtin.package: + name: + - "iptables" + - "iptables-services" + state: present + notify: Start iptables + tags: + - 3.6.1 + + - name: 3.6.1 - Disable firewalld + ansible.builtin.service: + name: firewalld + state: stopped + enabled: false ignore_errors: true - failed_when: false - - - name: 3.6.1 - Remove iptables-services - package: - name: "iptables-services" - state: absent - - - name: 3.6.1 - Set default zone - ansible.builtin.lineinfile: - path: "/etc/firewalld/firewalld.conf" - regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' - line: "DefaultZone={{ firewalld_default_zone }}" - when: firewalld_default_zone is defined - notify: restart firewalld - - when: enable_firewall is defined and enable_firewall == "firewalld" - tags: - - 3.6.1 - - - name: 3.6.1 - Install iptables - block: - - name: 3.6.1 Install iptables - ansible.builtin.package: - name: - - "iptables" - - "iptables-services" - state: present - notify: start iptables - tags: - - 3.6.1 - - - name: 3.6.1 - Disable firewalld - ansible.builtin.service: - name: firewalld - state: stopped - enabled: false - ignore_errors: true - when: enable_firewall is defined and enable_firewall == "iptables" - tags: - - 3.6.1 - - - ansible.builtin.debug: - msg: " Ensure default firewall policy (3.6.[2-5]) must be handled locally" + +- name: Notify user to configure firewall policy + ansible.builtin.debug: + msg: " Ensure default firewall policy (3.6.[2-5]) must be handled locally" # Control 3.7 Ensure wireless interfaces are disabled is interface dependent # skipping - # Section 4 - Logging and Auditing - - - name: 4.1 Install and configure system auditing - block: - - name: 4.1.1 - Install Audit - ansible.builtin.package: - name: - - audit - - audit-libs - state: present - tags: - - 4.1.1.1 - # The replace module here is looking through file and make replacements of partial lines - - name: 4.1.1-[2-3] - Configure audit log storage size - ansible.builtin.replace: - path: /etc/audit/auditd.conf - regexp: "{{ item.find }}" - replace: "{{ item.replace }}" - loop: - - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.1.1 - - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.1.2 - - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.1.2 - - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.1.2 - - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.1.2 - notify: restart auditd - tags: - - 4.1.1.2 - - 4.1.1.3 - - - name: 4.1.2 - Enable auditd service - ansible.builtin.service: - name: auditd - enabled: true - state: started - tags: - - 4.1.2 - - - name: 4.1.1.3 - Ensure auditing for processes that start prior to auditd - # We check here because we don't know what position the audit=1 is in - # order to simply do the replace, so we are instead looking for the match in the file first. - # If it doesn't exist, then we can just insert it - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' - state: absent - check_mode: true - changed_when: false - register: audit_exist - failed_when: false - tags: - - 4.1.1.3 - - # use the replace module to add it to grub bootloader and then notify - # grub to rebuild - - name: 4.1.1.3 - enable audit service in grub - ansible.builtin.replace: - path: /etc/default/grub - regexp: '^GRUB_CMDLINE_LINUX="' - replace: 'GRUB_CMDLINE_LINUX="audit=1 ' - notify: rebuild grub - when: not audit_exist.found - tags: - - 4.1.1.3 - - # For the next several checks, each one is in their own file, so we are using - # the copy module to place each file independently and then motifying - # a restart of auditd if anything changes. - - name: 4.1.4 - Ensure to collect events that modify date/time - ansible.builtin.template: - dest: /etc/audit/rules.d/datetime.rules - src: audit_rules/datetime.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - - 4.1.4 - - - name: 4.1.5 - Ensure events that modify user/group information are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/user-group-info.rules - src: audit_rules/user-group-info.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.5 - - - name: 4.1.6 - Ensure to collect events that modify network - ansible.builtin.template: - dest: /etc/audit/rules.d/network.rules - src: audit_rules/network.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - - 4.1.6 - - - name: 4.1.7 - Ensure modifications to Mandatory Access Controls are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/MAC-policy.rules - src: audit_rules/MAC-policy.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.7 - - - name: 4.1.8 - Ensure system logins are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/login.rules - src: audit_rules/login.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.8 - - - name: 4.1.9 - Ensure session initiation information is collected - ansible.builtin.template: - dest: /etc/audit/rules.d/sessions.rules - src: audit_rules/sessions.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.9 - - # This is the first control that we use the min_uid variable that we determined earlier - - name: 4.1.10 - Ensure modifications to discretionary access controls are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/dac.rules - src: audit_rules/dac.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.10 - - - name: 4.1.11 - Ensure unsuccessful unauthorized file access attempts are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/bad-file-access.rules - content: - src: audit_rules/bad-file-access.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.11 - - # Control 4.1.12 - Ensure use of privileged ansible.builtin.commands is collected, is machine dependent - # skipping - - - name: 4.1.13 - Ensure successful file system mounts are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/file-system-mounts.rules - src: audit_rules/file-system-mounts.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.13 - - - name: 4.1.14 - Ensure file deletion events by users are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/delete.rules - src: audit_rules/delete.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.14 - - - name: 4.1.15 - Ensure sysadmin actions (sudolog) are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/sudolog.rules - src: audit_rules/sudolog.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.15 - 4.1.16 - - - name: 4.1.17 - Ensure kernel module loading and unloading is collected - ansible.builtin.template: - dest: /etc/audit/rules.d/modules.rules - src: audit_rules/modules.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.17 - - - name: 4.1.18 - Ensure audit configuration is immutable - ansible.builtin.copy: - dest: /etc/audit/rules.d/99-finalize.rules - content: | - -e 2 - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.18 - when: enable_audit is defined and enable_audit - - - - name: 4.2.1.1 - Ensure rsyslog is installed - ansible.builtin.package: - name: rsyslog - state: present - tags: - - 4.2.3 - - - name: 4.2.1.1 - Enable Rsyslog - ansible.builtin.service: - name: rsyslog - enabled: true - tags: - - 4.2.1.1 - - - name: 4.2.1.2 - Ensure logging is configured - ansible.builtin.copy: - src: "{{ rsyslog_file }}" - dest: "/etc/rsyslog.d/{{ rsyslog_file }}" - owner: root - group: root - mode: 0640 - when: rsylog_file is defined - tags: - - 4.2.1.2 - - - name: 4.2.1.3 - Ensure rsyslog default file permissions are configured - ansible.builtin.lineinfile: - path: /etc/rsyslog.conf - regexp: '^\$FileCreateMode\s+0640' - line: "$FileCreateMode 0640" - create: true - state: present +# Section 4 - Logging and Auditing + +- name: 4.1 Install and configure system auditing + when: enable_audit is defined and enable_audit + block: + - name: 4.1.1 - Install Audit + ansible.builtin.package: + name: + - audit + - audit-libs + state: present + tags: + - 4.1.1.1 + # The replace module here is looking through file and make replacements of partial lines + - name: 4.1.1-[2-3] - Configure audit log storage size + ansible.builtin.replace: + path: /etc/audit/auditd.conf + regexp: "{{ item.find }}" + replace: "{{ item.replace }}" + loop: + - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.1.1 + - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.1.2 + - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.1.2 + - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.1.2 + - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.1.2 + notify: Restart auditd + tags: + - 4.1.1.2 + - 4.1.1.3 + + - name: 4.1.2 - Enable auditd service + ansible.builtin.service: + name: auditd + enabled: true + state: started + tags: + - 4.1.2 + + - name: 4.1.1.3 - Ensure auditing for processes that start prior to auditd + # We check here because we don't know what position the audit=1 is in + # order to simply do the replace, so we are instead looking for the match in the file first. + # If it doesn't exist, then we can just insert it + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' + state: absent + check_mode: true + changed_when: false + register: audit_exist + failed_when: false + tags: + - 4.1.1.3 + + # use the replace module to add it to grub bootloader and then notify + # grub to rebuild + - name: 4.1.1.3 - enable audit service in grub + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit=1 ' + notify: Rebuild grub + when: not audit_exist.found + tags: + - 4.1.1.3 + + # For the next several checks, each one is in their own file, so we are using + # the copy module to place each file independently and then motifying + # a restart of auditd if anything changes. + - name: 4.1.4 - Ensure to collect events that modify date/time + ansible.builtin.template: + dest: /etc/audit/rules.d/datetime.rules + src: audit_rules/datetime.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.4 + + - name: 4.1.5 - Ensure events that modify user/group information are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/user-group-info.rules + src: audit_rules/user-group-info.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.5 + + - name: 4.1.6 - Ensure to collect events that modify network + ansible.builtin.template: + dest: /etc/audit/rules.d/network.rules + src: audit_rules/network.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.6 + + - name: 4.1.7 - Ensure modifications to Mandatory Access Controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/MAC-policy.rules + src: audit_rules/MAC-policy.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.7 + + - name: 4.1.8 - Ensure system logins are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/login.rules + src: audit_rules/login.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.8 + + - name: 4.1.9 - Ensure session initiation information is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/sessions.rules + src: audit_rules/sessions.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.9 + + # This is the first control that we use the min_uid variable that we determined earlier + - name: 4.1.10 - Ensure modifications to discretionary access controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/dac.rules + src: audit_rules/dac.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.10 + + - name: 4.1.11 - Ensure unsuccessful unauthorized file access attempts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/bad-file-access.rules + content: + src: audit_rules/bad-file-access.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.11 + + # Control 4.1.12 - Ensure use of privileged ansible.builtin.commands is collected, is machine dependent + # skipping + + - name: 4.1.13 - Ensure successful file system mounts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/file-system-mounts.rules + src: audit_rules/file-system-mounts.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.13 + + - name: 4.1.14 - Ensure file deletion events by users are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/delete.rules + src: audit_rules/delete.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.14 + + - name: 4.1.15 - Ensure sysadmin actions (sudolog) are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/sudolog.rules + src: audit_rules/sudolog.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.15 + 4.1.16 + + - name: 4.1.17 - Ensure kernel module loading and unloading is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/modules.rules + src: audit_rules/modules.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.17 + + - name: 4.1.18 - Ensure audit configuration is immutable + ansible.builtin.copy: + dest: /etc/audit/rules.d/99-finalize.rules + content: | + -e 2 + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.18 + + +- name: 4.2.1.1 - Ensure rsyslog is installed + ansible.builtin.package: + name: rsyslog + state: present + tags: + - 4.2.3 + +- name: 4.2.1.1 - Enable Rsyslog + ansible.builtin.service: + name: rsyslog + enabled: true + tags: + - 4.2.1.1 + +- name: 4.2.1.2 - Ensure logging is configured + ansible.builtin.copy: + src: "{{ rsyslog_file }}" + dest: "/etc/rsyslog.d/{{ rsyslog_file }}" owner: root group: root + mode: 0640 + when: rsylog_file is defined + tags: + - 4.2.1.2 + +- name: 4.2.1.3 - Ensure rsyslog default file permissions are configured + ansible.builtin.lineinfile: + path: /etc/rsyslog.conf + regexp: '^\$FileCreateMode\s+0640' + line: "$FileCreateMode 0640" + create: true + state: present mode: 0644 - tags: - - 4.2.1.3 + tags: + - 4.2.1.3 - # Control 4.2.1.4 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent - # skipping +# Control 4.2.1.4 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent +# skipping + +- name: 4.2.1.5 - Ensure remote rsyslog messages are only acepted on designated log hosts + tags: + - 4.2.1.5 + block: + - name: 4.2.1.5 - Find all rsyslog conf files in /etc/rsyslog.d + ansible.builtin.find: + paths: "/etc/rsyslog.d" + patterns: "*.conf" + register: rsyslog_module_found + + - name: 4.2.1.5 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.5 - Disable imtcp loading module on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$ModLoad\s+imtcp' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.5 - Disable TCP port listening on non log hosts (rsylog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$InputTCPServerRun' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.5 - Disable TCP port listening on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$InputTCPServerRun' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.5 - Enable loading of imtcp module on log hosts + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$ModLoad\s+imtcp' + line: "$ModLoad imtcp" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + - name: 4.2.1.5 - Enable TCP Port listening on port {{ log_port }} + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$InputTCPServerRun {{ log_port }}' + line: "$InputTCPServerRun {{ log_port }}" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + +# 4.2.2 - Configure syslog-ng skipped as it is not a supported package from RHEL + +# Ensure rsyslog package is installed (4.2.3) moved to before 4.2.1.1 which is the package install - - name: 4.2.1.5 - Ensure remote rsyslog messages are only acepted on designated log hosts - block: - - name: 4.2.1.5 - Find all rsyslog conf files in /etc/rsyslog.d - ansible.builtin.find: - paths: "/etc/rsyslog.d" - patterns: "*.conf" - register: rsyslog_module_found - - - name: 4.2.1.5 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) - ansible.builtin.lineinfile: - dest: "{{ item.path }}" - regexp: '^\$ModLoad\s+imtcp' - state: absent - loop: "{{ rsyslog_module_found.files }}" - when: log_host is defined and not log_host - - - name: 4.2.1.5 - Disable imtcp loading module on non log hosts (main rsyslog conf file) - ansible.builtin.lineinfile: - dest: "/etc/rsyslog.conf" - regexp: '^\$ModLoad\s+imtcp' - state: absent - when: log_host is defined and not log_host - - - name: 4.2.1.5 - Disable TCP port listening on non log hosts (rsylog.d conf files) - ansible.builtin.lineinfile: - dest: "{{ item.path }}" - regexp: '^\$InputTCPServerRun' - state: absent - loop: "{{ rsyslog_module_found.files }}" - when: log_host is defined and not log_host - - - name: 4.2.1.5 - Disable TCP port listening on non log hosts (main rsyslog conf file) - ansible.builtin.lineinfile: - dest: "/etc/rsyslog.conf" - regexp: '^\$InputTCPServerRun' - state: absent - when: log_host is defined and not log_host - - - name: 4.2.1.5 - Enable loading of imtcp module on log hosts - ansible.builtin.lineinfile: - dest: /etc/rsyslog.d/CIS.conf - regexp: '^\$ModLoad\s+imtcp' - line: "$ModLoad imtcp" - create: true - owner: root - group: root - mode: 0644 - when: log_host is defined and log_host - - - name: 4.2.1.5 - Enable TCP Port listening on port {{ log_port }} - ansible.builtin.lineinfile: - dest: /etc/rsyslog.d/CIS.conf - regexp: '^\$InputTCPServerRun {{ log_port }}' - line: "$InputTCPServerRun {{ log_port }}" - create: true - owner: root - group: root - mode: 0644 - when: log_host is defined and log_host - tags: - - 4.2.1.5 - - # 4.2.2 - Configure syslog-ng skipped as it is not a supported package from RHEL - - # Ensure rsyslog package is installed (4.2.3) moved to before 4.2.1.1 which is the package install - - - name: 4.3 - Ensure logrotate is installed and configured - ansible.builtin.package: - name: logrotate - state: present - tags: - - 4.3.0 - - # 4.3 - Ensure logrotate is configured skipped as machine and environment dependent - - # Section 5 - Access and Authorization - # - - # This control is early in order to create the files. This will - # make sure they are available when cron starts - - name: Create the cron/at allow files (5.1.8) - ansible.builtin.copy: - dest: "{{ item }}" - content: "" - force: false - owner: root - group: root - mode: 0644 - with_items: - - /etc/cron.allow - - /etc/at.allow - tags: - - 5.1.8 - - - name: 5.1.1 - Ensure cron is enabled (RHEL Clones) - ansible.builtin.service: - name: crond - enabled: true - state: started - when: ansible_distribution != "SLES" - tags: - - 5.1.1 - - - name: 5.1.2 - Ensure permissions on /etc/crontab - ansible.builtin.file: - path: /etc/crontab - owner: root - group: root - mode: 0600 - tags: - - 5.1.2 - - - name: 5.1.[3-7] - Ensure permissions on crontab directories - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0700 - loop: - - /etc/cron.hourly - - /etc/cron.daily - - /etc/cron.weekly - - /etc/cron.monthly - - /etc/cron.d - tags: - - 5.1.3 - - 5.1.4 - - 5.1.5 - - 5.1.6 - - 5.1.7 - - # Restrict at/cron skipped (5.1.8) as is rarely used and environment dependent - - # If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag - - name: 5.2 - SSH File configurations - block: +- name: 4.3 - Ensure logrotate is installed and configured + ansible.builtin.package: + name: logrotate + state: present + tags: + - 4.3.0 + +# 4.3 - Ensure logrotate is configured skipped as machine and environment dependent + +# Section 5 - Access and Authorization +# + +# This control is early in order to create the files. This will +# make sure they are available when cron starts +- name: Create the cron/at allow files (5.1.8) + ansible.builtin.copy: + dest: "{{ item }}" + content: "" + force: false + owner: root + group: root + mode: 0644 + with_items: + - /etc/cron.allow + - /etc/at.allow + tags: + - 5.1.8 + +- name: 5.1.1 - Ensure cron is enabled (RHEL Clones) + ansible.builtin.service: + name: crond + enabled: true + state: started + when: ansible_distribution != "SLES" + tags: + - 5.1.1 + +- name: 5.1.2 - Ensure permissions on /etc/crontab + ansible.builtin.file: + path: /etc/crontab + owner: root + group: root + mode: 0600 + tags: + - 5.1.2 + +- name: 5.1.[3-7] - Ensure permissions on crontab directories + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0700 + loop: + - /etc/cron.hourly + - /etc/cron.daily + - /etc/cron.weekly + - /etc/cron.monthly + - /etc/cron.d + tags: + - 5.1.3 + - 5.1.4 + - 5.1.5 + - 5.1.6 + - 5.1.7 + +# Restrict at/cron skipped (5.1.8) as is rarely used and environment dependent + +# If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag +- name: 5.2 - SSH File configurations + tags: + 5.2.0 + block: - name: 5.2.1 - Set permissions on SSH file ansible.builtin.file: dest: /etc/ssh/sshd_config @@ -1991,15 +1987,15 @@ tags: - 5.2.1 - # Control 5.2.2, Ensure SSH Protocol is set to 2, is not supported in OpenSSH - # supplied by Red Hat (or SUSE 15) any longer. Omitting + # Control 5.2.2, Ensure SSH Protocol is set to 2, is not supported in OpenSSH + # supplied by Red Hat (or SUSE 15) any longer. Omitting - name: 5.2.3 - Set LogLevel to {{ ssh_log_level }} or more verbose, but not ansible.builtin.debug ansible.builtin.replace: path: /etc/ssh/sshd_config replace: "LogLevel {{ ssh_log_level | upper }}" regexp: '^#?LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' - notify: restart sshd + notify: Restart sshd when: ssh_log_level == "INFO" or ssh_log_level == "WARN" tags: - 5.2.3 @@ -2011,7 +2007,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^X11Forwarding\s*yes' - notify: restart sshd + notify: Restart sshd tags: - 5.2.4 @@ -2021,7 +2017,7 @@ line: "MaxAuthTries {{ ssh_max_auth_tries }}" regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' insertafter: "^#MaxAuthTries" - notify: restart sshd + notify: Restart sshd tags: - 5.2.5 @@ -2030,7 +2026,7 @@ path: /etc/ssh/sshd_config line: "IgnoreRhosts yes" regexp: '^IgnoreRhosts\s*[^y]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.6 @@ -2039,7 +2035,7 @@ path: /etc/ssh/sshd_config line: "HostbasedAuthentication no" regexp: '^HostbasedAuthentication\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.7 @@ -2048,7 +2044,7 @@ path: /etc/ssh/sshd_config line: "PermitRootLogin no" regexp: '^PermitRootLogin\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.8 @@ -2057,7 +2053,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.9 @@ -2066,7 +2062,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.10 @@ -2076,7 +2072,7 @@ line: "MACs {{ ssh_mac_list }}" insertafter: EOF regexp: '^MACs {{ ssh_mac_list }}' - notify: restart sshd + notify: Restart sshd tags: - 5.2.11 @@ -2086,7 +2082,7 @@ line: "ClientAliveInterval {{ ssh_alive_interval }}" regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" insertafter: "^#ClientAliveInterval" - notify: restart sshd + notify: Restart sshd tags: - 5.2.12 @@ -2096,7 +2092,7 @@ line: "ClientAliveCountMax {{ ssh_alive_count_max }}" regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" insertafter: "^#ClientAliveCountMax" - notify: restart sshd + notify: Restart sshd tags: - 5.2.12 @@ -2106,7 +2102,7 @@ line: "LoginGraceTime {{ ssh_grace_time }}" regexp: "^LoginGraceTime {{ ssh_grace_time }}" insertafter: "^#LoginGraceTime" - notify: restart sshd + notify: Restart sshd tags: - 5.2.13 @@ -2118,7 +2114,7 @@ path: "/etc/ssh/sshd_config" line: "Banner /etc/{{ ssh_login_banner }}" regexp: "^Banner /etc/{{ ssh_login_banner }}" - notify: restart sshd + notify: Restart sshd tags: - 5.2.15 @@ -2130,16 +2126,14 @@ line: "AllowTcpForwarding no" regexp: '^AllowTcpForwarding\s+(yes|no)' insertafter: "^#AllowTcpForwarding" - notify: restart sshd + notify: Restart sshd tags: - 5.2.17 - tags: - 5.2.0 - - - - name: 5.3.1 - Configure PAM files and password requirements - block: +- name: 5.3.1 - Configure PAM files and password requirements + tags: + - 5.3.1 + block: - name: 5.3.1 - require at least one digit in passwords ansible.builtin.lineinfile: path: /etc/security/pwquality.conf @@ -2179,8 +2173,6 @@ regexp: "^minlen = {{ password_min_length }}" insertafter: "^# minlen =" when: password_req_digit - tags: - - 5.3.1 # Control 5.3.2, Ensure lockout for failed password attempts, really requires a file replacement # skipping @@ -2188,8 +2180,11 @@ # Control 5.3.3, Set password retention, requries file replacement # skipping - - name: 5.3.4 - Ensure password hashing algorithm is set to sha-512 - block: +- name: 5.3.4 - Ensure password hashing algorithm is set to sha-512 + when: not ansible_check_mode + tags: + 5.3.4 + block: - name: 5.3.4 - Determine if we are currently using sha512 password ansible.builtin.command: authconfig --test register: sha512_exist @@ -2199,390 +2194,386 @@ ansible.builtin.command: /usr/sbin/authconfig --enableshadow --passalgo sha512 --update when: not sha512_exist.stdout is search("sha512") failed_when: false - when: not ansible_check_mode - tags: - 5.3.4 - - - name: 5.4.1.1 - Ensure password expiration is {{ password_expire_days }} days or less - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' - line: "PASS_MAX_DAYS {{ password_expire_days }}" - state: present - tags: - - 5.4.1.1 - - - name: 5.4.1.2 - Ensure password change days is set to {{ password_min_days }} - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' - line: "PASS_MIN_DAYS {{ password_min_days }}" - state: present - tags: - - 5.4.1.2 - - - name: 5.4.1.3 - Ensure password warning days is set to {{ password_warning_days }} - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' - line: "PASS_WARN_AGE {{ password_warning_days }}" - state: present - tags: - - 5.4.1.3 - - # We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days - # The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well - - name: 5.4.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration - ansible.builtin.replace: - path: /etc/default/useradd - regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" - replace: "INACTIVE={{ password_inactive_lock_days }}" - owner: root - group: root - mode: 0600 - tags: - - 5.4.1.4 - - # 5.4.1.5, Ensure all users last password change date is in the past, - # is not easily automated. Will revisit later - - # 5.4.2, Ensure all system accounts do not have an active shell, - # is not easily automated. Will revisit later - - # Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod - - name: 5.4.3 - Ensure default group for root is GID 0 - ansible.builtin.command: /usr/sbin/usermod -g 0 root - changed_when: false - tags: - - 5.4.3 - - - name: 5.4.4 - Ensure umask is set - ansible.builtin.replace: - path: "{{ item }}" - replace: " umask {{ default_umask }}" - regexp: '^\s*umask\s*022' - loop: - - /etc/bashrc - - /etc/profile - when: ansible_distribution != "SLES" - tags: - - 5.4.4 - - - name: 5.4.5 - Ensure shell timeout is {{ shell_timeout }} seconds or less - ansible.builtin.blockinfile: - path: "{{ item }}" - block: "TMOUT={{ shell_timeout }}" - marker: "# {mark} Ansible Managed CIS Timeout" - loop: - - /etc/bashrc - - /etc/profile - when: ansible_distribution != "SLES" - tags: - - 5.4.5 - - - # 5.5.5, Ensure root login is restricted to system console - # not easily automatable because of the various TTYs on a machine - # Manually verify that only physically secure TTYs are listed in - # /etc/securetty - - - name: 5.6 - Restrict su to wheel group - block: - - name: Configure PAM to only allow su from wheel group (5.6) - ansible.builtin.replace: - path: /etc/pam.d/su - regexp: '^#auth\s+required\s+pam_wheel.so\s+use_uid' - replace: "auth required pam_wheel.so use_uid" - - - name: Add root to the wheel group (5.6) - ansible.builtin.user: - name: root - groups: wheel - append: true - tags: - - 5.6.0 - - # Section 6 - System Maintenance - # - - - name: 6.1.[2,4] - Ensure permissions on /etc/passwd /etc/group - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0644 - loop: - - passwd - - group - tags: - - 6.1.2 - - 6.1.4 - - - name: 6.1.[3,5] - Ensure permissions on /etc/shadow /etc/gshadow - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - loop: - - shadow - tags: - - 6.1.3 - - - name: 6.1.[3,5] - Ensure permissions on /etc/gshadow - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - loop: - - gshadow - when: ansible_distribution != "SLES" - tags: - - 6.1.5 - - - name: 6.1.[6-8] - Ensure permissions on /etc/passwd- /etc/shadow- /etc/group- - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - with_items: - - passwd- - - shadow- - - group- - tags: - - 6.1.6 - - 6.1.7 - - 6.1.8 - - - name: 6.1.9 - Ensure permissions on /etc/passwd- /etc/gshadow- - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - with_items: - - gshadow- - when: ansible_distribution != "SLES" - tags: - - 6.1.9 - - # Control 6.1.10, Ensure no world writable files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.10 - Ensure no world writable files exist - block: - - name: 6.1.10 - Find any world writiable files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" - register: ww_files - changed_when: false - check_mode:false - - - name: 6.1.10 - Print any world writable files found - ansible.builtin.debug: - msg: "World writiable files found: {{ ww_files.stdout }}" - changed_when: true - when: ww_files.stdout - tags: - - 6.1.10 - - # Control 6.1.11, Ensure no unowned files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.11 - Ensure no unowned files exist - block: - - name: 6.1.11 - Find any unowned files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" - register: uo_files - changed_when: false - check_mode:false - - - name: 6.1.11 - Print any unowned files found - ansible.builtin.debug: - msg: "unowned files found: {{ uo_files.stdout }}" - changed_when: true - when: uo_files.stdout - tags: - - 6.1.11 - - # Control 6.1.12, Enscure no ungrouped files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.12 - Ensure no ungrouped files exist - block: - - name: 6.1.12 - Find any ungrouped files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" - register: ug_files - changed_when: false - check_mode:false - - - name: 6.1.12 - Print any ungrouped files found - ansible.builtin.debug: - msg: "ungrouped files found: {{ uo_files.stdout }}" - changed_when: true - when: ug_files.stdout - tags: - - 6.1.12 - - - # Control 6.1.13, Audit SUID executables, is a verification and is system dependent. - # Not implementing because it will always return some SUID files - # Manually review the control - - # Control 6.1.14, Audit SGID executables, is a verification and is system dependent. - # Not implementing because it will always return some SUID files - # Manually review the control - - - name: 6.2.1 - Ensure password fields are not empty - block: - - name: 6.2.1 - Check to see if there are any accounts with empty passwords - ansible.builtin.shell: "/usr/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" - changed_when: false - register: empty_passwords - check_mode:false - - - name: 6.2.1 - Report the named users to the report - ansible.builtin.debug: - msg: "The user {{ item }} has an empty password" - when: empty_passwords.stdout - changed_when: true - loop: "{{ empty_passwords.stdout_lines }}" - tags: - - 6.2.1 - - - name: 6.2.[2,4-5] - Ensure no legacy "+" entries exist in password files - ansible.builtin.lineinfile: - regexp: '^\+:.*' - state: absent - path: "{{ item }}" - when: ypbind is defined and not ypbind - loop: - - /etc/passwd - - /etc/shadow - - /etc/group - tags: - - 6.2.2 - - 6.2.3 - - 6.2.4 - - - name: Report on multiple accounts with UID of 0 (6.2.5) - block: - - name: find accounts with UID of 0 - ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" - register: rootuid - changed_when: rootuid.rc == 2 - check_mode: false - - - name: Report on mulitple accounts with UID of 0 - ansible.builtin.debug: - msg: "Accounts with UID zero in addition to root" - var: rootuid.stdout - when: rootuid.stdout != 'root' - tags: - - 6.2.5 - - - name: 6.2.6 - Ensure root PATH integrity - block: - - name: 6.2.6 - Run script on path variable - ansible.builtin.script: files/path_check.sh - changed_when: false - register: path_check - check_mode: false - - - name: 6.2.6 - Print report to user - ansible.builtin.debug: - msg: - - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" - - "that exist in root's path because of this. It should be run as root on the target machine manually." - - " {{ path_check.stdout }}" - when: path_check.stdout - tags: - - 6.2.6 - - # Control 6.2.7 is environment dependent, skipping - # Control 6.2.8 is environment dependent, skipping - # Control 6.2.9 is environment dependent, skipping - # Controls 6.2.[10-14] are recommended to be handled by monitoring software - - - name: 6.2.15 - Report on groups in /etc/passwd with a GID not in /etc/group - block: - - name: 6.2.15 - Use script to pull the list of groups - ansible.builtin.script: - cmd: files/undefined_groups.sh - register: undefined_groups - changed_when: false - check_mode: false - - - name: 6.2.15 - Report to user any unreferenced groups - ansible.builtin.debug: - msg: "{{ undefined_groups.stdout_lines }}" - changed_when: true - when: undefined_groups.stdout - tags: - - 6.2.15 - - - name: 6.2.16 - Report on duplicate UIDs in /etc/passwd - block: - - name: 6.2.16 - Use script to pull the list of duplicate UIDs - ansible.builtin.script: - cmd: files/duplicate_uids.sh - register: duplicate_uids - changed_when: false - check_mode: false - - - name: 6.2.16 - Print report of duplicated UIDs to user - ansible.builtin.debug: - msg: "{{ duplicate_uids.stdout_lines }}" - changed_when: true - when: duplicate_uids.stdout - tags: - - 6.2.16 - - - name: 6.2.17 - Report on duplicate GIDs in /etc/group - block: - - name: 6.2.17 - Use script to pull the list of duplicate GIDs - ansible.builtin.script: - cmd: files/duplicate_guids.sh - register: duplicate_guids - changed_when: false - check_mode: false - - - name: 6.2.17 - Print report of duplcate GIDs to user - ansible.builtin.debug: - msg: "{{ duplicate_guids.stdout_lines }}" - changed_when: true - when: duplicate_guids.stdout - tags: - - 6.2.17 - - - name: 6.2.18 - Report on duplicate users in /etc/passwd - block: - - name: 6.2.18 - Use script to pull the list of users - ansible.builtin.script: - cmd: files/duplicate_users.sh - register: duplicate_users - changed_when: false - check_mode: false - - - name: 6.2.18 - Print report of duplicate users to user - ansible.builtin.debug: - msg: "{{ duplicate_users.stdout_lines }}" - changed_when: true - when: duplicate_users.stdout - tags: - - 6.2.18 - - - name: 6.2.19 - Report on duplicate groups in /etc/group - block: - - name: 6.2.19 - Use script to pull the list of groups - ansible.builtin.script: - cmd: files/duplicate_groups.sh - register: duplicate_groups - changed_when: false - check_mode: false - - - name: 6.2.19 - Print report of duplicate groups to user - ansible.builtin.debug: - msg: "{{ duplicate_groups.stdout_lines }}" - changed_when: true - when: duplicate_groups.stdout - tags: - - 6.2.19 + +- name: 5.4.1.1 - Ensure password expiration is {{ password_expire_days }} days or less + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' + line: "PASS_MAX_DAYS {{ password_expire_days }}" + state: present + tags: + - 5.4.1.1 + +- name: 5.4.1.2 - Ensure password change days is set to {{ password_min_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' + line: "PASS_MIN_DAYS {{ password_min_days }}" + state: present + tags: + - 5.4.1.2 + +- name: 5.4.1.3 - Ensure password warning days is set to {{ password_warning_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' + line: "PASS_WARN_AGE {{ password_warning_days }}" + state: present + tags: + - 5.4.1.3 + +# We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days +# The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well +- name: 5.4.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration + ansible.builtin.replace: + path: /etc/default/useradd + regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" + replace: "INACTIVE={{ password_inactive_lock_days }}" + owner: root + group: root + mode: 0600 + tags: + - 5.4.1.4 + +# 5.4.1.5, Ensure all users last password change date is in the past, +# is not easily automated. Will revisit later + +# 5.4.2, Ensure all system accounts do not have an active shell, +# is not easily automated. Will revisit later + +# Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod +- name: 5.4.3 - Ensure default group for root is GID 0 + ansible.builtin.command: /usr/sbin/usermod -g 0 root + changed_when: false + tags: + - 5.4.3 + +- name: 5.4.4 - Ensure umask is set + ansible.builtin.replace: + path: "{{ item }}" + replace: " umask {{ default_umask }}" + regexp: '^\s*umask\s*022' + loop: + - /etc/bashrc + - /etc/profile + when: ansible_distribution != "SLES" + tags: + - 5.4.4 + +- name: 5.4.5 - Ensure shell timeout is {{ shell_timeout }} seconds or less + ansible.builtin.blockinfile: + path: "{{ item }}" + block: "TMOUT={{ shell_timeout }}" + marker: "# {mark} Ansible Managed CIS Timeout" + loop: + - /etc/bashrc + - /etc/profile + when: ansible_distribution != "SLES" + tags: + - 5.4.5 + + +# 5.5.5, Ensure root login is restricted to system console +# not easily automatable because of the various TTYs on a machine +# Manually verify that only physically secure TTYs are listed in +# /etc/securetty + +- name: 5.6 - Restrict su to wheel group + tags: + - 5.6.0 + block: + - name: Configure PAM to only allow su from wheel group (5.6) + ansible.builtin.replace: + path: /etc/pam.d/su + regexp: '^#auth\s+required\s+pam_wheel.so\s+use_uid' + replace: "auth required pam_wheel.so use_uid" + + - name: Add root to the wheel group (5.6) + ansible.builtin.user: + name: root + groups: wheel + append: true + +# Section 6 - System Maintenance +# + +- name: 6.1.[2,4] - Ensure permissions on /etc/passwd /etc/group + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0644 + loop: + - passwd + - group + tags: + - 6.1.2 + - 6.1.4 + +- name: 6.1.[3,5] - Ensure permissions on /etc/shadow /etc/gshadow + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + loop: + - shadow + tags: + - 6.1.3 + +- name: 6.1.[3,5] - Ensure permissions on /etc/gshadow + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + loop: + - gshadow + when: ansible_distribution != "SLES" + tags: + - 6.1.5 + +- name: 6.1.[6-8] - Ensure permissions on /etc/passwd- /etc/shadow- /etc/group- + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + with_items: + - passwd- + - shadow- + - group- + tags: + - 6.1.6 + - 6.1.7 + - 6.1.8 + +- name: 6.1.9 - Ensure permissions on /etc/passwd- /etc/gshadow- + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + with_items: + - gshadow- + when: ansible_distribution != "SLES" + tags: + - 6.1.9 + +# Control 6.1.10, Ensure no world writable files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.10 - Ensure no world writable files exist + tags: + - 6.1.10 + block: + - name: 6.1.10 - Find any world writiable files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" + register: ww_files + changed_when: false + check_mode: false + + - name: 6.1.10 - Print any world writable files found + ansible.builtin.debug: + msg: "World writiable files found: {{ ww_files.stdout }}" + changed_when: true + when: ww_files.stdout + +# Control 6.1.11, Ensure no unowned files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.11 - Ensure no unowned files exist + tags: + - 6.1.11 + block: + - name: 6.1.11 - Find any unowned files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" + register: uo_files + changed_when: false + check_mode: false + + - name: 6.1.11 - Print any unowned files found + ansible.builtin.debug: + msg: "unowned files found: {{ uo_files.stdout }}" + changed_when: true + when: uo_files.stdout + +# Control 6.1.12, Enscure no ungrouped files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.12 - Ensure no ungrouped files exist + tags: + - 6.1.12 + block: + - name: 6.1.12 - Find any ungrouped files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" + register: ug_files + changed_when: false + check_mode: false + + - name: 6.1.12 - Print any ungrouped files found + ansible.builtin.debug: + msg: "ungrouped files found: {{ uo_files.stdout }}" + changed_when: true + when: ug_files.stdout + +# Control 6.1.13, Audit SUID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +# Control 6.1.14, Audit SGID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +- name: 6.2.1 - Ensure password fields are not empty + tags: + - 6.2.1 + block: + - name: 6.2.1 - Check to see if there are any accounts with empty passwords + ansible.builtin.shell: "/usr/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" + changed_when: false + register: empty_passwords + check_mode: false + + - name: 6.2.1 - Report the named users to the report + ansible.builtin.debug: + msg: "The user {{ item }} has an empty password" + when: empty_passwords.stdout + changed_when: true + loop: "{{ empty_passwords.stdout_lines }}" + +- name: 6.2.[2,4-5] - Ensure no legacy "+" entries exist in password files + ansible.builtin.lineinfile: + regexp: '^\+:.*' + state: absent + path: "{{ item }}" + when: ypbind is defined and not ypbind + loop: + - /etc/passwd + - /etc/shadow + - /etc/group + tags: + - 6.2.2 + - 6.2.3 + - 6.2.4 + +- name: Report on multiple accounts with UID of 0 (6.2.5) + tags: + - 6.2.5 + block: + - name: Find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc == 2 + check_mode: false + + - name: Report on mulitple accounts with UID of 0 + ansible.builtin.debug: + msg: "Accounts with UID zero in addition to root" + var: rootuid.stdout + when: rootuid.stdout != 'root' + +- name: 6.2.6 - Ensure root PATH integrity + tags: + - 6.2.6 + block: + - name: 6.2.6 - Run script on path variable + ansible.builtin.script: files/path_check.sh + changed_when: false + register: path_check + check_mode: false + + - name: 6.2.6 - Print report to user + ansible.builtin.debug: + msg: + - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" + - "that exist in root's path because of this. It should be run as root on the target machine manually." + - " {{ path_check.stdout }}" + when: path_check.stdout + +# Control 6.2.7 is environment dependent, skipping +# Control 6.2.8 is environment dependent, skipping +# Control 6.2.9 is environment dependent, skipping +# Controls 6.2.[10-14] are recommended to be handled by monitoring software + +- name: 6.2.15 - Report on groups in /etc/passwd with a GID not in /etc/group + tags: + - 6.2.15 + block: + - name: 6.2.15 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/undefined_groups.sh + register: undefined_groups + changed_when: false + check_mode: false + + - name: 6.2.15 - Report to user any unreferenced groups + ansible.builtin.debug: + msg: "{{ undefined_groups.stdout_lines }}" + changed_when: true + when: undefined_groups.stdout + +- name: 6.2.16 - Report on duplicate UIDs in /etc/passwd + tags: + - 6.2.16 + block: + - name: 6.2.16 - Use script to pull the list of duplicate UIDs + ansible.builtin.script: + cmd: files/duplicate_uids.sh + register: duplicate_uids + changed_when: false + check_mode: false + + - name: 6.2.16 - Print report of duplicated UIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_uids.stdout_lines }}" + changed_when: true + when: duplicate_uids.stdout + +- name: 6.2.17 - Report on duplicate GIDs in /etc/group + tags: + - 6.2.17 + block: + - name: 6.2.17 - Use script to pull the list of duplicate GIDs + ansible.builtin.script: + cmd: files/duplicate_guids.sh + register: duplicate_guids + changed_when: false + check_mode: false + + - name: 6.2.17 - Print report of duplcate GIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_guids.stdout_lines }}" + changed_when: true + when: duplicate_guids.stdout + +- name: 6.2.18 - Report on duplicate users in /etc/passwd + tags: + - 6.2.18 + block: + - name: 6.2.18 - Use script to pull the list of users + ansible.builtin.script: + cmd: files/duplicate_users.sh + register: duplicate_users + changed_when: false + check_mode: false + + - name: 6.2.18 - Print report of duplicate users to user + ansible.builtin.debug: + msg: "{{ duplicate_users.stdout_lines }}" + changed_when: true + when: duplicate_users.stdout + +- name: 6.2.19 - Report on duplicate groups in /etc/group + tags: + - 6.2.19 + block: + - name: 6.2.19 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/duplicate_groups.sh + register: duplicate_groups + changed_when: false + check_mode: false + + - name: 6.2.19 - Print report of duplicate groups to user + ansible.builtin.debug: + msg: "{{ duplicate_groups.stdout_lines }}" + changed_when: true + when: duplicate_groups.stdout diff --git a/roles/cis_security/tasks/type-files/redhat-8-type.yml b/roles/cis_security/tasks/type-files/redhat-8-type.yml index fd27e3a..a1f90e3 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -11,1382 +11,1715 @@ # Comments about how the modules are used will become more infrequent as # the file goes along to avoid repeating oneself. - # Let the user know what version of the controls file is running - # Use a variable so it prints out the correct version. - - name: Print Header - ansible.builtin.debug: msg="CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" - - # Collect the packages installed on the system so we can check agains them later - - name: Collect package list - ansible.builtin.package_facts: - manager: auto - tags: - - always - - # Find the minimum UID of the machine for normal acocunts. This varies - # between machines and environments, so we pull it from the file it - # is supposed to exist in. - - name: Determine the Minimum UID for new, non-system, accounts - ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" - register: min_uid - changed_when: min_uid.rc == "2" - check_mode: false - tags: - - always - - # Update the system with security packages using the system's package manager - # Only update the system if the 'update_system' variable is set to true - - name: 1.9.0 - Ensure updated system - ansible.builtin.dnf: - name: "*" - state: latest - security: true - when: update_system - tags: - - 1.9.0 - - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: 1.1 Disable unused filesystems - ansible.builtin.set_fact: - unused_filesystems: [] - - - name: 1.1.1.1 - Add cramfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'cramfs' ] }}" - tags: - - 1.1.1.1 - - - name: 1.1.1.2 - Add vfat to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'vfat' ] }}" - tags: - - 1.1.1.2 - - - name: 1.1.1.3 - Add squashfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'squashfs' ] }}" - tags: - - 1.1.1.3 - - - name: 1.1.1.4 - Add udf to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'udf' ] }}" - tags: - - 1.1.1.4 - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: Remove unused_filesystem list - ansible.builtin.dnf: - name: unused_filesystems - state: absent - - - name: Add unused_filesystems to /etc/modprobe.d/CIS.conf - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" - state: present - create: true - owner: root - group: root - mode: 0644 - with_items: - - "{{ unused_filesystems }}" - - # Create and configure the local-fs systemd service file - - name: 1.1.[2-5] - Ensure /tmp is configured - block: - # Create a file to hold the system specific local-fs service information - # be sure to set the selinux security context. Even if selinux is disabled, - # it's a good idea to make sure it is set on files - - name: Ensure the local-fs directory is created - ansible.builtin.file: - path: /etc/systemd/system/local-fs.target.wants - state: directory - owner: root - group: root - mode: 0755 - setype: etc_t - - # Add content to the file we created using the blockinfile command. - # Notify systemd to reload its daemons and start the local-fs service - - name: 1.1.[2-5] - Configure config file for tmpfs - ansible.builtin.blockinfile: - path: /etc/systemd/system/local-fs.target.wants/tmp.mount - block: | - [Mount] - What=tmpfs - Where=/tmp - Type=tmpfs - Options=mode=1777,strictatime,noexec,nodev,nosuid - create: true - owner: root - group: root - mode: 0644 - notify: restart tmpfs - tags: - - 1.1.2 - - 1.1.3 - - 1.1.4 - - 1.1.5 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.6 - Report if /var is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.6 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.6 - Determine if /var is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.6 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.6 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.7 - /var/tmp partition and mount options - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.7 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - tags: - - 1.1.7 - - 1.1.8 - - 1.1.9 - - 1.1.10 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.7 - Determine if /var/tmp is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - mount_options: "{{ item.options }}" - when: item.mount == "/var/tmp" - with_items: - - "{{ ansible_mounts }}" - tags: - - 1.1.7 - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.7 - Report to user if /var/tmp not on separate partition - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" - when: mount_count == 0 - changed_when: true - tags: - - 1.1.7 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.8 - Report to user if /var/tmp does not have nodev set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.8 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.9 - Report to user if /var/tmp does not have nosuid set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" - when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.9 - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: 1.1.10 - Report to user if /var/tmp does not have noexec set - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" - when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 - changed_when: true - tags: - - 1.1.10 - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.7 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.11 - Report if /var/log is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.11 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.11 - Determine if /var/log is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var/log" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.11 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/log is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.11 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.12 - Report if /var/log/audit is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.12 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.12 - Determine if /var/log/audit is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - when: item.mount == "/var/log/audit" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.12 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" - when: mount_count == 0 - changed_when: true - # This whole block can be turned off by excluding the following tag(s) - tags: - - 1.1.12 - - # Determine if a filesystem is on a separate partition, if so, then - # check to see if various filesystem options exist for the filesystem - - name: 1.1.13 - Report if /home is not on a separate partition - block: - # Create a empty integer variable and set it as a fact on the managed - # machine. - - name: 1.1.13 - Set/reset mount counter - ansible.builtin.set_fact: - mount_count: 0 - - # Examine the ansible_mounts variable which includes all of the system mounts - # on machine. Search for the appropriate mount information. If it exists, - # increment the integer variable by '1' and save the filesystems options to a - # new variable called mount_options. - - name: 1.1.13 - Determine if /home is on a separate partition - ansible.builtin.set_fact: - mount_count: "addition{{ mount_count + 1 }}" - mount_options: "{{ item.options }}" - when: item.mount == "/home" - with_items: - - "{{ ansible_mounts }}" - - # If the number in mount_count variable is > 0, then we found the mount. If not, - # then report to the user that the given filesystem was not on a separate partition. - - name: 1.1.13 - Report to user if /home is not on a separate partition - ansible.builtin.debug: - msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" - when: mount_count == 0 - changed_when: true - - # Look through the mount_options variable for the given filesystem option. if it is - # not found, or if the filesystem is not on a separate partition (therefore has no mount options) - # let the user know. - - name: Report to user if /home does not have nodev set - ansible.builtin.debug: - msg: "FAILED CONTROL: /home does not have nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.13 - - # /dev/shm does not exist in ansible_mounts so we have to check the - # mount command directly. This requires the use of the shell command which - # is not ideal. - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.15 - Report if /dev/shm does not have nodev set - block: - - name: Determine if /dev/shm has nodev set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nodev - register: devshm_nodev_out - failed_when: devshm_nodev_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.15 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have nodev set" - when: devshm_nodev_out is defined and devshm_nodev_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.15 - - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.16 - Report if /dev/shm does not have nosuid set - block: - - name: Determine if /dev/shm has nosuid set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nosuid - register: devshm_nosuid_out - failed_when: devshm_nosuid_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.16 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have nosuid set" - when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.16 - - # Grep out /dev/shm and see if the given option is set. - - name: 1.1.17 - Report if /dev/shm does not have noexec set - block: - - name: 1.1.17 - Determine if /dev/shm has noexec set - ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v noexec - register: devshm_noexec_out - failed_when: devshm_noexec_out == "2" - changed_when: false - check_mode: false - - # Let the user know if we did not find the option set. - - name: 1.1.17 - Report to user - ansible.builtin.debug: - msg: "FAILED CONTROL: /dev/shm does not have noexec set" - when: devshm_noexec_out is defined and devshm_noexec_out.stdout - changed_when: true - tags: - # This whole block can be turned off by excluding the following tag(s) - - 1.1.17 - -# Control 1.1.18, 1.1.19, 1.1.20 are for removable media +# Let the user know what version of the controls file is running +# Use a variable so it prints out the correct version. +- name: Print Header + ansible.builtin.debug: + msg: "CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" + +# Collect the packages installed on the system so we can check agains them later +- name: Collect package list + ansible.builtin.package_facts: + manager: auto + tags: + - always + +# Find the minimum UID of the machine for normal acocunts. This varies +# between machines and environments, so we pull it from the file it +# is supposed to exist in. +- name: Determine the Minimum UID for new, non-system, accounts + ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" + register: min_uid + changed_when: min_uid.rc == "2" + check_mode: false + tags: + - always + +# Update the system with security packages using the system's package manager +# Only update the system if the 'update_system' variable is set to true +- name: 1.9.0 - Ensure updated system + ansible.builtin.dnf: + name: "*" + state: latest + security: true + when: update_system + tags: + - 1.9.0 + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. + +# RHEL 8.0 v2.0.0 controls removed a lot of the older unused filesystems from controls, so switching to a more basic +# setup to match RHEL 9. +- name: 1.1.1.0 - Remove old /etc/modprobe.d/CIS.conf + ansible.builtin.file: + path: /etc/modprobe.d/CIS.conf + state: absent + tags: + - 1.1.1.0 + +- name: 1.1.1.1 - disable cramfs + ansible.builtin.blockinfile: + path: /etc/modprobe.d/cramfs.conf + create: true + owner: root + group: root + mode: 644 + setype: modules_conf_t + block: | + install cramfs /bin/false + blacklist cramfs + tags: + - 1.1.1.1 + +- name: 1.1.1.2 - disable squashfs + ansible.builtin.blockinfile: + path: /etc/modprobe.d/squashfs.conf + create: true + owner: root + group: root + mode: 644 + setype: modules_conf_t + block: | + install squashfs /bin/false + blacklist squashfs + tags: + - 1.1.1.2 + +- name: 1.1.1.3 - disable udf + ansible.builtin.blockinfile: + path: /etc/modprobe.d/udf.conf + create: true + owner: root + group: root + mode: 0644 + setype: modules_conf_t + block: | + install udf /bin/false + blacklist udf + tags: + - 1.1.1.3 + +# Create and configure the local-fs systemd service file +- name: 1.1.[2-5] - Ensure /tmp is configured + tags: + - 1.1.2.1 + - 1.1.2.2 + - 1.1.2.3 + - 1.1.2.4 + block: + # Create a file to hold the system specific local-fs service information + # be sure to set the selinux security context. Even if selinux is disabled, + # it's a good idea to make sure it is set on files + - name: 1.1.1.2 - Ensure the local-fs directory is created + ansible.builtin.file: + path: /etc/systemd/system/local-fs.target.wants + state: directory + owner: root + group: root + mode: 0755 + setype: etc_t + + # Add content to the file we created using the blockinfile command. + # Notify systemd to reload its daemons and start the local-fs service + - name: 1.1.2.[2-4] - Configure config file for tmpfs + ansible.builtin.blockinfile: + path: /etc/systemd/system/local-fs.target.wants/tmp.mount + block: | + [Mount] + What=tmpfs + Where=/tmp + Type=tmpfs + Options=mode=1777,strictatime,noexec,nodev,nosuid + create: true + owner: root + group: root + mode: 0644 + notify: Restart tmpfs + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.3 - Configure /var + tags: + - 1.1.3 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.3 - Set/reset /var mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.3.1 - Determine if /var is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var" + with_items: + - "{{ ansible_mounts }}" + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.3.1 - Report to user if /var is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.3.1 - # Find all local filesystem directories and set the sticky bit on world writable ones -# - name: 1.1.21 - Ensure sticky bit is set on world-writeable directories -# ansible.builtin.shell: set -o pipefail ; /usr/bin/df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null | xargs -I '{}' chmod a+t '{}' -# changed_when: false -# tags: -# - 1.1.21 - - # Turn off and disable the autofs service using the service module. - # We check to see if the package that autofs belongs to (convienently called autofs) - # exists in the ansible_facts.packages list we gathered early in the play - - name: 1.1.22 - disable automounting - ansible.builtin.service: - name: autofs - enabled: false - state: stopped - when: "'autofs' in ansible_facts.packages" - tags: - - 1.1.22 - - - name: 1.1.23 - Disable USB storage module - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "install usb-storage /bin/true" - state: present - create: true - owner: root - group: root - mode: 0644 - tags: - - 1.1.23 + - name: 1.1.3.[2-3] - /var mount option controls + tags: + - 1.1.3.2 + - 1.1.3.3 + when: mount_count != 0 + block: + - name: 1.1.3.2 - Report to user if /var does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.3.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.3.3 Report to user if /var does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.3.3 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.4.1 - Configure /var/tmp + tags: + - 1.1.4 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.4 - Set/reset /var/tmp mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.4.1 - Determine if /var/tmp is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/tmp" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.4.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.4.1 - Report to user if /var/tmp not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.4.1 + + - name: 1.1.4.[2-4] - /var/tmp mount option controls + tags: + - 1.1.4.2 + - 1.1.4.3 + - 1.1.4.4 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.2 - Report to user if /var/tmp does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options + changed_when: true + tags: + - 1.1.4.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.3 - Report to user if /var/tmp does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.4.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.4 - Report to user if /var/tmp does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.4.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.5 - Configure /var/log + tags: + - 1.1.5 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.5 - Set/reset /var/log mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.5.1 - Determine if /var/log is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/log" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.5.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.5.1 - Report to user if /var/log is not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.5.1 + + - name: 1.1.5.[2-3] - /var/log mount option controls + tags: + - 1.1.5.2 + - 1.1.5.3 + when: mount_count != 0 + block: + - name: 1.1.5.2 - Report to user if /var/log does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.5.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.5.3 - Report to user if /var/log does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.5.3 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.6 - Configure /var/log/audit + tags: + - 1.1.6 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.6 - Set/reset /var/log/audit mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.6.1 - Determine if /var/log/audit is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/log/audit" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.6.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.6.1 - Report to user if /var/log/audit is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.6.1 + + - name: 1.1.6.[2-4] - /var/log/audit mount option controls + tags: + - 1.1.5.2 + - 1.1.5.3 + - 1.1.5.4 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.2 - Report to user if /var/log/audit does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options + changed_when: true + tags: + - 1.1.6.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.3 - Report to user if /var/log/audit does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.6.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.4 - Report to user if /var/log/audit does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.6.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.7 - Configure /home + tags: + - 1.1.7 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.7 - Set/reset /home mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.7.1 - Determine if /home is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/home" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.7.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.7.1 - Report to user if /home is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.7.1 + + - name: 1.1.7.[2-3] - /home mount option controls + tags: + - 1.1.7.2 + - 1.1.7.3 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.7.2 - Report to user if /home does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.7.2 + + - name: 1.1.7.3 Report to user if /home does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.7.3 + +# /dev/shm does not exist in ansible_mounts so we have to check the +# mount command directly. This requires the use of the shell command which +# is not ideal. +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8 - Configure /dev/shm + tags: + - 1.1.8 + block: + - name: Determine if /dev/shm has nodev set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nodev + register: devshm_nodev_out + failed_when: devshm_nodev_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.8.2 - Report to user if /dev/shm does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nodev set" + when: devshm_nodev_out is defined and devshm_nodev_out.stdout + changed_when: true + tags: + - 1.1.8.2 + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8.4 - Report if /dev/shm does not have nosuid set + tags: + - 1.1.8.4 + block: + - name: Determine if /dev/shm has nosuid set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nosuid + register: devshm_nosuid_out + failed_when: devshm_nosuid_out == "2" + changed_when: false + check_mode: false + +- name: 1.1.8.3 - Report if /dev/shm does not have noexec set + tags: + - 1.1.8.3 + block: + - name: 1.1.8.3 - Determine if /dev/shm has noexec set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v noexec + register: devshm_noexec_out + failed_when: devshm_noexec_out == "2" + changed_when: false + check_mode: false + tags: + - 1.1.8.3 + +# Let the user know if we did not find the option set. + - name: 1.1.8.3 - Report to user if /dev/shm does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have noexec set" + when: devshm_noexec_out is defined and devshm_noexec_out.stdout + changed_when: true + tags: + - 1.1.8.3 +# Let the user know if we did not find the option set. + - name: 1.1.8.2 - Report to user about /dev/shm + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nosuid set" + when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout + changed_when: true + +# Control 1.1.9 is for removable media + +# Turn off and disable the autofs service using the service module. +# We check to see if the package that autofs belongs to (convienently called autofs) +# exists in the ansible_facts.packages list we gathered early in the play +- name: 1.1.22 - disable automounting + ansible.builtin.systemd: + name: autofs + enabled: false + state: stopped + masked: true + when: "'autofs' in ansible_facts.packages" + tags: + - 1.1.22 + +- name: 1.1.23 - Disable USB storage module + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install usb-storage /bin/true" + state: present + create: true + owner: root + group: root + mode: 0644 + tags: + - 1.1.23 # Control 1.2.1 is system updating. Make sure system is set for some kind of system software update - # Use the service module to disable the rhnsd service. If you want the machine - # to respond to queued services from Satellite, do not disable this. - - name: 1.2.2 Disable rhnsd - ansible.builtin.service: - name: rhnsd - enabled: false - state: stopped - ignore_errors: true # Remove for RHEL - when: ansible_distribution == "RedHat" - tags: - - 1.2.2 - - # GPGKeys are used to sign packages. enabling them will mean that all packages - # from a given repo must be signed with the appropriate key - - name: 1.2.[3,4] - Ensure GPG keys are configured - block: - # Replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' - - name: 1.2.4 - set master yum.conf gpgcheck to '1' - ansible.builtin.replace: - dest: /etc/yum.conf - regexp: '^gpgcheck\s*=\s*[^1]*$' - replace: "gpgcheck = 1" - when: gpgcheck and ansible_distribution == "RedHat" - - - name: 1.2.4 - set master dnf.conf gpgcheck to '1' - ansible.builtin.replace: - dest: /etc/dnf/dnf.conf - regexp: '^gpgcheck\s*=\s*[^1]*$' - replace: "gpgcheck=1" - when: gpgcheck and ansible_distribution == "Fedora" - - # Find all files in /etc/yum.repos.d and add them to a list variable - - name: 1.2.4 - find all repo files in /etc/yum.repos.d/ - ansible.builtin.find: - paths: "/etc/yum.repos.d" - patterns: "*.repo" - register: yumrepos - when: gpgcheck is defined and gpgcheck - - # parse the list variable and replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' - - name: 1.2.4 - Set all repos gpgchecks to '1' - ansible.builtin.replace: - dest: "{{ item.path }}" - regexp: '^gpgcheck\s*=\s*[^1]*$' - replace: gpgcheck = 1 - with_items: "{{ yumrepos.files }}" - when: gpgcheck is defined and gpgcheck - tags: - - 1.2.3 - - 1.2.4 - - # Control 1.2.5 is a manual review to ensure repos are configured per site needs - - # use the system package module to ensure sudo is installed - - name: 1.3.1 - Ensure sudo is installed - ansible.builtin.dnf: - name: sudo - state: present - tags: - - 1.3.1 - - # Make sure the sudoers file includes the requirement to use pty - - name: 1.3.2 - Ensure sudo commands use pty - ansible.builtin.lineinfile: - path: /etc/sudoers - regexp: '^Defaults\s*use_pty' - line: "Defaults use_pty" - insertafter: "^# Defaults specification" - validate: /usr/sbin/visudo -cf %s - tags: - - 1.3.2 - - # Make sure the sudoers file includes the requirement to log to a file - - name: 1.3.3 - Ensure sudo log file exists - ansible.builtin.lineinfile: - path: /etc/sudoers - regexp: '^Defaults\s*logfile="{{ sudo_log }}"' - line: 'Defaults logfile="{{ sudo_log }}"' - insertafter: "^# Defaults specification" - validate: /usr/sbin/visudo -cf %s - tags: - - 1.3.3 - - # AIDE is a file system integrity checker which will document all - # filesystem changes. It's very noisy on busy systems and should be - # enabled when you have the sapce and need for it. - - name: 1.4 - Filesystem integrity checking w/AIDE - block: - # use the system package manager to install AIDE - - name: 1.4.1 Ensure aide is installed - ansible.builtin.package: - name: aide - state: present - tags: - - 1.4.1 - - # AIDE requires initialization the first time and it takes time on a large system. - # DUse stat module on the file that should be there if it is set up. - - name: 1.4.1 - Determine if AIDE has already been initialized - ansible.builtin.stat: - path: /var/lib/aide/aide.db.gz - register: aide_path - tags: - - 1.4.1 - - - name: 1.4.1 - Set up database file location - ansible.builtin.replace: - dest: /etc/aide.conf - regexp: "^database=file:((?!{{ aide_db_name }}).)*$" - replace: "database=file:{{ aide_db_name }}" - tags: - - 1.4.1 - - - name: 1.4.1 - Set up database_out file location - ansible.builtin.replace: - dest: /etc/aide.conf - regexp: "^database_out=file:((?!{{ aide_new_db_name }}).)*$" - replace: "database_out=file:{{ aide_new_db_name }}" - tags: - - 1.4.1 - - - name: 1.4.1 - enable gzip compression for database - ansible.builtin.lineinfile: - dest: /etc/aide.conf - regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' - line: "gzip_dbout={{ aide_gzip }}" - state: present - tags: - - 1.4.1 - - # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' - # is true if the file is a regular file. If either of these are not true, then - # run the initializatoin again. - - name: 1.4.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aide) - ansible.builtin.command: /usr/sbin/aide --init - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - register: aide - async: 1200 # 20 minutes until timeout - poll: 0 # run concurrently - tags: - - 1.4.1 - - - name: Wait for AIDE initialization to complete - ansible.builtin.async_status: jid={{ aide.ansible_job_id }} - register: aide_status - until: aide_status.finished - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - retries: 300 - tags: - - 1.4.1 - - # AIDE creates the new database as a different name. Use the copy module with - # the remote_src argument to copy the file on the remote machine to another location - # on the remote machine. - - name: 1.4.1 - Move the newly created database into place - ansible.builtin.copy: - src: /var/lib/aide/aide.db.new.gz - remote_src: true - dest: /var/lib/aide/aide.db.gz - mode: preserve - when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" - changed_when: false - tags: - - 1.4.1 - - # Copy in the already configured systemd service file using the copy module. - # Be sure to set the selinux context. - # Notify systemd to reload its daemons and start the service - - name: 1.4.2 - Ensure File integrity is regularly checked (aidecheck service) - ansible.builtin.template: - src: aidecheck.service - dest: /etc/systemd/system/aidecheck.service - owner: root - group: root - mode: 0644 - setype: systemd_unit_file_t - notify: restart aidecheck - tags: - - 1.4.2 - - - name: Enable aidecheck.service - ansible.builtin.systemd: - name: aidecheck.service - enabled: true - tags: 1.4.2 +# GPGKeys are used to sign packages. enabling them will mean that all packages +# from a given repo must be signed with the appropriate key +- name: 1.2.[3,4] - Ensure GPG keys are configured + tags: + - 1.2.3 + block: + # Replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.2 - set master yum.conf gpgcheck to '1' + ansible.builtin.replace: + dest: /etc/yum.conf + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: "gpgcheck = 1" + when: gpgcheck and ansible_distribution == "RedHat" + + - name: 1.2.2 - set master dnf.conf gpgcheck to '1' + ansible.builtin.replace: + dest: /etc/dnf/dnf.conf + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: "gpgcheck=1" + when: gpgcheck and ansible_distribution == "Fedora" + + # 1.2.4 - Ensure repo_gpgcheck is globally activated doesn't work on all repos so skipped. Environment dependant + + # Find all files in /etc/yum.repos.d and add them to a list variable + - name: 1.2.4 - find all repo files in /etc/yum.repos.d/ + ansible.builtin.find: + paths: "/etc/yum.repos.d" + patterns: "*.repo" + register: yumrepos + when: gpgcheck is defined and gpgcheck + + # parse the list variable and replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.4 - Set all repos gpgchecks to '1' + ansible.builtin.replace: + dest: "{{ item.path }}" + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: gpgcheck = 1 + with_items: "{{ yumrepos.files }}" + when: gpgcheck is defined and gpgcheck + +# This is out of order, but sudo is configured a lot through the other controls +- name: 5.3.1 - Ensure sudo is installed + ansible.builtin.dnf: + name: sudo + state: present + tags: + - 5.3.1 + +# AIDE is a file system integrity checker which will document all +# filesystem changes. It's very noisy on busy systems and should be +# enabled when you have the sapce and need for it. +- name: 1.3 - Filesystem integrity checking w/AIDE + tags: + - 1.3.0 + block: + # use the system package manager to install AIDE + - name: 1.3.1 - Ensure aide is installed + ansible.builtin.package: + name: aide + state: present + tags: + - 1.3.1 + + # AIDE requires initialization the first time and it takes time on a large system. + # DUse stat module on the file that should be there if it is set up. + - name: 1.3.1 - Determine if AIDE has already been initialized + ansible.builtin.stat: + path: /var/lib/aide/aide.db.gz + register: aide_path + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database=file:((?!{{ aide_db_name }}).)*$" + replace: "database=file:{{ aide_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database_out file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database_out=file:((?!{{ aide_new_db_name }}).)*$" + replace: "database_out=file:{{ aide_new_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - enable gzip compression for database + ansible.builtin.lineinfile: + dest: /etc/aide.conf + regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' + line: "gzip_dbout={{ aide_gzip }}" + state: present + tags: + - 1.3.1 + + # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' + # is true if the file is a regular file. If either of these are not true, then + # run the initializatoin again. + - name: 1.3.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aide) + ansible.builtin.command: /usr/sbin/aide --init + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + register: aide + async: 1200 # 20 minutes until timeout + poll: 0 # run concurrently + tags: + - 1.3.1 + + - name: 1.3.1 - Wait for AIDE initialization to complete + ansible.builtin.async_status: + jid: "{{ aide.ansible_job_id }}" + register: aide_status + until: aide_status.finished + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + retries: 300 + tags: + - 1.3.1 + + # AIDE creates the new database as a different name. Use the copy module with + # the remote_src argument to copy the file on the remote machine to another location + # on the remote machine. + - name: 1.3.1 - Move the newly created database into place + ansible.builtin.copy: + src: /var/lib/aide/aide.db.new.gz + remote_src: true + dest: /var/lib/aide/aide.db.gz + mode: preserve + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + changed_when: false + tags: + - 1.3.1 + + # Copy in the already configured systemd service file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the service + - name: 1.3.2 - Ensure File integrity is regularly checked (aidecheck service) + tags: + - 1.3.2 + notify: Restart aidecheck + block: + - name: 1.3.2 - Template in aidecheck.service file + ansible.builtin.template: + src: aidecheck.service + dest: /etc/systemd/system/aidecheck.service + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + + - name: 1.3.2 - Enable aidecheck.service + ansible.builtin.systemd: + name: aidecheck.service + enabled: true # Copy in the already configured systemd timer file using the copy module. # Be sure to set the selinux context. # Notify systemd to reload its daemons and start the timer - - name: 1.4.2 - Ensure File integrity is regulary checked (aidecheck timer) - ansible.builtin.template: - src: aidecheck.timer - dest: /etc/systemd/system/aidecheck.timer - owner: root - group: root - mode: 0644 - setype: systemd_unit_file_t - notify: restart aidecheck - tags: - - 1.4.2 - tags: - - 1.4.0 - - # 1.5 Secure Boot settings - - # Determine if we are using LILO or EFI - - name: 1.5.0 - Check if the EFI directory exists - ansible.builtin.stat: - path: "/boot/efi/EFI/{{ ansible_distribution | lower }}/grub.cfg" - register: efidir - tags: - 1.5.1 - - - name: 1.5.1 - set variable for grub.cfg in EFI location - ansible.builtin.set_fact: - grub_cfg_path: "{{ efidir.stat.path }}" - when: efidir.stat.path is defined - tags: - 1.5.1 - - - name: 1.5.0 - Check if the LILO path exists - ansible.builtin.stat: - path: "/boot/grub2/grub.cfg" - register: grubdir - tags: - 1.5.1 - - - name: 1.5.1 - set variable for grub.cfg in LILO location - ansible.builtin.set_fact: - grub_cfg_path: "{{ grubdir.stat.path }}" - when: grubdir.stat.path is defined - tags: - 1.5.1 - - # Use file module to set permissions on grub files - - name: 1.5.1 - Set permissions on grub.cfg - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0600 - loop: - - "{{ grub_cfg_path }}" - - /boot/grub2/grubenv - tags: - - 1.5.1 - - # Control 1.5.2, Grub bootloader password - skipped - - # Use replace module to add the requirement to enter password on single user startup - # With Support for Fedora 31 added, you can build a machine with a disabled root account. - # setting up secure single user mode shouldn't be done unless the root pasword is set or - # you'll lock yourself out. - - name: 1.5.3 - Set single user password - block: - - name: 1.5.3 - Check if root has a password - ansible.builtin.lineinfile: - path: /etc/shadow - regexp: '^root:[*\!|*\*]*:' - state: absent - check_mode: true - changed_when: false - register: root_pw_check - failed_when: false - - # The user module here uses a known salt to idompotently set the password for multiple runs - # see https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#hash-filters - - name: 1.5.3 - Set root password - ansible.builtin.user: - name: root - password: "{{ 'root_password' | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" - when: root_pw_check.found != "0" and root_password is defined - - - name: 1.5.3 - Set single user to use use a secure shell - ansible.builtin.replace: - dest: /usr/lib/systemd/system/{{ item }} - regexp: '^ExecStart=-((?!/usr/lib/systemd/systemd-sulogin-shell).)*$' - replace: "ExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue" - when: root_pw_check.found != "0" - with_items: - - rescue.service - - emergency.service - - - name: 1.5.3 - If no root password is set up, notify the user and do not set password or single user mode - ansible.builtin.debug: - msg: "Root password is not set and no password provided. Set root_password variable per instructions and restart playbook." - changed_when: true - when: root_pw_check.found and root_password is not defined - tags: - - 1.5.3 - - - # 1.6 Additional Process Hardening - - - name: 1.6.1 - Ensure core dumps are restricted - block: - # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl - # to reload them immediately if 'reload' is set to 'yes'. - - name: 1.6.1 - Ensure core dumps are restricted - ansible.builtin.sysctl: - name: fs.suid_dumpable - value: "0" - state: present - reload: true - - # The pam_limits module will configure the lines in the limits files. - - name: 1.6.1 - Ensure core limits are set - community.general.pam_limits: - dest: /etc/security/limits.d/CIS.conf - domain: "*" - limit_type: hard - limit_item: core - value: "0" - tags: - - 1.6.1 - - - name: 1.6.2 - Ensure address space layout reandomization (ASLR) is enabled - # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl - # to reload them immediately if 'reload' is set to 'yes'. - ansible.builtin.sysctl: - name: kernel.randomize_va_space - value: "2" - reload: true - state: present - sysctl_set: true - tags: - - 1.6.2 - - # 1.7 Mandatory Access Control - - # Use system package manager to remove - - name: 1.7.1.1 - Ensure SELinux is installed - ansible.builtin.dnf: - name: - - libselinux - - python3-libselinux - state: present - when: selinux is defined and selinux != "Disabled" - tags: - - 1.7.1.1 - - # re-gather system facts in case we installed selinux packages. - # If selinux wasn't installed, it will not populate ansible_selinux fact correctly, regathering - # will pull it with the right information - - name: Regather facts - ansible.builtin.setup: - tags: - - 1.7.1.1 - - # Use the replace module to remove any disablment of selinux in grub if - # it isn't expressly disabled from a variable - - name: 1.7.1.2 - Ensure SELinux is not disabled in bootloader configuration - ansible.builtin.replace: - dest: /etc/default/grub - regexp: "{{ item }}" - replace: "" - with_items: - - selinux=0 - - enforcing=0 - when: selinux is defined and selinux != "Disabled" - notify: rebuild grub - tags: - - 1.7.1.2 - - # Replace the current selinux policy with whatever the variable is set for - - name: 1.7.1.3 - Set SELinux policy to {{ selinux_policy }} - ansible.builtin.replace: - dest: /etc/selinux/config - regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" - replace: "SELINUXTYPE={{ selinux_policy }}" - when: ( selinux is defined and selinux_policy is defined ) and selinux != "Disabled" - tags: - - 1.7.1.3 - - # If we are going to be enabling selinux in passive or enforcing mode, - # set the autorelabel and notify the machine to reboot - - name: 1.7.1.3 - If disabled and we are enabling it, autorelabel - ansible.builtin.file: - path: /.autorelabel - owner: root - group: root - mode: 0644 - state: touch - when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" - notify: reboot - tags: - - 1.7.1.3 - - # Replace the current selinux mode with what the variable is set to - - name: 1.7.1.4 - Set SELinux to {{ selinux | lower }} - ansible.builtin.replace: - dest: /etc/selinux/config - regexp: "^SELINUX=((?!{{ selinux }}).)*$" - replace: "SELINUX={{ selinux | lower }}" - when: selinux is defined and ( selinux | lower == "enforcing" or selinux | lower == "permissive" or selinux | lower == "disabled" ) - notify: reboot - tags: - - 1.7.1.4 - - # Let the user know if there are any processes that are not running under the - # a selinux context - - name: 1.7.1.5 - Report on unconfined running services - block: - # In RHEL8, all unconfined services run under their own context - - name: 1.7.1.5 - Generate report on unconfined running services - ansible.builtin.shell: /usr/bin/ps -eZ | /usr/bin/grep unconfined_service_t - register: unconfined_services_out - when: ansible_selinux.status != "disabled" - failed_when: unconfined_services_out.rc == "2" - changed_when: false - check_mode: false - - # Print any findings to the user - - name: 1.7.1.5 - Report on unconfined running services to user - ansible.builtin.debug: - msg: - - "Unconfined processes found:" - - "{{ unconfined_services_out.stdout_lines }}" - changed_when: true - when: unconfined_services_out.stdout - tags: - - 1.7.1.5 - - # Use system package manager to remove package - - name: 1.7.1.6 - Remove setroubleshoot - ansible.builtin.dnf: - name: setroubleshoot - state: absent - tags: - - 1.7.1.6 - - - name: 1.7.1.7 - Remove MCS Translation Service - ansible.builtin.dnf: - name: mcstrans - state: absent - tags: - - 1.7.1.7 - - # 1.8 Warning Banners - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.8.1.1 - Install motd banners - ansible.builtin.copy: - src: "{{ motd_file }}" - dest: /etc/motd - owner: root - group: root - mode: 0644 - tags: - - 1.8.1.1 - - 1.8.1.4 - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.8.1.2 - Install issue banners - ansible.builtin.copy: - src: "{{ issue_file }}" - dest: /etc/issue - owner: root - group: root - mode: 0644 - tags: - - 1.8.1.2 - - 1.8.1.5 - - # Use copy module to copy in the appropriate files based on variable and set permissions - - name: 1.8.1.3 - Install issue.net banners - ansible.builtin.copy: - src: "{{ issue_file }}" - dest: /etc/issue.net - owner: root - group: root - mode: 0644 - tags: - - 1.8.1.3 - - 1.8.1.6 - - # add a banner to the login screen if the graphical_interface variable is set to true - - name: 1.8.2 Ensure GDM banner set up - ansible.builtin.blockinfile: - # Add our required pieces to the greeter defaults file - path: /etc/gdm/greeter.dconf-defaults - owner: root - group: root - mode: 0644 - block: | - [org/gnome/login-screen] - banner-message-enable=true - banner-message-text='Authorized uses only. All activity may be monitored and reported.' - when: graphical_inteface is defined and graphical_interface - tags: - - 1.8.2 - - # 1.10 Configure crypto policy - - name: 1.10.0 - Configure crypto-policy - block: - - name: 1.10.0 - Display error if crypto variable violates policy - ansible.builtin.debug: - msg: - - "crypto_policy is set to: {{ crypto_policy }}. Which is not a valid selection." - - "Valid choices are DEFAULT, FUTURE, and FIPS." - - "LEGACY selection does not satisfy the control requirement" - - "Refusing to update crypto_policy information" - when: crypto_policy is defined and ( crypto_policy != "DEFAULT" and crypto_policy != "FUTURE" and crypto_policy != "FIPS" ) - - - name: 1.10.0 - Set crypto-policy to {{ crypto_policy | upper | default('DEFAULT', true) }} - ansible.builtin.lineinfile: - path: /etc/crypto-policies/config - regexp: "^(LEGACY|FUTURE|FIPS|DEFAULT)" - line: "{{ crypto_policy | upper | default('DEFAULT', true) }}" - notify: update crypto_policy - - - name: 1.10.0 - Check to see if FIPS mode is already set up if crypto_policy == "FIPS" - ansible.builtin.command: /usr/sbin/fips-mode-setup --is-enabled - register: fips_mode - when: crypto_policy is defined and crypto_policy == "FIPS" - failed_when: false - changed_when: false - - - name: 1.10.0 - Enabling FIPS mode if crypt_policy set to FIPS - ansible.builtin.command: /usr/bin/fips-mode-setup --enable - when: ( crypto_policy is defined and crypto_policy == "FIPS") and fips_mode.rc == "2" - tags: - - 1.10.0 - - ### Part 2, Services ### - # Remove old, unused, insecure services - - name: 2.1.1 - Remove xinetd service - ansible.builtin.dnf: - name: xinetd - state: absent - when: tftp_server is defined and not tftp_server - - tags: - - 2.1.1 - - # RHEL 8 does not distribute ntp any longer, so we are not using the time_server - # variable for RHEL8 controls - - name: 2.2.1.1 - Verify chrony is installed - ansible.builtin.dnf: - name: "chrony" - state: present - tags: - - 2.2.1.1 - - # Use the template module to deploy the config file for the time sync program - # The default file does not have any template variables, but it's there so - # they can be added in the future. - - name: 2.2.1.2 - Configure chrony - ansible.builtin.template: - src: "chrony.conf" - dest: /etc/chrony.conf - owner: root - group: root - mode: 0644 - notify: restart chronyd - tags: - - 2.2.1.2 - - - name: 2.2.1.3 - configure sysconfig time_server options - ansible.builtin.template: - src: "{{ time_service }}d" - dest: /etc/sysconfig/{{ time_service }}d - owner: root - group: root - mode: 0644 - notify: restart {{ time_service }}d - tags: - - 2.2.1.3 - - - name: 2.2.2 - disable display manager if graphical desktop not needed - block: - # Find the current default run level. The systemctl module does not handle the - # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target - - name: 2.2.2 - get default runlevel - ansible.builtin.stat: - path: /etc/systemd/system/default.target - register: default_runlevel_out - tags: - - 2.2.2 - - # Use systemd module to stop the GDM service - - name: 2.2.2 - Disable the gdm display manager - ansible.builtin.systemd: - name: gdm - enabled: false - masked: true - state: stopped - daemon-reload: true - when: "'gdm' in ansible_facts.packages and not graphical_interface" - tags: - - 2.2.2 - - # Set the current run level. The systemctl module does not handle the - # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target - - name: 2.2.2 - Set current runlevel (non graphical) - ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target - register: isolate_out - changed_when: isolate_out.changed - when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface - tags: - - 2.2.2 - - - name: 2.2.2 - Set current runlevel (graphical) - ansible.builtin.command: /usr/bin/systemctl isolate graphical.target - register: isolate_out - changed_when: isolate_out.changed - when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface - tags: - - 2.2.2 - - # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default - - name: 2.2.2 - Set default runlevel (non graphical) - ansible.builtin.file: - src: /lib/systemd/system/multi-user.target - dest: /etc/systemd/system/default.target - owner: root - group: root - when: not graphical_interface - - - name: 2.2.2 - Set default runlevel (graphical) - ansible.builtin.file: - src: /lib/systemd/system/graphical.target - dest: /etc/systemd/system/default.target - owner: root - group: root - when: graphical_interface - tags: - - 2.2.2 - - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: create empty list for unneeded packages - ansible.builtin.set_fact: - unneeded_packages: [] - - - name: 2.2.3 - Remove rsync; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'rsync' ] }}" - tags: - - 2.2.3 - - - name: 2.2.4 - Remove avahi; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'avahi' ] }}" - tags: - - 2.2.4 - - - name: 2.2.5 - Remove snmp; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'net-snmp' ] + [ 'net-snmp-libs'] }}" - tags: - - 2.2.5 - - - name: 2.2.6 - Remove squid; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" - tags: - - 2.2.6 - - - name: 2.2.7 - Remove samba; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" - when: smb_server is defined and not smb_server - tags: - - 2.2.7 - - - name: 2.2.8 - Remove dovecot; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'dovecot' ] }}" - when: email_server is defined and not email_server - tags: - - 2.2.8 - - - name: 2.2.9 - Remove httpd; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'httpd' ] + [ 'httpd-tools' ] + [ 'mod_ssl' ] }}" - when: http_server is defined and not http_server - tags: - - 2.2.9 - - - name: 2.2.10 - Remove vsftpd; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'vsftpd' ] }}" - when: ftp_server is defined and not ftp_server - tags: - - 2.2.10 - - - name: 2.2.11 - Remove bind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'bind' ] + [ 'unbound' ] }}" - when: dns_server is defined and not dns_server - tags: - - 2.2.11 - - - name: 2.2.12 - Remove nfs server; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'nfs-utils' ] }}" - when: nfs_server is defined and not nfs_server - tags: - - 2.2.12 - - - name: 2.2.13 - Remove rpcbind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'rpcbind' ] }}" - tags: - - 2.2.13 - - # control 2.2.14 skipped. RHEL uses LDAP implemented in SSSD by default - - - name: 2.2.15 - Disable dhcpd server [controlled by host variable dhcp_server]; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'dhcp' ] }}" - when: dhcp_server is defined and not dhcp_server - tags: - - 2.2.15 - - - name: 2.2.17 - Remove ypserv; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypserv' ] }}" - tags: - - 2.2.17 - - - name: 2.3.1 - Remove ypbind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypbind' ] }}" - when: not ypbind - tags: - - 2.3.1 - - - name: 2.3.2 - Remove telnet; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'telnet' ] }}" - tags: - - 2.3.2 - - - name: 2.3.3 - Remove openldap-clients; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'openldap-clients' ] }}" - tags: - - 2.3.3 - - - name: 2.3.3 - list of packages to remove - ansible.builtin.debug: - var: unneeded_packages - - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: Process removal list - ansible.builtin.dnf: - name: "{{ unneeded_packages }}" - state: absent - - # Cups should be remove per control 2.2.16, but it may not be able to due to - # dependencies, so disable the service instead - - name: 2.2.16 - Disable cups as we my not be able to uninstall it - ansible.builtin.service: - name: "{{ item }}" - enabled: false - state: stopped - when: "'cups' in ansible_facts.packages" - loop: - - cups.service - - cups.socket - - cups-browsed.service - tags: - - 2.2.16 - - # Use the stat module to determine if the mail server config file exists. - # If it does and we are to be a mail server, then modify it per the control. - - name: 2.2.18 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) - block: - - name: 2.2.18 - Find if we have a mail agent config file - ansible.builtin.stat: - path: /etc/postfix/main.cf - register: postfix_out - changed_when: false - - - name: 2.2.18 - If the file exists and not a mail server, then set loopback only - ansible.builtin.replace: - dest: /etc/postfix/main.cf - regexp: "^inet_interfaces = ((?!localhost).)*$" - replace: "inet_interfaces = loopback-only" - when: postfix_out.stat.exists and not email_server - notify: restart postfix - tags: - - 2.2.18 - - # Section 3, Network parameters - - # The sysctl module will configure certain sysctl parameters. They are - # collected into a loop here to speed the implementation - # Once complete, notify the system to flush the network routes - - name: 3.1 - Set networking parameters for host only communications - block: - - name: 3.1 - Set ipv4 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.conf.all.forwarding # (3.1.1) - - net.ipv4.conf.all.send_redirects # (3.1.2) - - net.ipv4.conf.default.send_redirects # (3.1.2) - notify: flush network routes - - - name: 3.1 - Set ipv6 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv6.conf.all.forwarding # (3.1.1) - when: not ipv6_disable - notify: flush network routes - tags: - - 3.1.0 - - - name: 3.2 - Set networking parameters for host as router communications - block: - - name: 3.2 - Set ipv4 network parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.conf.all.accept_source_route # (3.2.1) - - net.ipv4.conf.default.accept_source_route # (3.2.1) - - net.ipv4.conf.all.accept_redirects # (3.2.2) - - net.ipv4.conf.default.accept_redirects # (3.2.2) - - net.ipv4.conf.all.secure_redirects # (3.2.3) - - net.ipv4.conf.default.secure_redirects # (3.2.3) - notify: flush network routes - - - name: 3.2.[4-8] - Set ipv4 networking parameters (ON) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "1" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv4.conf.all.log_martians # (3.2.4) - - net.ipv4.conf.default.log_martians # (3.2.4) - - net.ipv4.icmp_echo_ignore_broadcasts # (3.2.5) - - net.ipv4.icmp_ignore_bogus_error_responses # (3.2.6) - - net.ipv4.conf.all.rp_filter # (3.2.7) - - net.ipv4.conf.default.rp_filter # (3.2.7) - - net.ipv4.tcp_syncookies # ( 3.2.8) - notify: flush network routes - - - name: 3.2 - Set ipv6 networking parameters (OFF) - ansible.builtin.sysctl: - name: "{{ item }}" - value: "0" - reload: true - state: present - sysctl_set: true - loop: - - net.ipv6.conf.all.accept_source_route # (3.2.1) - - net.ipv6.conf.default.accept_source_route # (3.2.1) - - net.ipv6.conf.all.accept_redirects # (3.2.2) - - net.ipv6.conf.default.accept_redirects # (3.2.2) - - net.ipv6.conf.all.accept_ra # (3.2.9) - - net.ipv6.conf.default.accept_ra # (3.2.9) - notify: flush network routes - when: not ipv6_disable - tags: - - 3.2.0 - - - name: 3.3 - Disable uncommon network protocols - block: - # This collection of tasks creates a empty list and save it as a fact. - # For every item that is encountered (without the tag being skipped), - # add a string to the list. - - name: 3.3.0 - Create empty list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: [] - - - name: 3.3.1 - Add dccp to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'dccp' ] }}" - tags: - - 3.3.1 - - - name: 3.3.2 - Add sctp to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'sctp' ] }}" - tags: - - 3.3.2 - - - name: 3.3.3 - Add rds to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'rds' ] }}" - tags: - - 3.3.3 - - - name: 3.3.4 - Add tipc to list of uncommon network protocols to disable - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'tipc' ] }}" - tags: - - 3.3.4 - - # With the list complete, use it with the system's package manager - # to remove packages from the system that are not needed. - - name: 3.5.0 - Process uncommon network list - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" - state: present - create: true - owner: root - group: root - mode: 0644 - with_items: - - "{{ uncommon_network }}" - tags: - - 3.3.0 - - # Section 3 - Firewall - - - name: 3.4.1 - Install firewall package - block: + - name: 1.3.2 - Ensure File integrity is regulary checked (aidecheck timer) + ansible.builtin.template: + src: aidecheck.timer + dest: /etc/systemd/system/aidecheck.timer + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + notify: Restart aidecheck + + - name: 1.3.2 - Enable aidecheck.timer + ansible.builtin.systemd: + name: aidecheck.timer + enabled: true + + +# 1.5 Secure Boot settings + +# Determine if we are using LILO or EFI +- name: 1.5.0 - Check if the EFI directory exists + ansible.builtin.stat: + path: "/boot/efi/EFI/{{ ansible_distribution | lower }}/grub.cfg" + register: efidir + tags: + 1.4.1 + +- name: 1.4.1 - set variable for grub.cfg in EFI location + ansible.builtin.set_fact: + grub_cfg_path: "{{ efidir.stat.path }}" + when: efidir.stat.path is defined + tags: + 1.4.1 + +# 1.4 Secure Boot settings +- name: 1.4.0 - Check if the LILO path exists + ansible.builtin.stat: + path: "/boot/grub2/grub.cfg" + register: grubdir + tags: + 1.4.0 + +- name: 1.4.0 - set variable for grub.cfg in LILO location + ansible.builtin.set_fact: + grub_cfg_path: "{{ grubdir.stat.path }}" + when: grubdir.stat.path is defined + tags: + 1.4.0 + +# Control 1.4.1, Grub bootloader password - skipped + +# Use file module to set permissions on grub files +- name: 1.4.2 - Set permissions on grub.cfg, grubenv + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - "{{ grub_cfg_path }}" + - /boot/grub2/grubenv + tags: + - 1.4.2 + +- name: 1.4.3 - Require authorization to enter Rescue mode + tags: + - 1.4.3 + block: + - name: 1.4.3 - Require authorization to enter Rescue mode - create dir + ansible.builtin.file: + dest: "/etc/systemd/system/rescue.service.d/" + state: directory + owner: root + group: root + mode: 0755 + + - name: 1.4.3 - Require authorization to enter Rescue mode - set file + ansible.builtin.copy: + dest: "/etc/systemd/system/rescue.service.d/00-require-auth.conf" + owner: root + group: root + mode: 0644 + setype: etc_t + content: | + [Service] + ExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue + +# 1.5 Additional Process Hardening + +- name: 1.5.1 - Ensure core dump storage is disabled + ansible.builtin.blockinfile: + path: /etc/systemd/coredump.conf + create: true + owner: root + group: root + mode: 0644 + block: | + Storage=none + state: present + marker: "# {mark} Ansible managed Storage setting" + tags: + - 1.5.1 + +- name: 1.5.2 - Ensure core dump backtraces are disabled + ansible.builtin.blockinfile: + path: /etc/systemd/coredump.conf + create: true + owner: root + group: root + mode: 0644 + block: | + ProcessSizeMax=0 + state: present + marker: "# {mark} Ansible managed ProcessSize setting" + tags: + - 1.5.2 + +- name: 1.5.3 - Ensure address space layout reandomization (ASLR) is enabled + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'true'. + ansible.posix.sysctl: + name: kernel.randomize_va_space + value: "2" + reload: true + state: present + sysctl_set: true + tags: + - 1.5.3 + +# Use system package manager to remove +- name: 1.6.1.1 - Ensure SELinux is installed + ansible.builtin.dnf: + name: + - libselinux + - python3-libselinux + state: present + register: selinux_installed + when: selinux is defined and selinux | lower != "disabled" + tags: + - 1.6.1.1 + +# re-gather system facts in case we installed selinux packages. +# If selinux wasn't installed, it will not populate ansible_selinux fact correctly, regathering +# will pull it with the right information +- name: 1.6.1.1 - Regather facts if installed selinux package + ansible.builtin.setup: + when: selinux_installed.changed + tags: + - 1.6.1.1 + +# Use the replace module to remove any disablment of selinux in grub if +# it isn't expressly disabled from a variable +- name: 1.6.1.2 - Ensure SELinux is not disabled in bootloader configuration + ansible.builtin.replace: + dest: /etc/default/grub + regexp: "{{ item }}" + replace: "" + with_items: + - selinux=0 + - enforcing=0 + when: selinux is defined and selinux | lower != "disabled" + notify: Rebuild grub + tags: + - 1.6.1.2 + +# Replace the current selinux policy with whatever the variable is set for +- name: 1.6.1.3 - Set SELinux policy to {{ selinux_policy }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" + replace: "SELINUXTYPE={{ selinux_policy }}" + when: ( selinux is defined and selinux_policy is defined ) and selinux | lower != "disabled" + tags: + - 1.6.1.3 + +# If we are going to be enabling selinux in passive or enforcing mode, +# set the autorelabel and notify the machine to reboot +- name: 1.6.1.3 - If disabled and we are enabling it, autorelabel + ansible.builtin.file: + path: /.autorelabel + owner: root + group: root + mode: 0644 + state: touch + when: ( ansible_selinux.status == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed + notify: Reboot + tags: + - 1.6.1.3 + +# Replace the current selinux mode with what the variable is set to +- name: 1.6.1.[4-5] - Set SELinux to {{ selinux | lower }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUX=((?!{{ selinux }}).)*$" + replace: "SELINUX={{ selinux | lower }}" + when: selinux is defined and ( selinux | lower == "enforcing" or selinux | lower == "permissive" or selinux | lower == "disabled" ) + notify: Reboot + tags: + - 1.6.1.4 + - 1.6.1.5 + +# Let the user know if there are any processes that are not running under the +# a selinux context +- name: 1.6.1.6 - Report on unconfined running services + tags: + - 1.6.1.6 + when: ansible_selinux.status != "disabled" + block: + # In RHEL8, all unconfined services run under their own context + - name: 1.6.1.6 - Generate report on unconfined running services + ansible.builtin.shell: /usr/bin/ps -eZ | /usr/bin/grep unconfined_service_t + register: unconfined_services_out + failed_when: unconfined_services_out.rc == "2" + changed_when: false + check_mode: false + + # Print any findings to the user + - name: 1.6.1.6 - Report on unconfined running services to user + ansible.builtin.debug: + msg: + - "Unconfined processes found:" + - "{{ unconfined_services_out.stdout_lines }}" + changed_when: true + when: unconfined_services_out.stdout + +# Use system package manager to remove package +- name: 1.6.1.7 - Remove setroubleshoot + ansible.builtin.dnf: + name: setroubleshoot + state: absent + tags: + - 1.6.1.7 + +- name: 1.6.1.8 - Remove MCS Translation Service + ansible.builtin.dnf: + name: mcstrans + state: absent + tags: + - 1.6.1.8 + +# 1.7 Warning Banners + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1 - Install motd banners + ansible.builtin.copy: + src: "{{ motd_file }}" + dest: /etc/motd + owner: root + group: root + mode: 0644 + when: motd_use is defined and motd_use + tags: + - 1.7.1 + - 1.7.4 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.2 - Install issue banners + ansible.builtin.copy: + src: "{{ issue_file }}" + dest: /etc/issue + owner: root + group: root + mode: 0644 + when: issue_use is defined and issue_use + tags: + - 1.7.2 + - 1.7.5 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.3 - Install issue.net banners + ansible.builtin.copy: + src: "{{ issue_net_file }}" + dest: /etc/issue.net + owner: root + group: root + mode: 0644 + when: issue_net_use is defined and issue_net_use + tags: + - 1.7.3 + - 1.7.6 + +# 1.8 GDM + +# Disable GDM +- name: 1.8.1 - disable display manager if graphical desktop not needed + tags: + - 1.8.1 + block: + # Find the current default run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 1.8.1 - get default runlevel + ansible.builtin.stat: + path: /etc/systemd/system/default.target + register: default_runlevel_out + tags: + - 1.8.1 + + # Set the current run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 1.8.1 - Set current runlevel (non graphical) + ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface + tags: + - 1.8.1 + + - name: 1.8.1 - Set current runlevel (graphical) + ansible.builtin.command: /usr/bin/systemctl isolate graphical.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface + tags: + - 1.8.1 + + # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default + - name: 1.8.1 - Set default runlevel (non graphical) + ansible.builtin.file: + src: /lib/systemd/system/multi-user.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: not graphical_interface + + - name: 1.8.1 - Set default runlevel (graphical) + ansible.builtin.file: + src: /lib/systemd/system/graphical.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: graphical_interface + + - name: 1.8.1 - Remove the GNOME display manager + ansible.builtin.dnf: + name: gdm + state: absent + when: "'gdm' in ansible_facts.packages and not graphical_interface" + +# add a banner to the login screen if the graphical_interface variable is set to true +- name: 1.8.[2-3] Ensure GDM banner set up + when: graphical_inteface is defined and graphical_interface + tags: + - 1.8.2 + - 1.8.3 + block: + - name: 1.8.[2-3] - Set up dconf profile for gdm + ansible.builtin.blockinfile: + path: /etc/dconf/profile/gdm + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + block: | + user-db:user + system-db:gdm + file-db:/usr/share/gdm/greeter-dconf-defaults + tags: + - 1.8.2 + - 1.8.3 + + - name: 1.8.[2-3] - Create the defaults file and populate group + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + block: | + [org/gnome/login-screen] + tags: + - 1.8.2 + - 1.8.3 + + - name: 1.8.2 - Enable login screen for gdm + ansible.builtin.blockinfile: + # Add our required pieces to the greeter defaults file + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + block: | + banner-message-enable=true + banner-message-text='Authorized users only. All activity may be monitored and reported.' + tags: + - 1.8.2 + + - name: 1.8.3 Ensure GDM disable-user list is enabled + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + block: | + disable-user-list=true + tags: + - 1.8.3 + +# Control 1.8.4 - XDMCP is not enabled, skipped + +# 1.8.5 - Diable media automount +- name: 1.8.5 - disable removable media automount in GDM + ansible.builtin.copy: + dest: "/etc/dconf/db/distro.d/00-media-automount" + owner: root + group: root + mode: 0644 + content: | + [org/gnome/desktop/media-handling] + automount=false + automount-open=false + tags: + - 1.8.5 + +# 1.10 Configure crypto policy +- name: 1.10.0 - Configure crypto-policy + tags: + - 1.10.0 + block: + - name: 1.10.0 - Display error if crypto variable violates policy + ansible.builtin.debug: + msg: + - "crypto_policy is set to: {{ crypto_policy }}. Which is not a valid selection." + - "Valid choices are DEFAULT, FUTURE, and FIPS." + - "LEGACY selection does not satisfy the control requirement" + - "Refusing to update crypto_policy information" + when: crypto_policy is defined and ( crypto_policy != "DEFAULT" and crypto_policy != "FUTURE" and crypto_policy != "FIPS" ) + + - name: 1.10.0 - Set crypto-policy to {{ crypto_policy | upper | default('DEFAULT', true) }} + ansible.builtin.lineinfile: + path: /etc/crypto-policies/config + regexp: "^(LEGACY|FUTURE|FIPS|DEFAULT)" + line: "{{ crypto_policy | upper | default('DEFAULT', true) }}" + notify: Update crypto_policy + + - name: 1.10.0 - Check to see if FIPS mode is already set up if crypto_policy == "FIPS" + ansible.builtin.command: /usr/sbin/fips-mode-setup --is-enabled + register: fips_mode + when: crypto_policy is defined and crypto_policy == "FIPS" + failed_when: false + changed_when: false + + - name: 1.10.0 - Enabling FIPS mode if crypt_policy set to FIPS + ansible.builtin.command: /usr/bin/fips-mode-setup --enable + when: ( crypto_policy is defined and crypto_policy == "FIPS") and fips_mode.rc == "2" + +# 2 Services + +# RHEL 8 does not distribute ntp any longer, so we are not using the time_server +# variable for RHEL8 controls +- name: 2.1.1 - Verify chrony is installed + ansible.builtin.dnf: + name: "chrony" + state: present + tags: + - 2.1.1 + +# Use the template module to deploy the config file for the time sync program +# The default file does not have any template variables, but it's there so +# they can be added in the future. +# Control also sets the user to chrony, but it is already default in RHEL9 +- name: 2.1.2 - Configure chrony + ansible.builtin.template: + src: "chrony.conf" + dest: /etc/chrony.conf + owner: root + group: root + mode: 0644 + notify: Restart chronyd + tags: + - 2.1.2 + +- name: 2.2.1.3 - configure sysconfig time_server options + ansible.builtin.template: + src: "{{ time_service }}d" + dest: /etc/sysconfig/{{ time_service }}d + owner: root + group: root + mode: 0644 + notify: Restart {{ time_service }}d + tags: + - 2.2.1.3 + +- name: 2.2.2 - disable display manager if graphical desktop not needed + tags: + - 2.2.2 + block: + # Find the current default run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 2.2.2 - get default runlevel + ansible.builtin.stat: + path: /etc/systemd/system/default.target + register: default_runlevel_out + tags: + - 2.2.2 + + # Set the current run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 2.2.2 - Set current runlevel (non graphical) + ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface + tags: + - 2.2.2 + + - name: 2.2.2 - Set current runlevel (graphical) + ansible.builtin.command: /usr/bin/systemctl isolate graphical.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface + tags: + - 2.2.2 + + # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default + - name: 2.2.2 - Set default runlevel (non graphical) + ansible.builtin.file: + src: /lib/systemd/system/multi-user.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: not graphical_interface + + - name: 2.2.2 - Set default runlevel (graphical) + ansible.builtin.file: + src: /lib/systemd/system/graphical.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: graphical_interface + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: 2.2.1 - Create empty list for unneeded packages + ansible.builtin.set_fact: + unneeded_packages: [] + +- name: 2.2.1 - Remove xinetd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['xinetd'] }}" + when: tftp_server is defined and not tftp_server + + tags: + - 2.2.1 + +- name: 2.2.2 - Remove xorg-x11-server-common; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['xorg-x11-server-common'] }}" + tags: + - 2.2.3 + +- name: 2.2.3 - Remove avahi; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['avahi'] }}" + tags: + - 2.2.3 + +- name: 2.2.5 - Disable dhcpd server [controlled by host variable dhcp_server]; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['dhcp'] }}" + when: dhcp_server is defined and not dhcp_server + tags: + - 2.2.5 + +- name: 2.2.6 - Remove bind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['bind'] + ['unbound'] }}" + when: dns_server is defined and not dns_server + tags: + - 2.2.6 + +- name: 2.2.7 - Remove ftp server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['ftp'] }}" + when: ftp_server is defined and not ftp_server + tags: + - 2.2.7 + +- name: 2.2.8 - Remove vsftpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['vsftpd'] }}" + when: ftp_server is defined and not ftp_server + tags: + - 2.2.8 + +- name: 2.2.9 - Remove tftp-server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['tftp-server'] }}" + when: tftp_server is defined and not tftp_server + tags: + - 2.2.9 + +- name: 2.2.10 - Remove httpd and nginx; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['httpd'] + ['httpd-tools'] + ['mod_ssl'] + ['nginx'] }}" + when: http_server is defined and not http_server + tags: + - 2.2.10 + +- name: 2.2.11 - Remove dovecot; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['dovecot'] + ['cyrus-imapd'] }}" + when: email_server is defined and not email_server + tags: + - 2.2.11 + +- name: 2.2.12 - Remove samba; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['samba'] }}" + when: smb_server is defined and not smb_server + tags: + - 2.2.12 + +- name: 2.2.13 - Remove squid; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['squid'] }}" + tags: + - 2.2.13 + +- name: 2.2.14 - Remove snmp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['net-snmp'] + ['net-snmp-libs'] }}" + tags: + - 2.2.14 + +- name: 2.2.15 - Remove ypserv; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['ypserv'] }}" + tags: + - 2.2.15 + +- name: 2.2.16 - Remove telnet-server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['telnet-server'] }}" + tags: + - 2.2.16 + +- name: 2.2.18 - Remove nfs utils; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['nfs-utils'] }}" + when: nfs_server is defined and not nfs_server + tags: + - 2.2.18 + +- name: 2.2.19 - Remove rpcbind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['rpcbind'] }}" + tags: + - 2.2.19 + +- name: 2.2.20 - Remove rsync; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['rsync'] }}" + tags: + - 2.2.20 + +- name: 2.3.1 - Remove ypbind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['ypbind'] }}" + when: not ypbind + tags: + - 2.3.1 + +- name: 2.3.2 - Remove rsh; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['rsh'] }}" + when: not ypbind + tags: + - 2.3.2 + +- name: 2.3.3 - Remove talk; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['talk'] }}" + when: not ypbind + tags: + - 2.3.3 + +- name: 2.3.4 - Remove telnet; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['telnet'] }}" + tags: + - 2.3.4 + +- name: 2.3.5 - Remove openldap-clients; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['openldap-clients'] }}" + tags: + - 2.3.5 + +- name: 2.3.6 - Remove tftp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + ['tftp'] }}" + tags: + - 2.3.6 + +- name: 2.3 - list of packages to remove + ansible.builtin.debug: + var: unneeded_packages + tags: + - 2.3.0 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: 2.3 - Process removal list + ansible.builtin.dnf: + name: "{{ unneeded_packages }}" + state: absent + tags: + - 2.2.0 + - 2.3.0 + +# Cups should be remove per control 2.2.16, but it may not be able to due to +# dependencies, so disable the service instead +- name: 2.2.4 - Disable cups as we my not be able to uninstall it + ansible.builtin.service: + name: "{{ item }}" + enabled: false + state: stopped + when: "'cups' in ansible_facts.packages" + loop: + - cups.service + - cups.socket + - cups-browsed.service + tags: + - 2.2.4 + +# Use the stat module to determine if the mail server config file exists. +# If it does and we are to be a mail server, then modify it per the control. +- name: 2.2.17 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) + tags: + - 2.2.17 + block: + - name: 2.2.17 - Find if we have a mail agent config file + ansible.builtin.stat: + path: /etc/postfix/main.cf + register: postfix_out + changed_when: false + + - name: 2.2.17 - If the file exists and not a mail server, then set loopback only + ansible.builtin.replace: + dest: /etc/postfix/main.cf + regexp: "^inet_interfaces = ((?!localhost).)*$" + replace: "inet_interfaces = loopback-only" + when: postfix_out.stat.exists and not email_server + notify: Restart postfix + +# Control 2.4 is a manual control, skipping + +# Section 3, Network parameters +# +# Control 3.1.1 Report on IPv6 status skipped +# Control 3.1.2 Ensure wireless interfaces are disabled is interface dependent +# skipping + +# IPv4 network parameters +- name: 3.2.0 - Create empty dictionary for unneeded IPv4 network parameters + ansible.builtin.set_fact: + unneeded_ipv4_network: {} + +- name: 3.2.0 - Create empty dictionary for unneeded IPv6 network parameters + ansible.builtin.set_fact: + unneeded_ipv6_network: {} + +- name: 3.2.1 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({'net.ipv4.ip_forward': '0'}) }}" + tags: + - 3.2.1 + +- name: 3.2.1 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({'net.ipv6.conf.all.forwarding': '0'}) }}" + when: not ipv6_disable + tags: + - 3.2.1 + +- name: 3.2.2 - Ensure packet redirect sending is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" + loop: + - net.ipv4.conf.all.send_redirects + - net.ipv4.conf.default.send_redirects + tags: + - 3.2.2 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" + loop: + - net.ipv4.conf.all.accept_source_route + - net.ipv4.conf.default.accept_source_route + tags: + - 3.3.1 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" + loop: + - net.ipv6.conf.all.accept_source_route + - net.ipv6.conf.default.accept_source_route + when: not ipv6_disable + tags: + - 3.3.1 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" + loop: + - net.ipv4.conf.all.accept_redirects + - net.ipv4.conf.default.accept_redirects + tags: + - 3.3.2 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" + loop: + - net.ipv6.conf.all.accept_redirects + - net.ipv6.conf.default.accept_redirects + when: not ipv6_disable + tags: + - 3.3.2 + +- name: 3.3.3 - Ensure secure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" + loop: + - net.ipv4.conf.all.secure_redirects + - net.ipv4.conf.default.secure_redirects + tags: + - 3.3.3 + +- name: 3.3.4 - Ensure suspicious packets are logged + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '1'}) }}" + loop: + - net.ipv4.conf.all.log_martians + - net.ipv4.conf.default.log_martians + tags: + - 3.3.4 + +- name: 3.3.5 - Ensure broadcast ICMP requests are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({'net.ipv4.icmp_echo_ignore_broadcasts': '1'}) }}" + tags: + - 3.3.5 + +- name: 3.3.6 - Ensure bogus ICMP responses are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({'net.ipv4.icmp_ignore_bogus_error_responses': '1'}) }}" + tags: + - 3.3.6 + +- name: 3.3.7 - Ensure reverse path filtering is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '1'}) }}" + loop: + - net.ipv4.conf.all.rp_filter + - net.ipv4.conf.default.rp_filter + tags: + - 3.3.7 + +- name: 3.3.8 - Ensure TCP SYN Cookies is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({'net.ipv4.tcp_syncookies': '1'}) }}" + tags: + - 3.3.8 + +- name: 3.3.9 - Ensure IPv6 router advertisements are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" + when: not ipv6_disable + loop: + - net.ipv6.conf.all.accept_ra + - net.ipv6.conf.default.accept_ra + tags: + - 3.3.9 + +- name: 3.3 - list of IPv4 network settings + ansible.builtin.debug: + var: unneeded_ipv4_network + +- name: 3.3 - list of IPv6 network settings + ansible.builtin.debug: + var: unneeded_ipv6_network + +# The sysctl module will configure certain sysctl parameters. They are +# collected into a loop here to speed the implementation +# Once complete, notify the system to flush the network routes +- name: 3.3 - Process unneeded network settings for IPv4 + tags: + - 3.3.0 + block: + - name: 3.3 - Set networking parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + reload: true + state: present + sysctl_set: true + loop: "{{ lookup('ansible.builtin.dict', unneeded_ipv4_network) }}" + notify: Flush network routes + +- name: 3.3 - Process unneeded network settings for IPv6 + tags: + - 3.3.0 + block: + - name: 3.3 - Set networking parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + reload: true + state: present + sysctl_set: true + loop: "{{ lookup('ansible.builtin.dict', unneeded_ipv6_network) }}" + notify: Flush network routes + +- name: 3.1 - Disable uncommon network protocols + tags: + - 3.1.0 + block: + # This collection of tasks creates a empty list and save it as a fact. + # For every item that is encountered (without the tag being skipped), + # add a string to the list. + - name: 3.1.0 - Create empty list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: [] + + - name: 3.1.2 - Add sctp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['sctp'] }}" + tags: + - 3.1.2 + + - name: 3.1.3 - Add dccp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + ['dccp'] }}" + tags: + - 3.1.3 + +# Control 3.1.4 - disable wireless interfaces are machine dependent, skipping + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. + - name: 3.5.0 - Process uncommon network list + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install {{ item }} /bin/true" + state: present + create: true + owner: root + group: root + mode: 0644 + with_items: + - "{{ uncommon_network }}" + +# Section 3 - Firewall + +- name: 3.4.1 - Configure firewalld + when: enable_firewall is defined and enable_firewall == "firewalld" + tags: + - 3.4.1 + block: + - name: 3.4.1 - Configure firewalld + ansible.builtin.debug: + msg: "3.4.1 - Configure firewalld" + - name: 3.4.1.1 - Install firewalld ansible.builtin.dnf: name: "firewalld" state: present - notify: start firewalld # 3.4.2.1 + notify: Start firewalld # 3.4.2.1 - - name: 3.4.2.2 - Disable iptables service - ansible.builtin.service: - name: iptables + - name: 3.4.1.2 - Disable iptables service + ansible.builtin.systemd: + name: "{{ item }}" state: stopped enabled: false masked: true - ignore_errors: true - failed_when: false + when: "'iptables-services' in ansible_facts.packages" + loop: + - iptables + - ip6tables + tags: + - 3.4.1.2 + + - name: 3.4.1.2 - Remove iptables packages + ansible.builtin.package: + name: iptables-services + state: absent + tags: + - 3.4.1.2 - - name: 3.4.2.3 - Disable netfilters service + - name: 3.4.1.3 - Disable netfilters service ansible.builtin.systemd: name: nftables state: stopped @@ -1394,1241 +1727,1619 @@ masked: true when: "'nftables' in ansible_facts.packages" - - name: 3.4.2.4 - Set default zone + - name: 3.4.1.4 - Enable firewalld service + ansible.builtin.systemd: + name: firewalld + enabled: true + state: started + masked: false + + - name: 3.4.1.5 - Set default zone ansible.builtin.lineinfile: - path: "/etc/firewalld/firewalld.conf" - regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' - line: "DefaultZone={{ firewalld_default_zone }}" + path: "/etc/firewalld/firewalld.conf" + regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' + line: "DefaultZone={{ firewalld_default_zone }}" when: firewalld_default_zone is defined - notify: restart firewalld + notify: Restart firewalld - # 3.4.2.5 Ensure network interfaces are assigned to appropriate zone is machine dependent - # 3.4.2.6 Ensure unnecessary services and ports are not accepted + # 3.4.1.6 Ensure network interfaces are assigned to appropriate zone is machine dependent + # 3.4.1.7 Ensure unnecessary services and ports are not accepted - name: Notify users to configure the firewall ansible.builtin.debug: msg: - - "3.4.2.7 - Ensure default firewalld policy must be handled locally" + - "3.4.1.7 - Ensure default firewalld policy must be handled locally" + +- name: 3.4.2 - Configure nftables + when: enable_firewall is defined and enable_firewall == "nftables" + tags: + - 3.4.2 + block: + - name: 3.4.2 - Configure nftables + ansible.builtin.debug: + msg: "3.4.2 - Configure nftables" + + - name: 3.4.2.1 - ensure nftables is installed + ansible.builtin.dnf: + name: nftables + state: present + + - name: 3.4.2.2 - Disable firewalld service + ansible.builtin.systemd: + name: firewalld + enabled: false + masked: true + when: "'firewalld' is in ansible_facts.packages" + + - name: 3.4.2.3 - Ensure iptables-services not installed with nftables + ansible.builtin.dnf: + name: iptables-services + state: absent + + # Control 3.4.2.4 requires manual review, skipping (can be a TODO) + + - name: 3.4.2.5 - Ensure netfilters has at least one table + when: enable_firewall is defined and enable_firewall == "nftables" tags: - - 3.4.2 - when: enable_firewall is defined and enable_firewall == "firewalld" - tags: - - 3.4.1 - - 3.4.2 + - 3.4.2.5 + block: + - name: 3.4.2.5 - Find any current netfilter tables + ansible.builtin.command: nft list tables + register: tables_list + changed_when: false + + - name: 3.4.2.5 - Create a basic table if none exist + ansible.builtin.command: nft create table inet firewalld NFTables + when: not tables_list - # Control 3.4.3 Configure nftables, skipping +# Control 3.4.2.[6-9,11] is not set as it is very machine dependant - - name: 3.4.4.1 - Install iptables - block: - - name: 3.4.4.1 - Install iptables + - name: 3.4.2.10 - Ensure nftables is enabled + ansible.builtin.systemd: + name: nftables +- name: 3.4.3.1 - Install and configure iptables + when: enable_firewall is defined and enable_firewall == "iptables" + tags: + - 3.4.3.1 + block: + - name: 3.4.3.1.1 - Install iptables ansible.builtin.dnf: name: - "iptables" - "iptables-services" state: present - notify: start iptables + notify: Start iptables tags: - 3.4.4.1 - - name: 3.4.4.1 - Disable firewalld - ansible.builtin.service: + - name: 3.4.3.1.2 - Disable nftables + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: "'nftables' in ansible_facts.packages" + tags: + - 3.4.3.1.2 + + - name: 3.4.3.1.3 - Disable firewalld + ansible.builtin.systemd: name: firewalld state: stopped enabled: false - ignore_errors: true + masked: true + when: "'firewalld' in ansible_facts.packages" tags: - - 3.4.4.1 + - 3.4.3.1.3 - name: Notify user to configure firewall ansible.builtin.debug: msg: - - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" + - "Ensure default firewall policy (3.4.4.2.[1-4]) must be handled locally" tags: - - 3.4.4.1 + - 3.4.3.2.1 + +# Controls 3.4.3.2.[1-5] are machine specific + + - name: 3.4.3.2.6 - Ensure iptables is enabled and active + ansible.builtin.systemd: + name: iptables + enabled: true + masked: false + state: started + tags: + - 3.4.3.2.6 + +# Controls 3.4.3.3.[1-6] are manual configuration so just tell the user +- name: 3.4.3.3 - Install and configure iptables + when: enable_firewall is defined and enable_firewall == "iptables" and not ipv6_disable + tags: + - 3.4.3.3 + block: + - name: 3.4.3.3 - Configure IPv6 iptables + ansible.builtin.debug: + msg: "3.4.3.3.1 - Be sure to configure IPv6 ip6tables" + when: ipv6_disable and enable_firewall == "firewalld" + tags: + - 3.4.3.3 + +# Section 4 - Logging and Auditing + +- name: 4.1 Install and configure system auditing + when: enable_audit is defined and enable_audit + tags: + - 4.1.0 + block: + - name: 4.1.1.1 - Install Auditd + ansible.builtin.dnf: + name: + - audit + - audit-libs + state: present + tags: + - 4.1.1.1 + + - name: 4.1.1.3 - Ensure auditing for processes that start prior to auditd + # We check here because we don't know what position the audit=1 is in + # order to simply do the replace, so we are instead looking for the match in the file first. + # If it doesn't exist, then we can just insert it + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' + state: absent + check_mode: true + changed_when: false + register: audit_exist + failed_when: false + tags: + - 4.1.1.3 + + # use the replace module to add it to grub bootloader and then notify + # grub to rebuild + - name: 4.1.1.3 - enable audit service in grub + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit=1 ' + notify: Rebuild grub + when: not audit_exist.found + tags: + - 4.1.1.3 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, check if limit exists + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit_backlog_limit' + state: absent + check_mode: true + changed_when: false + register: audit_backlog_exist + failed_when: false + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, add audit_backlog_limit to grub + ansible.builtin.replace: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' + notify: Rebuild grub + when: not audit_backlog_exist.found + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (check) + ansible.builtin.lineinfile: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX=.*{{ audit_backlog_limit }}' + state: absent + check_mode: true + changed_when: false + register: our_limit + when: audit_backlog_exist.found + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (fix) + ansible.builtin.replace: + dest: /etc/default/grub + regexp: 'audit_backlog_limit=[\S]*' + replace: 'audit_backlog_limit={{ audit_backlog_limit }}' + notify: Rebuild grub + when: audit_backlog_exist.found and not our_limit.found + tags: + - 4.1.1.4 + + # Control is out of order to allow configuration before startup + - name: 4.1.1.2 - Enable auditd service + ansible.builtin.service: + name: auditd + enabled: true + state: started + tags: + - 4.1.1.2 + + # The replace module here is looking through file and make replacements of partial lines + + - name: 4.1.2.[1-3] - Configure audit log storage size + ansible.builtin.replace: + path: /etc/audit/auditd.conf + regexp: "{{ item.find }}" + replace: "{{ item.replace }}" + loop: + - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.2.1 + - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.2.2 + - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.2.2 + - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.2.2 + - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.2.2 + notify: Restart auditd + tags: + - 4.1.2.1 + - 4.1.2.2 + - 4.1.2.3 + + # For the next several checks, each one is in their own file, so we are using + # the copy module to place each file independently and then motifying + # a restart of auditd if anything changes. + + # cis-security versions before 1.5.0 did not enumerate the files, so the old files + # need to be removed to make way for the new versions + - name: 4.1.3 - Remove old rules files that were not in correct order (pre v1.5.0) + ansible.builtin.file: + path: "/etc/audit/rules.d/{{ item }}" + state: absent + tags: + - 4.1.3 + loop: + - sudolog.rules + - user_emulation.rules + - datetime.rules + - network.rules + - file-system-mounts.rules + - bad-file-access.rules + - user-group-info.rules + - dac.rules + - sessions.rules + - delete.rules + - login.rules + - MAC-policy.rules + - chcon.rules + - setfacl.rules + - chacl.rules + - usermod.rules + - modules.rules + + - name: 4.1.3.1 - Ensure changes to system administration scope is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sudolog.rules + src: audit_rules/sudolog.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.1 + - 4.1.3.3 + + - name: 4.1.3.2 - Ensure actions as another user are always logged + ansible.builtin.template: + dest: /etc/audit/rules.d/00-user_emulation.rules + src: audit_rules/user_emulation.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.2 + + - name: 4.1.3.4 - Ensure to collect events that modify date/time + ansible.builtin.template: + dest: /etc/audit/rules.d/00-datetime.rules + src: audit_rules/datetime.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.4 + + # TODO, determine if we need a separate RHEL9 version of network.rules + - name: 4.1.3.5 - Ensure modifications to network environment are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-network.rules + src: audit_rules/network.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.5 + + # Control 4.1.3.6 requires a system scan, skipping + + - name: 4.1.3.[7,10] - Ensure [un]successful file system mounts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-file-system-mounts.rules + src: audit_rules/file-system-mounts.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.3.7 + 4.1.3.10 + + - name: 4.1.3.7 - Ensure unsuccessful file access attempts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-bad-file-access.rules + src: audit_rules/bad-file-access.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.3.7 + + - name: 4.1.3.8 - Ensure events that modify user/group information are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-user-group-info.rules + src: audit_rules/user-group-info.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.8 + + - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-dac.rules + src: audit_rules/dac.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.9 + + - name: 4.1.3.11 - Ensure session initiation information is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sessions.rules + src: audit_rules/sessions.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.11 + + - name: 4.1.3.12 - Ensure system logins are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-login.rules + src: audit_rules/login.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.12 + + - name: 4.1.3.13 - Ensure file deletion events by users are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-delete.rules + src: audit_rules/delete.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.13 + + - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-MAC-policy.rules + src: audit_rules/MAC-policy.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.14 + + - name: 4.1.3.15 - Ensure successful and unsuccessful attempts to use the chcon command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chcon.rules + src: audit_rules/chcon.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.15 + + - name: 4.1.3.16 - Ensure successful and unsuccessful attempts to use the setfacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-setfacl.rules + src: audit_rules/setfacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.16 + + - name: 4.1.3.17 - Ensure successful and unsuccessful attempts to use the chacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chacl.rules + src: audit_rules/chacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.17 + + - name: 4.1.3.18 - Ensure successful and unsuccessful attempts to use the usermod command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-usermod.rules + src: audit_rules/usermod.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.18 + + - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-modules.rules + src: audit_rules/modules.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.19 + + - name: 4.1.3.20 - Ensure audit configuration is immutable + ansible.builtin.copy: + dest: /etc/audit/rules.d/99-finalize.rules + content: | + -e 2 + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.20 + + # 4.1.3.21 requires manual verification and ansible won't be able to check until after handlers are run; skipping + +# Section 4, Logging +- name: 4.2.1 - Configuring Rsyslog + when: log_service and log_service == "rsyslog" + block: + - name: 4.2.1.1 - Ensure rsyslog is installed + ansible.builtin.dnf: + name: rsyslog + state: present + tags: + - 4.2.1.1 + + - name: 4.2.1.2 - Ensure Rsyslog service is running + ansible.builtin.service: + name: rsyslog + enabled: true + state: started + tags: + - 4.2.1.2 + + - name: 4.2.1.3 - Configure journald to forward logs to rsyslog + tags: + - 4.2.1.3 + block: + - name: 4.2.1.3 - Find any rsyslog files where all logs are being forwarded to a loghost + ansible.builtin.shell: /usr/bin/grep -l -s "^*.*[^I][^I]*@" /etc/rsyslog.conf /etc/rsyslog.d/*.conf + register: rsyslog_forward_out + changed_when: false + failed_when: rsyslog_forward_out.rc == "2" + check_mode: false + + - name: 4.2.1.3 - Forward journald logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + line: "ForwardToSyslog=yes" + insertafter: "#ForwardToSyslog=no" + when: rsyslog_forward_out.stdout + + - name: 4.2.1.4 - Ensure rsyslog default file permissions are configured + ansible.builtin.lineinfile: + path: /etc/rsyslog.conf + regexp: '^\$FileCreateMode\s+0640' + line: "$FileCreateMode 0640" + create: true + owner: root + group: root + mode: 0644 + state: present + notify: Restart rsyslog + tags: + - 4.2.1.4 + + - name: 4.2.1.5 - Ensure logging is configured in rsyslog + ansible.builtin.copy: + src: "{{ rsyslog_file }}" + dest: "/etc/rsyslog.d/{{ rsyslog_file }}" + owner: root + group: root + mode: 0640 + when: rsyslog_file is defined and rsyslog_file | length > 0 + tags: + - 4.2.1.5 + + # Control 4.2.1.6 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent + # skipping + + - name: 4.2.1.7 - Ensure remote rsyslog messages are only acepted on designated log hosts + tags: + - 4.2.1.7 + block: + - name: 4.2.1.7 - Find all rsyslog conf files in /etc/rsyslog.d + ansible.builtin.find: + paths: "/etc/rsyslog.d" + patterns: "*.conf" + register: rsyslog_module_found + + - name: 4.2.1.7 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable imtcp loading module on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$ModLoad\s+imtcp' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable TCP port listening on non log hosts (rsylog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$InputTCPServerRun' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable TCP port listening on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$InputTCPServerRun' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Enable loading of imtcp module on log hosts + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$ModLoad\s+imtcp' + line: "$ModLoad imtcp" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + - name: 4.2.1.7 - Enable TCP Port listening on port {{ log_port }} + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$InputTCPServerRun {{ log_port }}' + line: "$InputTCPServerRun {{ log_port }}" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + +# 4.2.2 Configure journald +- name: 4.2.2.1.1 - configure journald + when: log_service and log_service == "journald" + tags: + - 4.2.2.1 + block: + - name: 4.2.2.1.1 - Ensure systemd-journald-remote is installed + ansible.builtin.dnf: + name: systemd-journal-remote + state: present + tags: + - 4.2.2.1.1 + + # Control 4.2.2.1.2 is machine dependent, skipping + # Control 4.2.2.1.3 required 4.2.2.1.2 be configured prior. skipping + + - name: 4.2.2.1.4 Ensure systemd-journal-remote.socket is masked + ansible.builtin.systemd: + name: systemd-journal-remote.socket + enabled: false + masked: true + tags: + - 4.2.2.1.4 + + - name: 4.2.2.1.4 Ensure jorunald service is masked + ansible.builtin.systemd: + name: systemd-journal-remote.service + enabled: false + masked: true + tags: + - 4.2.2.1.4 + + - name: 4.2.2.3 - Ensure journald compresses large files + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Compress=((?!yes).)*$" + line: "Compress=yes" + insertafter: "^#Compress=" + notify: Restart journald + tags: + - 4.2.2.3 + + - name: 4.2.2.4 - Ensure journald writes to peristent disk + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Storage=((?!persistent).)*$" + line: "Storage=persistent" + insertafter: "^#Storage=" + notify: Restart journald + tags: + - 4.2.2.4 + + - name: 4.2.2.5 - Ensure journald is not configured to send logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + state: absent + tags: + - 4.2.2.5 + + # Control 4.2.2.6, configure log rotation is machine specific, skipping + # TODO + # Control 4.2.2.7, Ensure journald default file permissions configured, is machine dependant, skipping + +# Control 4.2.3 is machine specific, skipping + + - name: 4.2.2.2 - Ensure journald service is enabled + ansible.builtin.systemd: + name: systemd-journald + state: started + masked: false + enabled: true + tags: + - 4.2.2.2 + +- name: 4.3 - Ensure logrotate is installed and configured + tags: + - 4.3.0 + block: + - name: 4.3.0 - Ensure logrotate is installed + ansible.builtin.package: + name: logrotate + state: present + + - name: 4.3.0 - Copy source logrotate file + ansible.builtin.copy: + src: "{{ logrotate_file }}" + dest: /etc/logrotate.conf + owner: root + group: root + mode: 0644 + setype: etc_t + when: logrotate_file and logrotate_file | length > 0 + +# Section 5 - Access and Authorization +# + +# This control is early in order to create the files. This will +# make sure they are available when cron starts +- name: 5.1.0 - Configure cron/at + when: "'cronie' in ansible_facts.packages" + tags: + - 5.1.0 + block: + - name: 5.1.8 - Ensure cron is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/cron.allow + owner: root + group: root + mode: 0600 + state: file + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.8 - Ensure cron is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/cron.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ cron_allow }}" + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.1 - Ensure cron is enabled + ansible.builtin.service: + name: crond + enabled: true + state: started + tags: + - 5.1.1 + + - name: 5.1.2 - Ensure permissions on /etc/crontab + ansible.builtin.file: + path: /etc/crontab + owner: root + group: root + mode: 0600 + tags: + - 5.1.2 + + - name: 5.1.[3-7] - Ensure permissions on crontab directories + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0700 + loop: + - /etc/cron.hourly + - /etc/cron.daily + - /etc/cron.weekly + - /etc/cron.monthly + - /etc/cron.d + tags: + - 5.1.3 + - 5.1.4 + - 5.1.5 + - 5.1.6 + - 5.1.7 + + - name: 5.1.9 - Ensure at is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/at.allow + owner: root + group: root + mode: 0600 + state: file + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + + - name: 5.1.9 - Ensure at is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/at.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ at_allow }}" + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + +- name: 5.2 - SSH File configurations + tags: + - 5.2.0 + block: + - name: 5.2.1 - Set permissions on SSH file + ansible.builtin.file: + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: 0600 + tags: + - 5.2.1 + + - name: 5.2.2 - Set Permissions on ssh private host keys + tags: + - 5.2.2 + block: + - name: 5.2.2 - Find all ssh private host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key + register: ssh_host_out + changed_when: false + + - name: 5.2.2 - Set permissions on all ssh private host keys (Red Hat set the group to ssh_keys and mode to 640) + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0600 + loop: "{{ ssh_host_out.files }}" + + - name: 5.2.3 - Set Permissions on ssh public host keys + tags: + - 5.2.3 + block: + - name: 5.2.3 - Find all ssh public host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key.pub + register: ssh_hostpub_out + changed_when: false + + - name: 5.2.3 - Set permissions on all ssh public host keys + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0644 + loop: "{{ ssh_hostpub_out.files }}" + + - name: 5.2.4 - Ensure SSH access is limited (AllowUsers) + tags: + - 5.2.4 + block: + - name: 5.2.4 - Ensure SSH access is limited (AllowedUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowUsers', value: "{{ ssh_allowed_users }}" } + when: ssh_allowed_users is defined and ssh_allowed_users + + - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowGroups', value: "{{ ssh_allowed_groups }}" } + when: ssh_allowed_groups is defined and ssh_allowed_groups + + - name: 5.2.4 - Ensure SSH access is limited (DenyUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: "^{{ item.key }}\ *{{ item.value }}" + line: "{{ item.key }} {{ item.value }}" + loop: + - { key: 'DenyUsers', value: "{{ ssh_denied_users }}" } + notify: Restart sshd + when: ssh_denied_users is defined and ssh_denied_users + + - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } + when: ssh_denied_groups is defined and ssh_denied_groups + + - name: 5.2.5 - Set LogLevel to {{ ssh_log_level }} + ansible.builtin.replace: + path: /etc/ssh/sshd_config + replace: "LogLevel {{ ssh_log_level | upper }}" + regexp: '^LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' + notify: Restart sshd + when: ssh_log_level == "INFO" or ssh_log_level == "WARN" + tags: + - 5.2.5 - when: enable_firewall is defined and enable_firewall == "iptables" - tags: - - 3.4.4 - - - - name: 3.4.4.2 - Configure IPv6 iptables - ansible.builtin.debug: - msg: "3.4.4.2, Configure IPv6 ip6tables skipping due to low use" - when: ipv6_disable and enable_firewall == "firewalld" - tags: - - 3.4.4.2 - - # Control 3.5 Ensure wireless interfaces are disabled is interface dependent - # skipping - - - name: 3.6 - Disable IPv6 - # We check here because we don't know what position the ipv6.disable is in - # order to simply do the replace, so we are instead looking for the match in the file first. - # If it doesn't exist, then we can just insert it - block: - - name: 3.6 - Find if IPv6 is currently in the grub file, shows changed when it is in the file - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX.*ipv6.disable=1' - state: absent - check_mode: true - changed_when: false - register: ipv6_disable_grub - failed_when: false - # use the replace module to add it to grub bootloader and then notify - # grub to rebuild - - name: 3.6 - Disable IPv6 in grub - ansible.builtin.replace: - path: /etc/default/grub - regexp: '^GRUB_CMDLINE_LINUX="' - replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' - notify: rebuild grub - when: not ipv6_disable_grub.found and ipv6_disable - - tags: - 3.6.0 - - # Section 4 - Logging and Auditing - - - name: 4.1 Install and configure system auditing - block: - - name: 4.1.1 - Install Audit - ansible.builtin.dnf: - name: - - audit - - audit-libs - state: present - tags: - - 4.1.1.1 - - - name: 4.1.1.2 - Enable auditd service - ansible.builtin.service: - name: auditd - enabled: true - state: started - tags: - - 4.1.1.2 - - - name: 4.1.1.3 - Ensure auditing for processes that start prior to auditd - # We check here because we don't know what position the audit=1 is in - # order to simply do the replace, so we are instead looking for the match in the file first. - # If it doesn't exist, then we can just insert it - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' - state: absent - check_mode: true - changed_when: false - register: audit_exist - failed_when: false - tags: - - 4.1.1.3 - - # use the replace module to add it to grub bootloader and then notify - # grub to rebuild - - name: 4.1.1.3 - enable audit service in grub - ansible.builtin.replace: - path: /etc/default/grub - regexp: '^GRUB_CMDLINE_LINUX="' - replace: 'GRUB_CMDLINE_LINUX="audit=1 ' - notify: rebuild grub - when: not audit_exist.found - tags: - - 4.1.1.3 - - - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, check if limit exists - ansible.builtin.lineinfile: - path: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX.*audit_backlog_limit' - state: absent - check_mode: true - changed_when: false - register: audit_backlog_exist - failed_when: false - tags: - - 4.1.1.4 - - - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, add audit_backlog_limit to grub - ansible.builtin.replace: - dest: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX="' - replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' - notify: rebuild grub - when: not audit_backlog_exist.found - tags: - - 4.1.1.4 - - - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (check) - ansible.builtin.lineinfile: - dest: /etc/default/grub - regexp: '^\s*GRUB_CMDLINE_LINUX=.*{{ audit_backlog_limit }}' - state: absent - check_mode: true - changed_when: false - register: our_limit - when: audit_backlog_exist.found - tags: - - 4.1.1.4 - - - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (fix) - ansible.builtin.replace: - dest: /etc/default/grub - regexp: 'audit_backlog_limit=[\S]*' - replace: 'audit_backlog_limit={{ audit_backlog_limit }}' - notify: rebuild grub - when: audit_backlog_exist.found and not our_limit.found - tags: - - 4.1.1.4 - - # The replace module here is looking through file and make replacements of partial lines - - - name: 4.1.2.[1-2] - Configure audit log storage size - ansible.builtin.replace: - path: /etc/audit/auditd.conf - regexp: "{{ item.find }}" - replace: "{{ item.replace }}" - loop: - - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.2.1 - - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.2.2 - - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.2.2 - - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.2.2 - - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.2.2 - notify: restart auditd - tags: - - 4.1.2.1 - - 4.1.2.2 - - 4.1.2.3 - - # For the next several checks, each one is in their own file, so we are using - # the copy module to place each file independently and then motifying - # a restart of auditd if anything changes. - - name: 4.1.4 - Ensure system logins are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/login.rules - src: audit_rules/login.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.4 - - - name: 4.1.5 - Ensure session initiation information is collected - ansible.builtin.template: - dest: /etc/audit/rules.d/sessions.rules - src: audit_rules/sessions.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.5 - - - name: 4.1.6 Ensure to collect events that modify date/time - ansible.builtin.template: - dest: /etc/audit/rules.d/datetime.rules - src: audit_rules/datetime.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - - 4.1.6 - - - name: 4.1.7 - Ensure modifications to Mandatory Access Controls are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/MAC-policy.rules - src: audit_rules/MAC-policy.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.7 - - - name: 4.1.8 - Ensure modifications to network environment are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/network.rules - src: audit_rules/network.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.8 - - # This is the first control that we use the min_uid variable that we determined earlier - - name: 4.1.9 - Ensure modifications to discretionary access controls are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/dac.rules - src: audit_rules/dac.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.9 - - - name: 4.1.10 - Ensure unsuccessful unauthorized file access attempts are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/bad-file-access.rules - src: audit_rules/bad-file-access.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.10 - - - name: 4.1.11 - Ensure events that modify user/group information are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/user-group-info.rules - src: audit_rules/user-group-info.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.11 - - - name: 4.1.12 - Ensure successful file system mounts are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/file-system-mounts.rules - src: audit_rules/file-system-mounts.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.12 - - # Control 4.1.13 - Ensure use of privileged commands is collected, is machine dependent - # skipping - - - name: 4.1.14 - Ensure file deletion events by users are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/delete.rules - src: audit_rules/delete.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.14 - - - name: 4.1.15 - Ensure kernel module loading and unloading is collected - ansible.builtin.template: - dest: /etc/audit/rules.d/modules.rules - src: audit_rules/modules.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.15 - - - name: 4.1.16 - Ensure sysadmin actions (sudolog) are collected - ansible.builtin.template: - dest: /etc/audit/rules.d/sudolog.rules - src: audit_rules/sudolog.rules - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - - 4.1.16 - - 4.1.3 - - - name: 4.1.17 - Ensure audit configuration is immutable - ansible.builtin.copy: - dest: /etc/audit/rules.d/99-finalize.rules - content: | - -e 2 - owner: root - group: root - mode: 0600 - notify: restart auditd - tags: - 4.1.17 - when: enable_audit is defined and enable_audit - - # Section 4, Logging - - name: 4.2.1.1 - Ensure rsyslog is installed - ansible.builtin.dnf: - name: rsyslog - state: present - tags: - - 4.2.1.1 - - - name: 4.2.1.2 - Enable Rsyslog - ansible.builtin.service: - name: rsyslog - enabled: true - tags: - - 4.2.1.2 - - - name: 4.2.1.3 - Ensure rsyslog default file permissions are configured - ansible.builtin.lineinfile: - path: /etc/rsyslog.conf - regexp: '^\$FileCreateMode\s+0640' - line: "$FileCreateMode 0640" - create: true - owner: root - group: root - mode: 0644 - state: present - tags: - - 4.2.1.3 - - - name: 4.2.1.4 - Ensure logging is configured - ansible.builtin.copy: - src: "{{ rsyslog_file }}" - dest: "/etc/rsyslog.d/{{ rsyslog_file }}" - owner: root - group: root - mode: 0640 - when: rsylog_file is defined - tags: - - 4.2.1.4 - - # Control 4.2.1.5 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent - # skipping - - - name: 4.2.1.6 - Ensure remote rsyslog messages are only acepted on designated log hosts - block: - - name: 4.2.1.6 - Find all rsyslog conf files in /etc/rsyslog.d - ansible.builtin.find: - paths: "/etc/rsyslog.d" - patterns: "*.conf" - register: rsyslog_module_found - - - name: 4.2.1.6 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) - ansible.builtin.lineinfile: - dest: "{{ item.path }}" - regexp: '^\$ModLoad\s+imtcp' - state: absent - loop: "{{ rsyslog_module_found.files }}" - when: log_host is defined and not log_host - - - name: 4.2.1.6 - Disable imtcp loading module on non log hosts (main rsyslog conf file) - ansible.builtin.lineinfile: - dest: "/etc/rsyslog.conf" - regexp: '^\$ModLoad\s+imtcp' - state: absent - when: log_host is defined and not log_host - - - name: 4.2.1.6 - Disable TCP port listening on non log hosts (rsylog.d conf files) - ansible.builtin.lineinfile: - dest: "{{ item.path }}" - regexp: '^\$InputTCPServerRun' - state: absent - loop: "{{ rsyslog_module_found.files }}" - when: log_host is defined and not log_host - - - name: 4.2.1.6 - Disable TCP port listening on non log hosts (main rsyslog conf file) - ansible.builtin.lineinfile: - dest: "/etc/rsyslog.conf" - regexp: '^\$InputTCPServerRun' - state: absent - when: log_host is defined and not log_host - - - name: 4.2.1.6 - Enable loading of imtcp module on log hosts - ansible.builtin.lineinfile: - dest: /etc/rsyslog.d/CIS.conf - regexp: '^\$ModLoad\s+imtcp' - line: "$ModLoad imtcp" - create: true - owner: root - group: root - mode: 0644 - when: log_host is defined and log_host - - - name: 4.2.1.6 - Enable TCP Port listening on port {{ log_port }} - ansible.builtin.lineinfile: - dest: /etc/rsyslog.d/CIS.conf - regexp: '^\$InputTCPServerRun {{ log_port }}' - line: "$InputTCPServerRun {{ log_port }}" - create: true - owner: root - group: root - mode: 0644 - when: log_host is defined and log_host - tags: - - 4.2.1.6 - - - name: 4.2.2 - Configure journald - block: - - name: Find any rsyslog files where all logs are being forwarded to a loghost - ansible.builtin.shell: /usr/bin/grep -l -s "^*.*[^I][^I]*@" /etc/rsyslog.conf /etc/rsyslog.d/*.conf - register: rsyslog_forward_out - changed_when: false - failed_when: rsyslog_forward_out.rc == "2" - check_mode: false - - - name: 4.2.2.1 - Forward journald logs to rsyslog IF rsyslog is sending logs to a log host - ansible.builtin.lineinfile: - dest: /etc/systemd/journald.conf - regexp: "^ForwardToSyslog=((?!yes).)*$" - line: "ForwardToSyslog=yes" - insertafter: "#ForwardToSyslog=no" - when: rsyslog_forward_out.stdout - tags: - - 4.2.2.1 - - - name: 4.2.2.2 - Ensure journald compresses large files - ansible.builtin.lineinfile: - dest: /etc/systemd/journald.conf - regexp: "^Compress=((?!yes).)*$" - line: "Compress=yes" - insertafter: "^#Compress=" - tags: - - 4.2.2.2 - - - name: 4.2.2.3 - Ensure journald writes to peristent disk - ansible.builtin.lineinfile: - dest: /etc/systemd/journald.conf - regexp: "^Storage=((?!persistent).)*$" - line: "Storage=persistent" - insertafter: "^#Storage=" - tags: - - 4.2.2.3 - - # Control 4.2.3, Ensure permissions on log files are configured, is machine dependant - # skipping - - - name: 4.3 - Ensure logrotate is installed and configured - ansible.builtin.dnf: - name: logrotate - state: present - tags: - - 4.3.0 - - # 4.3 - Ensure logrotate is configured skipped as machine and environment dependent - - # Section 5 - Access and Authorization - # - - # This control is early in order to create the files. This will - # make sure they are available when cron starts - - name: Create the cron/at allow files (5.1.8) - ansible.builtin.copy: - dest: "{{ item }}" - content: "" - force: false - owner: root - group: root - mode: 0644 - with_items: - - /etc/cron.allow - - /etc/at.allow - tags: - - 5.1.8 - - - name: 5.1.1 - Ensure cron is enabled - ansible.builtin.service: - name: crond - enabled: true - state: started - when: "'cronie' in ansible_facts.packages" - tags: - - 5.1.1 - - - name: 5.1.2 - Ensure permissions on /etc/crontab - ansible.builtin.file: - path: /etc/crontab - owner: root - group: root - mode: 0600 - tags: - - 5.1.2 - - - name: 5.1.[3-7] - Ensure permissions on crontab directories - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0700 - loop: - - /etc/cron.hourly - - /etc/cron.daily - - /etc/cron.weekly - - /etc/cron.monthly - - /etc/cron.d - tags: - - 5.1.3 - - 5.1.4 - - 5.1.5 - - 5.1.6 - - 5.1.7 - - # Restrict at/cron skipped (5.1.8) as is rarely used and environment dependent - - # If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag - - name: 5.2 - SSH File configurations - block: - - name: 5.2.1 - Set permissions on SSH file - ansible.builtin.file: - dest: /etc/ssh/sshd_config - owner: root - group: root - mode: 0600 - tags: - - 5.2.1 - - # Control 5.2.2, Ensure SSH access is limited is environment dependent - # skipping - - - name: 5.2.3 - Set Permissions on ssh private host keys - block: - - name: 5.2.3 - Find all ssh private host keys - ansible.builtin.find: - paths: /etc/ssh - file_type: file - patterns: ssh_host_*_key - register: ssh_host_out - changed_when: false - - - name: 5.2.3 - Set permissions on all ssh private host keys (Red Hat set the group to ssh_keys and mode to 640) - ansible.builtin.file: - dest: "{{ item.path }}" - owner: root - group: ssh_keys - mode: 0640 - loop: "{{ ssh_host_out.files }}" - tags: - - 5.2.3 - - - name: 5.2.4 - Set Permissions on ssh public host keys - block: - - name: 5.2.4 - Find all ssh public host keys - ansible.builtin.find: - paths: /etc/ssh - file_type: file - patterns: ssh_host_*_key.pub - register: ssh_hostpub_out - changed_when: false - - - name: 5.2.4 - Set permissions on all ssh public host keys - ansible.builtin.file: - dest: "{{ item.path }}" - owner: root - group: root - mode: 0644 - loop: "{{ ssh_hostpub_out.files }}" - tags: - - 5.2.4 - - - name: 5.2.5 - Set LogLevel to {{ ssh_log_level }} or more verbose, but not debug - ansible.builtin.replace: - path: /etc/ssh/sshd_config - replace: "LogLevel {{ ssh_log_level | upper }}" - regexp: '^LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' - notify: restart sshd - when: ssh_log_level == "INFO" or ssh_log_level == "WARN" - tags: - - 5.2.5 - - # Using replace with a replace argument of "" removes the selected - # text. - - name: 5.2.6 - Disable X11 forwarding - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - state: absent - regexp: '^X11Forwarding\s*yes' - notify: restart sshd - tags: - - 5.2.6 - - - name: 5.2.7 - Ensure SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} or less - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "MaxAuthTries {{ ssh_max_auth_tries }}" - regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' - insertafter: "^#MaxAuthTries" - notify: restart sshd - tags: - - 5.2.7 - - - name: 5.2.8 - Ensure IgnoreRhosts is set - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "IgnoreRhosts yes" - regexp: '^IgnoreRhosts\s*[^y]' - notify: restart sshd - tags: - - 5.2.8 - - - name: 5.2.9 - Ensure HostbasedAuthentication is disabled - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "HostbasedAuthentication no" - regexp: '^HostbasedAuthentication\s*[^n]' - notify: restart sshd - tags: - - 5.2.9 - - - name: 5.2.10 Ensure PermitRootLogin is disbled - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "PermitRootLogin no" - regexp: '^PermitRootLogin\s*[^n]' - notify: restart sshd - tags: - - 5.2.10 - - - name: 5.2.11 - Ensure SSH PermitEmptyPasswords is disabled - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - state: absent - regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd - tags: - - 5.2.11 - - - name: 5.2.12 - Ensure PermitUserEnvironment is disabled - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - state: absent - regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd - tags: - - 5.2.12 - - - name: 5.2.13 - Ensure SSH Idle Timeout is configured ClientAliveInterval - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "ClientAliveInterval {{ ssh_alive_interval }}" - regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" - insertafter: "^#ClientAliveInterval" - notify: restart sshd - tags: - - 5.2.13 - - - name: 5.2.13 - Ensure SSH Idle Timeout is configured ClientAliveCountMax - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "ClientAliveCountMax {{ ssh_alive_count_max }}" - regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" - insertafter: "^#ClientAliveCountMax" - notify: restart sshd - tags: - - 5.2.13 - - - name: 5.2.14 - Ensure SSH LoginGraceTime is set to {{ ssh_grace_time }} or less - ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "LoginGraceTime {{ ssh_grace_time }}" - regexp: "^LoginGraceTime {{ ssh_grace_time }}" - insertafter: "^#LoginGraceTime" - notify: restart sshd - tags: - - 5.2.14 - - - name: 5.2.15 - Ensure SSH Banner is configured - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "Banner /etc/{{ ssh_login_banner }}" - regexp: "^Banner /etc/{{ ssh_login_banner }}" - notify: restart sshd - tags: - - 5.2.15 - - - name: 5.2.16 - Ensure SSH is configured to use PAM - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "UsePAM yes" - regexp: '^UsePAM\s+[yes|no]' - notify: restart sshd - tags: - - 5.2.16 - - - name: 5.2.17 - Disable TCP Forwarding - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "AllowTcpForwarding no" - regexp: '^AllowTcpForwarding\s+(yes|no)' - insertafter: "^#AllowTcpForwarding" - notify: restart sshd - tags: - - 5.2.17 - - - name: 5.2.18 - Limit max unauthenticated startups - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "maxstartups 10:30:60" - regexp: '^maxstartups\s+10:30:60' - notify: restart sshd - tags: - - 5.2.18 - - - name: 5.2.19 - Limit max sessions - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "maxsessions {{ ssh_max_sessions }}" - regexp: '^maxsessions\s+[{{ ssh_max_sessions }}]' - notify: restart sshd - tags: - - 5.2.19 - - - name: 5.2.20 - Ensure system crypto policy isn't overriden in SSH - ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - state: absent - regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' - notify: restart sshd - tags: - - 5.2.20 - tags: - - 5.2.0 - - # Control section 5.3, authselect, cannot be used with Red Hat IPA or Microsoft AD - # Skipping until can assure we know how to test against this. - - - name: 5.4.1 - Configure PAM files and password requirements - block: - - name: 5.4.1 - require at least one digit in passwords - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: dcredit = -1 - regexp: "^dcredit = -1" - insertafter: "# dcredit = 0" - when: password_req_digit - - - name: 5.4.1 - require at least one uppercase letter in passwords - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: ucredit = -1 - regexp: "^ucredit = -1" - insertafter: "# ucredit = 0" - when: password_req_upper - - - name: 5.4.1 - require at least one lowercase letter in passwords - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: lcredit = -1 - regexp: "^lcredit = -1" - insertafter: "^# lcredit = 0" - when: password_req_lower - - - name: 5.4.1 - Require at least one special character in passwords - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: ocredit = -1 - regexp: "^ocredit = -1" - insertafter: "^# ocredit = 0" - when: password_req_digit - - - name: 5.4.1 - Require at least {{ password_min_length }} characters in passwords - ansible.builtin.lineinfile: - path: /etc/security/pwquality.conf - line: minlen = {{ password_min_length }} - regexp: "^minlen = {{ password_min_length }}" - insertafter: "^# minlen = 8" - when: password_req_digit - tags: - - 5.4.1 - - # Control 5.4.2, Ensure lockout for failed password attempts, requires file replacement - # skipping - - # Control 5.4.3, Set password retention, requries file replacement - # skipping - - # Control 5.4.4, Ensure password hashing algorithm is SHA-512, requires file replacement - # skipping - - - name: 5.5.1.1 - Ensure password expiration is {{ password_expire_days }} days or less - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' - line: "PASS_MAX_DAYS {{ password_expire_days }}" - state: present - tags: - - 5.5.1.1 - - - name: 5.5.1.2 - Ensure password change days is set to {{ password_min_days }} - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' - line: "PASS_MIN_DAYS {{ password_min_days }}" - state: present - tags: - - 5.5.1.2 - - - name: 5.5.1.3 - Ensure password warning days is set to {{ password_warning_days }} - ansible.builtin.lineinfile: - dest: /etc/login.defs - regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' - line: "PASS_WARN_AGE {{ password_warning_days }}" - state: present - tags: - - 5.5.1.3 - - # We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days - # The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well - - name: 5.5.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration - ansible.builtin.replace: - path: /etc/default/useradd - regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" - replace: "INACTIVE={{ password_inactive_lock_days }}" - owner: root - group: root - mode: 0600 - tags: - - 5.5.1.4 - - # 5.5.1.5, Ensure all users last password change date is in the past, - # is not easily automated. Will revisit later - - # 5.5.2, Ensure system accounts are secured, is machine dependent. - # skipping - - - name: 5.5.3 - Ensure default shell timeout is {{ shell_timeout }} seconds or less - ansible.builtin.blockinfile: - path: "{{ item }}" - block: "TMOUT={{ shell_timeout }}" - marker: "# {mark} Ansible Managed CIS Timeout" - loop: - - /etc/bashrc - - /etc/profile - tags: - - 5.5.3 - - # Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod - - name: 5.5.4 - Ensure default group for root is GID 0 - ansible.builtin.command: /usr/sbin/usermod -g 0 root - changed_when: false - tags: - - 5.5.4 - - - name: 5.5.5 - Ensure umask is set - ansible.builtin.replace: - path: "{{ item }}" - replace: " umask {{ default_umask }}" - regexp: '^\s*umask\s*022' - loop: - - /etc/bashrc - - /etc/profile - tags: - - 5.5.5 - - # 5.5.6, Ensure root login is restricted to system console - # not easily automatable because of the various TTYs on a machine - # Manually verify that only physically secure TTYs are listed in - # /etc/securetty - - - name: 5.7 - Restrict su to wheel group - block: - - name: 5.7 - Configure PAM to only allow su from wheel group - ansible.builtin.replace: - path: /etc/pam.d/su - regexp: '^#auth\s+required\s+pam_wheel.so\s+use_uid' - replace: "auth required pam_wheel.so use_uid" - - - name: 5.7 - Add root to the wheel group - ansible.builtin.user: - name: root - groups: wheel - append: true - tags: - - 5.7.0 - - # Section 6 - System Maintenance - - # Control 6.1.1 - Audit system file permissions, the report is time consuming and requires manual review - # skipping - - - name: 6.1.[2,4] - Ensure permissions on /etc/passwd /etc/group - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0644 - loop: - - passwd - - group - tags: - - 6.1.2 - - 6.1.4 - - - name: 6.1.[3,5] - Ensure permissions on /etc/shadow /etc/gshadow - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - loop: - - shadow - - gshadow - tags: - - 6.1.3 - - 6.1.5 - - - name: 6.1.[6-9] - Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- - ansible.builtin.file: - path: /etc/{{ item }} - owner: root - group: root - mode: 0000 - with_items: - - passwd- - - shadow- - - group- - - gshadow- - tags: - - 6.1.6 - - 6.1.7 - - 6.1.8 - - 6.1.9 - - # Control 6.1.10, Ensure no world writable files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.10 - Ensure no world writable files exist - block: - - name: 6.1.10 - Find any world writiable files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" - register: ww_files - changed_when: false - check_mode: false - - - name: 6.1.10 - Print any world writable files found - ansible.builtin.debug: - msg: "World writiable files found: {{ ww_files.stdout }}" - changed_when: true - when: ww_files.stdout - tags: - - 6.1.10 - - # Control 6.1.11, Ensure no unowned files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.11 - Ensure no unowned files exist - block: - - name: 6.1.11 - Find any unowned files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" - register: uo_files - changed_when: false - check_mode: false - - - name: 6.1.11 - Print any unowned files found - ansible.builtin.debug: - msg: "unowned files found: {{ uo_files.stdout }}" - changed_when: true - when: uo_files.stdout - tags: - - 6.1.11 - - # Control 6.1.12, Enscure no ungrouped files exist, is system dependent so we are only - # providing a list to the user here. - - name: 6.1.12 - Ensure no ungrouped files exist - block: - - name: 6.1.12 - Find any ungrouped files - ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" - register: ug_files - changed_when: false - check_mode: false - - - name: 6.1.12 - Print any ungrouped files found - ansible.builtin.debug: - msg: "ungrouped files found: {{ uo_files.stdout }}" - changed_when: true - when: ug_files.stdout - tags: - - 6.1.12 - - - # Control 6.1.13, Audit SUID executables, is a verification and is system dependent. - # Not implementing because it will always return some SUID files - # Manually review the control - - # Control 6.1.14, Audit SGID executables, is a verification and is system dependent. - # Not implementing because it will always return some SUID files - # Manually review the control - - - name: 6.2.1 - Ensure password fields are not empty - block: - - name: 6.2.1 - Check to see if there are any accounts with empty passwords - ansible.builtin.shell: "/usr/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" - changed_when: false - register: empty_passwords - check_mode: false - - - - name: 6.2.1 - Report the named users to the report - ansible.builtin.debug: - msg: "The user {{ item }} has an empty password" - when: empty_passwords.stdout - changed_when: true - loop: "{{ empty_passwords.stdout_lines }}" - tags: - - 6.2.1 - - - name: 6.2.[2,4-5] - Ensure no legacy "+" entries exist in password files - ansible.builtin.lineinfile: - regexp: '^\+:.*' - state: absent - path: "{{ item }}" - when: ypbind is defined and not ypbind - loop: - - /etc/passwd - - /etc/shadow - - /etc/group - tags: - - 6.2.2 - - 6.2.4 - - 6.2.5 - - - name: 6.2.3 - Ensure root PATH integrity - block: - - name: 6.2.3 - Run script on path variable - ansible.builtin.script: files/path_check.sh - changed_when: false - register: path_check - check_mode: false - - - name: 6.2.3 - Print report to user - ansible.builtin.debug: - msg: - - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" - - "that exist in root's path because of this. It should be run as root on the target machine manually." - - " {{ path_check.stdout }}" - when: path_check.stdout and not ansible_check_mode - tags: - - 6.2.3 - - - name: 6.2.6 - Report on multiple accounts with UID of 0 - block: - - name: 6.2.6 - find accounts with UID of 0 - ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" - register: rootuid - changed_when: rootuid.rc == 2 - check_mode: false - - - name: 6.2.6 - Report on mulitple accounts with UID of 0 - ansible.builtin.debug: - msg: - - "Accounts with UID zero in addition to root" - - " {{ rootuid.stdout_lines }}" - changed_when: true - when: rootuid.stdout != 'root' - tags: - - 6.2.6 - - # Control 6.2.7 is environment dependent, skipping - # Control 6.2.8 is environment dependent, skipping - # Controls 6.2.[9-13,20] are recommended to be handled by monitoring software - - - name: 6.2.14 - Report on groups in /etc/passwd with a GID not in /etc/group - block: - - name: 6.2.14 - Use script to pull the list of groups - ansible.builtin.script: - cmd: files/undefined_groups.sh - register: undefined_groups - changed_when: false - check_mode: false - - - name: 6.2.14 - Report to user any unreferenced groups - ansible.builtin.debug: - msg: "{{ undefined_groups.stdout_lines }}" - changed_when: true - when: undefined_groups.stdout - tags: - - 6.2.14 - - - name: 6.2.15 - Report on duplicate UIDs in /etc/passwd - block: - - name: 6.2.15 - Use script to pull the list of duplicate UIDs - ansible.builtin.script: - cmd: files/duplicate_uids.sh - register: duplicate_uids - changed_when: false - check_mode: false - - - name: 6.2.15 - Print report of duplicated UIDs to user - ansible.builtin.debug: - msg: "{{ duplicate_uids.stdout_lines }}" - changed_when: true - when: duplicate_uids.stdout - tags: - - 6.2.15 - - - name: 6.2.16 - Report on duplicate GIDs in /etc/group - block: - - name: 6.2.16 - Use script to pull the list of duplicate GIDs - ansible.builtin.script: - cmd: files/duplicate_guids.sh - register: duplicate_guids - changed_when: false - check_mode: false - - - name: 6.2.16 - Print report of duplcate GIDs to user - ansible.builtin.debug: - msg: "{{ duplicate_guids.stdout_lines }}" - changed_when: true - when: duplicate_guids.stdout - tags: - - 6.2.16 - - - name: 6.2.17 - Report on duplicate users in /etc/passwd - block: - - name: 6.2.17 - Use script to pull the list of users - ansible.builtin.script: - cmd: files/duplicate_users.sh - register: duplicate_users - changed_when: false - check_mode: false - - - name: 6.2.17 - Print report of duplicate users to user - ansible.builtin.debug: - msg: "{{ duplicate_users.stdout_lines }}" - changed_when: true - when: duplicate_users.stdout - tags: - - 6.2.17 - - - name: 6.2.18 - Report on duplicate groups in /etc/group - block: - - name: 6.2.18 - Use script to pull the list of groups - ansible.builtin.script: - cmd: files/duplicate_groups.sh - register: duplicate_groups - changed_when: false - check_mode: false - - - name: 6.2.18 - Print report of duplicate groups to user - ansible.builtin.debug: - msg: "{{ duplicate_groups.stdout_lines }}" - changed_when: true - when: duplicate_groups.stdout - tags: - - 6.2.18 - - - name: 6.2.19 - Report on shadow group in /etc/group - block: - - name: 6.2.19 - Determine if the shadow group exists in /etc/group - ansible.builtin.command: /usr/bin/grep "^shadow:" /etc/group - register: shadow_out - changed_when: false - failed_when: shadow_out.rc == "2" - check_mode: false - - - name: 6.2.19 - Print report of shadow group to user - ansible.builtin.debug: - msg: "Shadow group exists in /etc/group. Remove" - changed_when: true - when: shadow_out.stdout - - - name: 6.2.20 - Report on users that do not have a home directory - block: - - name: 6.2.20 - Use script to find the users - ansible.builtin.script: - cmd: files/non_existant_homedirs.sh - register: nohomedir - changed_when: false - - - name: 6.2.20 - Print report of users that do not have a home directory - ansible.builtin.debug: - msg: "{{ nohomedir.stdout_lines }}" - changed_when: true - when: nohomedir.stdout - tags: - - 6.2.20 + - name: 5.2.6 - Ensure SSH is configured to use PAM + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "UsePAM yes" + regexp: '^UsePAM\s+[yes|no]' + insertafter: "#UsePAM" + notify: Restart sshd + tags: + - 5.2.6 + + - name: 5.2.7 - Ensure PermitRootLogin is disbled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "PermitRootLogin no" + regexp: '^PermitRootLogin\s*[^n]' + insertafter: '^#PermitRootLogin\s*[^n]' + notify: Restart sshd + tags: + - 5.2.7 + + - name: 5.2.8 - Ensure HostbasedAuthentication is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "HostbasedAuthentication no" + regexp: '^HostbasedAuthentication\s*[^n]' + insertafter: '^#HostbasedAuthentication\s*[^n]' + notify: Restart sshd + tags: + - 5.2.8 + + - name: 5.2.9 - Ensure SSH PermitEmptyPasswords is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitEmptyPasswords\s*[^n]' + notify: Restart sshd + tags: + - 5.2.9 + + - name: 5.2.10 - Ensure PermitUserEnvironment is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitUserEnvironment\s*[^n]' + notify: Restart sshd + tags: + - 5.2.10 + + - name: 5.2.11 - Ensure IgnoreRhosts is set + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "IgnoreRhosts yes" + regexp: '^IgnoreRhosts\s*[^y]' + insertafter: '^#IgnoreRhosts\s*[^y]' + notify: Restart sshd + tags: + - 5.2.11 + + - name: 5.2.12 - Disable X11 forwarding + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^X11Forwarding\s*yes' + state: absent + notify: Restart sshd + tags: + - 5.2.12 + + - name: 5.2.13 - Disable TCP Forwarding + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "AllowTcpForwarding no" + regexp: '^AllowTcpForwarding\s+(yes|no)' + insertafter: "^#AllowTcpForwarding" + notify: Restart sshd + tags: + - 5.2.13 + + - name: 5.2.14 - Ensure system crypto policy isn't overriden in SSH + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + state: absent + regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' + notify: Restart sshd + tags: + - 5.2.14 + + - name: 5.2.15 - Ensure SSH Banner is configured + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "Banner /etc/{{ ssh_login_banner }}" + regexp: "^Banner /etc/{{ ssh_login_banner }}" + insertafter: "^#Banner none" + notify: Restart sshd + tags: + - 5.2.15 + + - name: 5.2.16 - Ensure SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "MaxAuthTries {{ ssh_max_auth_tries }}" + regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' + insertafter: "^#MaxAuthTries" + notify: Restart sshd + tags: + - 5.2.16 + + - name: 5.2.17 - Limit max unauthenticated startups + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxStartups 10:30:60" + regexp: '^MaxStartups\s+10:30:60' + insertafter: '^#MaxStartups\s+10:30:100' + notify: Restart sshd + tags: + - 5.2.17 + + - name: 5.2.18 - Limit max sessions to {{ ssh_max_sessions }} + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxSessions {{ ssh_max_sessions }}" + regexp: '^MaxSessions\s+[{{ ssh_max_sessions }}]' + insertafter: '^#MaxSessions\s+10' + notify: Restart sshd + tags: + - 5.2.18 + + - name: 5.2.19 - Ensure SSH LoginGraceTime is set to {{ ssh_grace_time }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "LoginGraceTime {{ ssh_grace_time }}" + regexp: "^LoginGraceTime {{ ssh_grace_time }}" + insertafter: "^#LoginGraceTime" + notify: Restart sshd + tags: + - 5.2.19 + + - name: 5.2.20 - Ensure SSH Idle Timeout is configured ClientAliveInterval + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveInterval {{ ssh_alive_interval }}" + regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" + insertafter: "^#ClientAliveInterval" + notify: Restart sshd + tags: + - 5.2.20 + + - name: 5.2.20 - Ensure SSH Idle Timeout is configured ClientAliveCountMax + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveCountMax {{ ssh_alive_count_max }}" + regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" + insertafter: "^#ClientAliveCountMax" + notify: Restart sshd + tags: + - 5.2.20 + +# Make sure the sudoers file includes the requirement to use pty +- name: 5.3.2 - Ensure sudo commands use pty + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*use_pty' + line: "Defaults use_pty" + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.2 + +# Make sure the sudoers file includes the requirement to log to a file +- name: 5.3.3 - Ensure sudo log file exists + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*logfile="{{ sudo_log }}"' + line: 'Defaults logfile="{{ sudo_log }}"' + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.3 + +- name: 5.3.4 - Require password for priviledge escalation + tags: + - 5.3.4 + block: + - name: 5.3.4 - Find any instances of 'NOPASSWD' in /etc/sudoers or /etc/sudoers.d/* + ansible.builtin.find: + paths: "{{ item }}" + file_type: file + contains: "NOPASSWD" + register: sudo_nopasswd + with_fileglob: + - "/etc/sudoers" + - "/etc/sudoers.d/*" + + - name: 5.3.4 - Inform the user that instances were found + ansible.builtin.debug: + msg: "NOPASSWD was found in a sudoers file, please check and remove if not needed!" + when: sudo_nopasswd + +- name: 5.3.5 - Ensure re-authentication for privilege escalation is not disabled globally + tags: + - 5.3.5 + block: + - name: 5.3.5 - Find any instances of '!authenticate' in /etc/sudoers or /etc/sudoers.d/* + ansible.builtin.find: + paths: "{{ item }}" + file_type: file + contains: '^[^#].*\!authenticate' + register: sudo_reauthenticate + with_fileglob: + - "/etc/sudoers" + - "/etc/sudoers.d/*" + + - name: 5.3.5 - Inform the user that instances were found + ansible.builtin.debug: + msg: "!authenticate was found in a sudoers file, please check and remove if not needed!" + when: sudo_reauthenticate + +# Control 5.3.6 TODO + +- name: 5.3.7 - Restrict su to wheel group + tags: + - 5.3.7 + block: + - name: 5.3.7 - Configure PAM to only allow su from wheel group + ansible.builtin.replace: + path: /etc/pam.d/su + regexp: '^#auth\s+required\s+pam_wheel.so\s+use_uid' + replace: "auth required pam_wheel.so use_uid group=wheel" + + - name: 5.3.7 - Add root to the wheel group + ansible.builtin.user: + name: root + groups: wheel + append: true + + +# Control section 5.4, authselect, cannot be used with Red Hat IPA or Microsoft AD +# Skipping until can assure we know how to test against this. + +# Control 5.4.3, Set password retention, requries file replacement +# skipping + +- name: 5.5.0 - Configure PAM files and password requirements + tags: + - 5.5.0 + block: + - name: 5.5.1 - require at least one digit in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: dcredit = -1 + regexp: "^dcredit = -1" + insertafter: "# dcredit = 0" + when: password_req_digit + + - name: 5.5.1 - require at least one uppercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ucredit = -1 + regexp: "^ucredit = -1" + insertafter: "# ucredit = 0" + when: password_req_upper + + - name: 5.5.1 - require at least one lowercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: lcredit = -1 + regexp: "^lcredit = -1" + insertafter: "^# lcredit = 0" + when: password_req_lower + + - name: 5.5.1 - Require at least one special character in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ocredit = -1 + regexp: "^ocredit = -1" + insertafter: "^# ocredit = 0" + when: password_req_digit + + - name: 5.5.1 - Require at least {{ password_min_length }} characters in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: minlen = {{ password_min_length }} + regexp: "^minlen = {{ password_min_length }}" + insertafter: "^# minlen = 8" + when: password_req_digit + +- name: 5.5.2 - Ensure lockout attempts for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" + line: "deny = {{ password_failed_attempts }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 + +- name: 5.5.2 - Ensure lockout time for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" + line: "unlock_time={{ password_failed_time }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 + +# 5.5.3 - Password retention involves configuring pam.conf, skipping + +- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 + ansible.builtin.replace: + path: /etc/libuser.conf + regexp: "^crypt_style\ *=\ *(sha512|yescrypt)" + replace: "crypt_style = sha512" + after: "[defaults]" + owner: root + group: root + mode: 0644 + tags: + - 5.5.4 + +- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 + ansible.builtin.replace: + path: /etc/login.defs + regexp: "^ENCRYPT_METHOD\ *(SHA512|YESCRYPT)" + replace: "ENCRYPT_METHOD SHA512" + tags: + - 5.5.4 + +- name: 5.6.1.1 - Ensure password expiration is {{ password_expire_days }} days or less + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' + line: "PASS_MAX_DAYS {{ password_expire_days }}" + state: present + tags: + - 5.6.1.1 + +- name: 5.6.1.2 - Ensure password change days is set to {{ password_min_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' + line: "PASS_MIN_DAYS {{ password_min_days }}" + state: present + tags: + - 5.6.1.2 + +- name: 5.6.1.3 - Ensure password warning days is set to {{ password_warning_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' + line: "PASS_WARN_AGE {{ password_warning_days }}" + state: present + tags: + - 5.6.1.3 + +# We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days +# The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well +- name: 5.6.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration + ansible.builtin.replace: + path: /etc/default/useradd + regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" + replace: "INACTIVE={{ password_inactive_lock_days }}" + owner: root + group: root + mode: 0600 + tags: + - 5.6.1.4 + +# 5.6.1.5, Ensure all users last password change date is in the past, +# is not easily automated. Will revisit later + +# 5.6.2, Ensure system accounts are secured, requires manual intervention, skipping + +- name: 5.6.3 - Ensure default shell timeout is {{ shell_timeout }} seconds or less + ansible.builtin.blockinfile: + path: "{{ item }}" + block: | + TMOUT={{ shell_timeout }} + export TMOUT + marker: "# {mark} Ansible Managed CIS Timeout" + loop: + - /etc/bashrc + - /etc/profile + tags: + - 5.6.3 + +# Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod +- name: 5.6.4 - Ensure default group for root is GID 0 + ansible.builtin.command: /usr/sbin/usermod -g 0 root + changed_when: false + tags: + - 5.6.4 + +- name: 5.6.5 - Ensure default user umask is set + ansible.builtin.replace: + path: "{{ item }}" + replace: " umask {{ default_umask }}" + regexp: '^\s*umask\s*022' + loop: + - /etc/bashrc + - /etc/profile + - /etc/login.defs + tags: + - 5.6.5 + +# The user module here uses a known salt to idompotently set the password for multiple runs +# see https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#hash-filters +- name: 5.6.6 - Set root password + tags: + - 5.6.6 + block: + - name: 5.6.6 - Check if root has a password + ansible.builtin.lineinfile: + path: /etc/shadow + regexp: '^root:[*\!|*\*]*:' + state: absent + check_mode: true + changed_when: false + register: root_pw_check + failed_when: false + + - name: 5.6.6 - Set root password + ansible.builtin.user: + name: root + password: "{{ root_password | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" + when: root_pw_check.found != "0" and root_password is defined + +# Section 6 - System Maintenance + +# Control 6.1.1 - Audit system file permissions, the report is time consuming and requires manual review +# skipping + + # Find all local filesystem directories and set the sticky bit on world writable ones +- name: 6.1.2 - Ensure sticky bit is set on world-writeable directories + ansible.builtin.shell: > + set -o pipefail ; + /usr/bin/df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null | + xargs -I '{}' chmod a+t '{}' + changed_when: false + tags: + - 6.1.2 + +- name: 6.1.[3,5,7,9] - Ensure permissions on /etc/passwd /etc/group + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0644 + loop: + - passwd + - passwd- + - group + - group- + tags: + - 6.1.3 + - 6.1.5 + - 6.1.7 + - 6.1.9 + +- name: 6.1.[4,6,8,10] - Ensure permissions on /etc/shadow /etc/gshadow + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + loop: + - shadow + - shadow- + - gshadow + - gshadow- + tags: + - 6.1.4 + - 6.1.6 + - 6.1.8 + - 6.1.10 + +# Control 6.1.11, Ensure no world writable files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.11 - Ensure no world writable files exist + tags: + - 6.1.11 + block: + - name: 6.1.11 - Find any world writiable files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" + register: ww_files + changed_when: false + check_mode: false + + - name: 6.1.11 - Print any world writable files found + ansible.builtin.debug: + msg: "World writiable files found: {{ ww_files.stdout }}" + changed_when: true + when: ww_files.stdout + +# Control 6.1.12, Ensure no unowned files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.12 - Ensure no unowned files exist + tags: + - 6.1.12 + block: + - name: 6.1.12 - Find any unowned files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" + register: uo_files + changed_when: false + check_mode: false + + - name: 6.1.12 - Print any unowned files found + ansible.builtin.debug: + msg: "unowned files found: {{ uo_files.stdout }}" + changed_when: true + when: uo_files.stdout + +- name: 6.1.12 - Ensure no ungrouped files exist + tags: + - 6.1.12 + block: + - name: 6.1.13 - Find any ungrouped files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" + register: ug_files + changed_when: false + check_mode: false + + - name: 6.1.13 - Print any ungrouped files found + ansible.builtin.debug: + msg: "ungrouped files found: {{ uo_files.stdout }}" + changed_when: true + when: ug_files.stdout + + +# Control 6.1.14, Audit SUID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +# Control 6.1.15, Audit SGID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +- name: 6.2.1 - Ensure accounts in /etc/passwd use shadowed passwords + tags: + - 6.2.1 + block: + - name: 6.2.1 - Check to see if there are any accounts with empty passwords + ansible.builtin.shell: "/usr/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" + changed_when: false + register: empty_passwords + check_mode: false + + - name: 6.2.1 - Report the named users to the report + ansible.builtin.debug: + msg: "The user {{ item }} has an empty password" + when: empty_passwords.stdout + changed_when: true + loop: "{{ empty_passwords.stdout_lines }}" + +- name: 6.2.2 - Ensure all groups in /etc/passwd exist in /etc/group + tags: + - 6.2.2 + block: + - name: 6.2.2 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/undefined_groups.sh + register: undefined_groups + changed_when: false + check_mode: false + + - name: 6.2.2 - Report to user any unreferenced groups + ansible.builtin.debug: + msg: "{{ undefined_groups.stdout_lines }}" + changed_when: true + when: undefined_groups.stdout + +- name: 6.2.3 - Report on duplicate UIDs in /etc/passwd + tags: + - 6.2.3 + block: + - name: 6.2.3 - Use script to pull the list of duplicate UIDs + ansible.builtin.script: + cmd: files/duplicate_uids.sh + register: duplicate_uids + changed_when: false + check_mode: false + + - name: 6.2.3 - Print report of duplicated UIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_uids.stdout_lines }}" + changed_when: true + when: duplicate_uids.stdout + +- name: 6.2.4 - Report on duplicate GIDs in /etc/group + tags: + - 6.2.4 + block: + - name: 6.2.4 - Use script to pull the list of duplicate GIDs + ansible.builtin.script: + cmd: files/duplicate_guids.sh + register: duplicate_guids + changed_when: false + check_mode: false + + - name: 6.2.4 - Print report of duplcate GIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_guids.stdout_lines }}" + changed_when: true + when: duplicate_guids.stdout + +- name: 6.2.5 - Report on duplicate users in /etc/passwd + tags: + - 6.2.5 + block: + - name: 6.2.5 - Use script to pull the list of users + ansible.builtin.script: + cmd: files/duplicate_users.sh + register: duplicate_users + changed_when: false + check_mode: false + + - name: 6.2.5 - Print report of duplicate users to user + ansible.builtin.debug: + msg: "{{ duplicate_users.stdout_lines }}" + changed_when: true + when: duplicate_users.stdout + +- name: 6.2.6 - Report on duplicate groups in /etc/group + tags: + - 6.2.6 + block: + - name: 6.2.6 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/duplicate_groups.sh + register: duplicate_groups + changed_when: false + check_mode: false + + - name: 6.2.6 - Print report of duplicate groups to user + ansible.builtin.debug: + msg: "{{ duplicate_groups.stdout_lines }}" + changed_when: true + when: duplicate_groups.stdout + +- name: 6.2.7 - Ensure root PATH integrity + tags: + - 6.2.7 + block: + - name: 6.2.7 - Run script on path variable + ansible.builtin.script: files/path_check.sh + changed_when: false + register: path_check + check_mode: false + + - name: 6.2.7 - Print report to user + ansible.builtin.debug: + msg: + - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" + - "that exist in root's path because of this. It should be run as root on the target machine manually." + - " {{ path_check.stdout }}" + when: path_check.stdout and not ansible_check_mode + +- name: 6.2.8 - Ensure root is the only UID 0 account + tags: + - 6.2.8 + block: + - name: 6.2.8 - find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc >= 2 + check_mode: false + + - name: 6.2.8 - Report on mulitple accounts with UID of 0 + ansible.builtin.debug: + msg: + - "Accounts with UID zero in addition to root" + - " {{ rootuid.stdout_lines }}" + changed_when: true + when: rootuid.stdout != 'root' + +- name: 6.2.9 - Report on users that do not have a home directory + tags: + - 6.2.9 + block: + - name: 6.2.9 - Use script to find the users + ansible.builtin.script: + cmd: files/non_existant_homedirs.sh + register: nohomedir + changed_when: false + + - name: 6.2.9 - Print report of users that do not have a home directory + ansible.builtin.debug: + msg: "{{ nohomedir.stdout_lines }}" + changed_when: true + when: nohomedir.stdout + +# Control 6.2.10 - Ensure users own their home directories is environment dependent, skipping +# Control 6.2.11 - Ensure users home dir permissions are set is environment dependent, skipping +# Control 6.2.12 - Ensure users dot files are not gorup or world writable is environment dependent, skipping +# Control 6.2.13 - Ensure users .netrc files are not gorup or world writable is environment dependent, skipping +# Control 6.2.14 - Ensure users .forward files are removed is environment dependent, skipping +# Control 6.2.15 - Ensure users .netrc files are removed is environment dependent, skipping +# Control 6.2.16 - Ensure users .rhosts files are removed is environment dependent, skipping diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml new file mode 100644 index 0000000..56c961d --- /dev/null +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -0,0 +1,3184 @@ +--- +# Task file for CIS Controls +# This file is commented to help view what Ansible Automation is doing +# and under what circumstances. + +# Some blocks below have tasks with tags and some without. Blocks of tasks that +# contain multiple controls have tasks with tags. Blocks that consist of a +# single control and are just put together for convience sake, do not have +# sub-block tasks with tags. + +# Comments about how the modules are used will become more infrequent as +# the file goes along to avoid repeating oneself. + +# Let the user know what version of the controls file is running +# Use a variable so it prints out the correct version. +- name: Print Header + ansible.builtin.debug: + msg: "CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" + +# Collect the packages installed on the system so we can check agains them later +- name: Collect package list + ansible.builtin.package_facts: + manager: auto + tags: + - always + +# Find the minimum UID of the machine for normal acocunts. This varies +# between machines and environments, so we pull it from the file it +# is supposed to exist in. +- name: Determine the Minimum UID for new, non-system, accounts + ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" + register: min_uid + changed_when: min_uid.rc == "2" + check_mode: false + tags: + - always + +# Update the system with security packages using the system's package manager +# Only update the system if the 'update_system' variable is set to true +- name: 1.9.0 - Ensure updated system + ansible.builtin.dnf: + name: "*" + state: latest + security: true + when: update_system + tags: + - 1.9.0 + +- name: 1.1.1.1 - disable squashfs + ansible.builtin.blockinfile: + path: /etc/modprobe.d/squashfs.conf + create: true + owner: root + group: root + mode: 644 + setype: modules_conf_t + block: | + install squashfs /bin/false + blacklist squashfs + tags: + - 1.1.1.1 + +- name: 1.1.1.2 - disable udf + ansible.builtin.blockinfile: + path: /etc/modprobe.d/udf.conf + create: true + owner: root + group: root + mode: 0644 + setype: modules_conf_t + block: | + install udf /bin/false + blacklist udf + tags: + - 1.1.1.2 + + +# Create and configure the local-fs systemd service file +- name: 1.1.2 - Ensure /tmp is configured + tags: + - 1.1.2 + block: + # Create a file to hold the system specific local-fs service information + # be sure to set the selinux security context. Even if selinux is disabled, + # it's a good idea to make sure it is set on files + - name: 1.1.2.[2-5] - Ensure the local-fs directory is created + ansible.builtin.file: + path: /etc/systemd/system/local-fs.target.wants + state: directory + owner: root + group: root + mode: 0755 + setype: etc_t + tags: + - 1.1.2.1 + - 1.1.2.2 + - 1.1.2.3 + - 1.1.2.4 + + # Add content to the file we created using the blockinfile command. + # Notify systemd to reload its daemons and start the local-fs service + - name: 1.1.[2-5] - Configure config file for tmpfs + ansible.builtin.blockinfile: + path: /etc/systemd/system/local-fs.target.wants/tmp.mount + block: | + [Mount] + What=tmpfs + Where=/tmp + Type=tmpfs + Options=mode=1777,strictatime,noexec,nodev,nosuid + create: true + owner: root + group: root + mode: 0644 + notify: Restart tmpfs + tags: + - 1.1.2.1 + - 1.1.2.2 + - 1.1.2.3 + - 1.1.2.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.3 - Configure /var + tags: + - 1.1.3 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.3 - Set/reset /var mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.3.1 - Determine if /var is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var" + with_items: + - "{{ ansible_mounts }}" + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.3.1 - Report to user if /var is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.3.1 + + - name: 1.1.3.[2-3] - /var mount option controls + tags: + - 1.1.3.2 + - 1.1.3.3 + when: mount_count != 0 + block: + - name: 1.1.3.2 - Report to user if /var does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.3.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.3.3 Report to user if /var does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.3.3 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.4.1 - Configure /var/tmp + tags: + - 1.1.4 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.4 - Set/reset /var/tmp mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.4.1 - Determine if /var/tmp is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/tmp" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.4.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.4.1 - Report to user if /var/tmp not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.4.1 + + - name: 1.1.4.[2-4] - /var/tmp mount option controls + tags: + - 1.1.4.2 + - 1.1.4.3 + - 1.1.4.4 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.2 - Report to user if /var/tmp does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options + changed_when: true + tags: + - 1.1.4.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.3 - Report to user if /var/tmp does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.4.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.4 - Report to user if /var/tmp does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.4.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.5 - Configure /var/log + tags: + - 1.1.5 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.5 - Set/reset /var/log mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.5.1 - Determine if /var/log is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/log" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.5.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.5.1 - Report to user if /var/log is not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.5.1 + + - name: 1.1.5.[2-3] - /var/log mount option controls + tags: + - 1.1.5.2 + - 1.1.5.3 + when: mount_count != 0 + block: + - name: 1.1.5.2 - Report to user if /var/log does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.5.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.5.3 - Report to user if /var/log does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.5.3 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.6 - Configure /var/log/audit + tags: + - 1.1.6 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.6 - Set/reset /var/log/audit mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.6.1 - Determine if /var/log/audit is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/log/audit" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.6.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.6.1 - Report to user if /var/log/audit is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.6.1 + + - name: 1.1.6.[2-4] - /var/log/audit mount option controls + tags: + - 1.1.5.2 + - 1.1.5.3 + - 1.1.5.4 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.2 - Report to user if /var/log/audit does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options + changed_when: true + tags: + - 1.1.6.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.3 - Report to user if /var/log/audit does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.6.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.6.4 - Report to user if /var/log/audit does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.6.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.7 - Configure /home + tags: + - 1.1.7 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.7 - Set/reset /home mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.7.1 - Determine if /home is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/home" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.7.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.7.1 - Report to user if /home is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.7.1 + + - name: 1.1.7.[2-3] - /home mount option controls + tags: + - 1.1.7.2 + - 1.1.7.3 + when: mount_count != 0 + block: + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.7.2 - Report to user if /home does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options + changed_when: true + tags: + - 1.1.7.2 + + - name: 1.1.7.3 Report to user if /home does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options + changed_when: true + tags: + - 1.1.7.3 + +# /dev/shm does not exist in ansible_mounts so we have to check the +# mount command directly. This requires the use of the shell command which +# is not ideal. +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8 - Configure /dev/shm + tags: + - 1.1.8 + block: + - name: Determine if /dev/shm has nodev set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nodev + register: devshm_nodev_out + failed_when: devshm_nodev_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.8.2 - Report to user if /dev/shm does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nodev set" + when: devshm_nodev_out is defined and devshm_nodev_out.stdout + changed_when: true + tags: + - 1.1.8.2 + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8.4 - Report if /dev/shm does not have nosuid set + tags: + - 1.1.8.4 + block: + - name: Determine if /dev/shm has nosuid set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v nosuid + register: devshm_nosuid_out + failed_when: devshm_nosuid_out == "2" + changed_when: false + check_mode: false + +- name: 1.1.8.3 - Report if /dev/shm does not have noexec set + tags: + - 1.1.8.3 + block: + - name: 1.1.8.3 - Determine if /dev/shm has noexec set + ansible.builtin.shell: cat /proc/mounts | /usr/bin/grep /dev/shm | /usr/bin/grep -v noexec + register: devshm_noexec_out + failed_when: devshm_noexec_out == "2" + changed_when: false + check_mode: false + tags: + - 1.1.8.3 + +# Let the user know if we did not find the option set. + - name: 1.1.8.3 - Report to user if /dev/shm does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have noexec set" + when: devshm_noexec_out is defined and devshm_noexec_out.stdout + changed_when: true + tags: + - 1.1.8.3 +# Let the user know if we did not find the option set. + - name: 1.1.8.2 - Report to user about /dev/shm + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nosuid set" + when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout + changed_when: true + +# Control 1.1.9 is for removable media + +# Turn off and disable the autofs service using the service module. +# We check to see if the package that autofs belongs to (convienently called autofs) +# exists in the ansible_facts.packages list we gathered early in the play +- name: 1.1.22 - disable automounting + ansible.builtin.systemd: + name: autofs + enabled: false + state: stopped + masked: true + when: "'autofs' in ansible_facts.packages" + tags: + - 1.1.22 + +- name: 1.1.23 - Disable USB storage module + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install usb-storage /bin/true" + state: present + create: true + owner: root + group: root + mode: 0644 + tags: + - 1.1.23 + +# Control 1.2.1 is for checking GPG keys. The control just says that each repo should have the correct key + +# GPGKeys are used to sign packages. enabling them will mean that all packages +# from a given repo must be signed with the appropriate key +- name: 1.2.[3,4] - Ensure GPG keys are configured + tags: + - 1.2.3 + block: + # Replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.2 - set master dnf.conf gpgcheck to '1' + ansible.builtin.replace: + dest: /etc/dnf/dnf.conf + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: "gpgcheck=1" + when: gpgcheck and ansible_distribution == "Fedora" + + # 1.2.4 - Ensure repo_gpgcheck is globally activated doesn't work on all repos so skipped. Environment dependant + + # Find all files in /etc/yum.repos.d and add them to a list variable + - name: 1.2.4 - find all repo files in /etc/dnf.repos.d/ + ansible.builtin.find: + paths: "/etc/dnf.repos.d" + patterns: "*.repo" + register: dnfrepos + when: gpgcheck is defined and gpgcheck + + # parse the list variable and replace any instances of gpgcheck with a 1 after it to 'gpgcheck = 1' + - name: 1.2.4 - Set all repos gpgchecks to '1' + ansible.builtin.replace: + dest: "{{ item.path }}" + regexp: '^gpgcheck\s*=\s*[^1]*$' + replace: gpgcheck = 1 + with_items: "{{ dnfrepos.files }}" + when: gpgcheck is defined and gpgcheck + +# This is out of order, but sudo is configured a lot through the other controls +- name: 5.3.1 - Ensure sudo is installed + ansible.builtin.dnf: + name: sudo + state: present + tags: + - 5.3.1 + +# AIDE is a file system integrity checker which will document all +# filesystem changes. It's very noisy on busy systems and should be +# enabled when you have the sapce and need for it. +- name: 1.3 - Filesystem integrity checking w/AIDE + tags: + - 1.3.0 + block: + # use the system package manager to install AIDE + - name: 1.3.1 - Ensure aide is installed + ansible.builtin.package: + name: aide + state: present + tags: + - 1.3.1 + + # AIDE requires initialization the first time and it takes time on a large system. + # DUse stat module on the file that should be there if it is set up. + - name: 1.3.1 - Determine if AIDE has already been initialized + ansible.builtin.stat: + path: /var/lib/aide/aide.db.gz + register: aide_path + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database=file:((?!{{ aide_db_name }}).)*$" + replace: "database=file:{{ aide_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database_out file location + ansible.builtin.replace: + dest: /etc/aide.conf + regexp: "^database_out=file:((?!{{ aide_new_db_name }}).)*$" + replace: "database_out=file:{{ aide_new_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - enable gzip compression for database + ansible.builtin.lineinfile: + dest: /etc/aide.conf + regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' + line: "gzip_dbout={{ aide_gzip }}" + state: present + tags: + - 1.3.1 + + # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' + # is true if the file is a regular file. If either of these are not true, then + # run the initializatoin again. + - name: 1.3.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aide) + ansible.builtin.command: /usr/sbin/aide --init + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + register: aide + async: 1200 # 20 minutes until timeout + poll: 0 # run concurrently + tags: + - 1.3.1 + + - name: 1.3.1 - Wait for AIDE initialization to complete + ansible.builtin.async_status: + jid: "{{ aide.ansible_job_id }}" + register: aide_status + until: aide_status.finished + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + retries: 300 + tags: + - 1.3.1 + + # AIDE creates the new database as a different name. Use the copy module with + # the remote_src argument to copy the file on the remote machine to another location + # on the remote machine. + - name: 1.3.1 - Move the newly created database into place + ansible.builtin.copy: + src: /var/lib/aide/aide.db.new.gz + remote_src: true + dest: /var/lib/aide/aide.db.gz + mode: preserve + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) and ansible_distribution != "SLES" + changed_when: false + tags: + - 1.3.1 + + # Copy in the already configured systemd service file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the service + - name: 1.3.2 - Ensure File integrity is regularly checked (aidecheck service) + tags: + - 1.3.2 + notify: Restart aidecheck + block: + - name: 1.3.2 - Template in aidecheck.service file + ansible.builtin.template: + src: aidecheck.service + dest: /etc/systemd/system/aidecheck.service + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + + - name: 1.3.2 - Enable aidecheck.service + ansible.builtin.systemd: + name: aidecheck.service + enabled: true + + # Copy in the already configured systemd timer file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the timer + - name: 1.3.2 - Ensure File integrity is regulary checked (aidecheck timer) + ansible.builtin.template: + src: aidecheck.timer + dest: /etc/systemd/system/aidecheck.timer + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + notify: Restart aidecheck + + - name: 1.3.2 - Enable aidecheck.timer + ansible.builtin.systemd: + name: aidecheck.timer + enabled: true + + - name: 1.3.3 Create aidecheck config.d dir + ansible.builtin.file: + path: /etc/aide.conf.d/ + owner: root + group: root + mode: 0644 + state: directory + tags: + - 1.3.3 + + - name: 1.3.3 Ensure cryptographic mechanisms are used to protect the integrity of audit tools + ansible.builtin.blockinfile: + path: /etc/aide.conf.d/crypt.conf + owner: root + group: root + create: true + mode: 0644 + block: | + # Audit Tools + /sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 + tags: + - 1.3.3 + +# 1.4 Secure Boot settings + +# EFI uses grub.cfg file in the LILO location now. So the extra checks have been removed +- name: 1.4.0 - Check if the LILO path exists + ansible.builtin.stat: + path: "/boot/grub2/grub.cfg" + register: grubdir + tags: + 1.4.0 + +- name: 1.4.0 - Check if the grub user.cfg exists + ansible.builtin.stat: + path: "/boot/grub2/user.cfg" + register: usercfgdir + tags: + 1.4.0 + +# Control 1.4.1, Grub bootloader password - skipped + +# Use file module to set permissions on grub files +- name: 1.4.2 - Set permissions on grub.cfg, grubenv + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - "{{ grubdir.stat.path }}" + - /boot/grub2/grubenv + tags: + - 1.4.2 + +- name: 1.4.3 - Require authorization to enter Rescue mode + tags: + - 1.4.3 + block: + - name: 1.4.3 - Require authorization to enter Rescue mode - create dir + ansible.builtin.file: + dest: "/etc/systemd/system/rescue.service.d/" + state: directory + owner: root + group: root + mode: 0755 + + - name: 1.4.3 - Require authorization to enter Rescue mode - set file + ansible.builtin.copy: + dest: "/etc/systemd/system/rescue.service.d/00-require-auth.conf" + owner: root + group: root + mode: 0644 + setype: etc_t + content: | + [Service] + ExecStart=-/usr/lib/systemd/systemd-sulogin-shell rescue + +# 1.5 Additional Process Hardening + +- name: 1.5.1 - Ensure core dump storage is disabled + ansible.builtin.blockinfile: + path: /etc/systemd/coredump.conf + create: true + owner: root + group: root + mode: 0644 + block: | + Storage=none + state: present + marker: "# {mark} Ansible managed Storage setting" + tags: + - 1.5.1 + +- name: 1.5.2 - Ensure core dump backtraces are disabled + ansible.builtin.blockinfile: + path: /etc/systemd/coredump.conf + create: true + owner: root + group: root + mode: 0644 + block: | + ProcessSizeMax=0 + state: present + marker: "# {mark} Ansible managed ProcessSize setting" + tags: + - 1.5.2 + +- name: 1.5.3 - Ensure address space layout reandomization (ASLR) is enabled + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'true'. + ansible.posix.sysctl: + name: kernel.randomize_va_space + value: "2" + reload: true + state: present + sysctl_set: true + tags: + - 1.5.3 + +# Use system package manager to remove +- name: 1.6.1.1 - Ensure SELinux is installed + ansible.builtin.dnf: + name: + - libselinux + - python3-libselinux + state: present + register: selinux_installed + when: selinux is defined and selinux | lower != "disabled" + tags: + - 1.6.1.1 + +# re-gather system facts in case we installed selinux packages. +# If selinux wasn't installed, it will not populate ansible_selinux fact correctly, regathering +# will pull it with the right information +- name: 1.6.1.1 - Regather facts if installed selinux package + ansible.builtin.setup: + when: selinux_installed.changed + tags: + - 1.6.1.1 + +# Use the replace module to remove any disablment of selinux in grub if +# it isn't expressly disabled from a variable +- name: 1.6.1.2 - Ensure SELinux is not disabled in bootloader configuration + ansible.builtin.replace: + dest: /etc/default/grub + regexp: "{{ item }}" + replace: "" + with_items: + - selinux=0 + - enforcing=0 + when: selinux is defined and selinux | lower != "disabled" + notify: Rebuild grub + tags: + - 1.6.1.2 + +# Replace the current selinux policy with whatever the variable is set for +- name: 1.6.1.3 - Set SELinux policy to {{ selinux_policy }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" + replace: "SELINUXTYPE={{ selinux_policy }}" + when: ( selinux is defined and selinux_policy is defined ) and selinux | lower != "disabled" + tags: + - 1.6.1.3 + +# If we are going to be enabling selinux in passive or enforcing mode, +# set the autorelabel and notify the machine to reboot +- name: 1.6.1.3 - If disabled and we are enabling it, autorelabel + ansible.builtin.file: + path: /.autorelabel + owner: root + group: root + mode: 0644 + state: touch + when: ( ansible_selinux.status == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed + notify: Reboot + tags: + - 1.6.1.3 + +# Replace the current selinux mode with what the variable is set to +- name: 1.6.1.[4-5] - Set SELinux to {{ selinux | lower }} + ansible.builtin.replace: + dest: /etc/selinux/config + regexp: "^SELINUX=((?!{{ selinux }}).)*$" + replace: "SELINUX={{ selinux | lower }}" + when: selinux is defined and ( selinux | lower == "enforcing" or selinux | lower == "permissive" or selinux | lower == "disabled" ) + notify: Reboot + tags: + - 1.6.1.4 + - 1.6.1.5 + +# Let the user know if there are any processes that are not running under the +# a selinux context +- name: 1.6.1.6 - Report on unconfined running services + tags: + - 1.6.1.6 + when: ansible_selinux.status != "disabled" + block: + # In RHEL8, all unconfined services run under their own context + - name: 1.6.1.6 - Generate report on unconfined running services + ansible.builtin.shell: /usr/bin/ps -eZ | /usr/bin/grep unconfined_service_t + register: unconfined_services_out + failed_when: unconfined_services_out.rc == "2" + changed_when: false + check_mode: false + + # Print any findings to the user + - name: 1.6.1.6 - Report on unconfined running services to user + ansible.builtin.debug: + msg: + - "Unconfined processes found:" + - "{{ unconfined_services_out.stdout_lines }}" + changed_when: true + when: unconfined_services_out.stdout + +# Use system package manager to remove package +- name: 1.6.1.7 - Remove setroubleshoot + ansible.builtin.dnf: + name: setroubleshoot + state: absent + tags: + - 1.6.1.7 + +- name: 1.6.1.8 - Remove MCS Translation Service + ansible.builtin.dnf: + name: mcstrans + state: absent + tags: + - 1.6.1.8 + +# 1.7 Warning Banners + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1 - Install motd banners + ansible.builtin.copy: + src: "{{ motd_file }}" + dest: /etc/motd + owner: root + group: root + mode: 0644 + when: motd_use is defined and motd_use + tags: + - 1.7.1 + - 1.7.4 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.2 - Install issue banners + ansible.builtin.copy: + src: "{{ issue_file }}" + dest: /etc/issue + owner: root + group: root + mode: 0644 + when: issue_use is defined and issue_use + tags: + - 1.7.2 + - 1.7.5 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.3 - Install issue.net banners + ansible.builtin.copy: + src: "{{ issue_net_file }}" + dest: /etc/issue.net + owner: root + group: root + mode: 0644 + when: issue_net_use is defined and issue_net_use + tags: + - 1.7.3 + - 1.7.6 + +# 1.8 GDM + +# Disable GDM +- name: 1.8.1 - disable display manager if graphical desktop not needed + tags: + - 1.8.1 + block: + # Find the current default run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 1.8.1 - get default runlevel + ansible.builtin.stat: + path: /etc/systemd/system/default.target + register: default_runlevel_out + tags: + - 1.8.1 + + # Set the current run level. The systemctl module does not handle the + # get-default routine, so we are looking at the target of the symlink at /etc/systemd/system/default.target + - name: 1.8.1 - Set current runlevel (non graphical) + ansible.builtin.command: /usr/bin/systemctl isolate multi-user.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface + tags: + - 1.8.1 + + - name: 1.8.1 - Set current runlevel (graphical) + ansible.builtin.command: /usr/bin/systemctl isolate graphical.target + register: isolate_out + changed_when: isolate_out.changed + when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface + tags: + - 1.8.1 + + # Set the default run level. We are doing it the hard way since systemctl doesn't handle set-default + - name: 1.8.1 - Set default runlevel (non graphical) + ansible.builtin.file: + src: /lib/systemd/system/multi-user.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: not graphical_interface + + - name: 1.8.1 - Set default runlevel (graphical) + ansible.builtin.file: + src: /lib/systemd/system/graphical.target + dest: /etc/systemd/system/default.target + owner: root + group: root + when: graphical_interface + + - name: 1.8.1 - Remove the GNOME display manager + ansible.builtin.dnf: + name: gdm + state: absent + when: "'gdm' in ansible_facts.packages and not graphical_interface" + +# add a banner to the login screen if the graphical_interface variable is set to true +- name: 1.8.[2-3] Ensure GDM banner set up + when: graphical_inteface is defined and graphical_interface + tags: + - 1.8.2 + - 1.8.3 + - 1.8.4 + block: + - name: 1.8.[2-4] - Set up dconf profile for gdm + ansible.builtin.blockinfile: + path: /etc/dconf/profile/gdm + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + block: | + user-db:user + system-db:gdm + file-db:/usr/share/gdm/greeter-dconf-defaults + tags: + - 1.8.2 + - 1.8.3 + - 1.8.4 + + - name: 1.8.[2-3] - Create the defaults file and populate group + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + block: | + [org/gnome/login-screen] + tags: + - 1.8.2 + - 1.8.3 + + - name: 1.8.2 - Enable login screen for gdm + ansible.builtin.blockinfile: + # Add our required pieces to the greeter defaults file + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + block: | + banner-message-enable=true + banner-message-text='Authorized users only. All activity may be monitored and reported.' + tags: + - 1.8.2 + + - name: 1.8.3 Ensure GDM disable-user list is enabled + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + block: | + disable-user-list=true + tags: + - 1.8.3 + + - name: 1.8.4 Set gdm timeouts + ansible.builtin.blockinfile: + path: /etc/dconf/db/distro.d/00-screensaver + owner: root + group: root + mode: 0644 + setype: etc_t + block: | + # Specify the dconf path + [org/gnome/desktop/session] + + # Number of seconds of inactivity before the screen goes blank + # Set to 0 seconds if you want to deactivate the screensaver. + idle-delay=uint32 {{ idle_delay }} + # Specify the dconf path + [org/gnome/desktop/screensaver] + # Number of seconds after the screen is blank before locking the screen + lock-delay=uint32 {{ lock_delay }} + tags: + - 1.8.4 + +# 1.8.5 TODO +# 1.8.6 TODO +# 1.8.7 TODO +# 1.8.8 TODO +# 1.8.9 TODO +# 1.8.10 - Ensure XDCMP is not enabled, skipping + +# 1.10 Configure crypto policy +- name: 1.10.0 - Configure crypto-policy + tags: + - 1.10.0 + block: + - name: 1.10.0 - Display error if crypto variable violates policy + ansible.builtin.debug: + msg: + - "crypto_policy is set to: {{ crypto_policy }}. Which is not a valid selection." + - "Valid choices are DEFAULT, FUTURE, and FIPS." + - "LEGACY selection does not satisfy the control requirement" + - "Refusing to update crypto_policy information" + when: crypto_policy is defined and ( crypto_policy != "DEFAULT" and crypto_policy != "FUTURE" and crypto_policy != "FIPS" ) + + - name: 1.10.0 - Set crypto-policy to {{ crypto_policy | upper | default('DEFAULT', true) }} + ansible.builtin.lineinfile: + path: /etc/crypto-policies/config + regexp: "^(LEGACY|FUTURE|FIPS|DEFAULT)" + line: "{{ crypto_policy | upper | default('DEFAULT', true) }}" + notify: Update crypto_policy + + - name: 1.10.0 - Check to see if FIPS mode is already set up if crypto_policy == "FIPS" + ansible.builtin.command: /usr/sbin/fips-mode-setup --is-enabled + register: fips_mode + when: crypto_policy is defined and crypto_policy == "FIPS" + failed_when: false + changed_when: false + + - name: 1.10.0 - Enabling FIPS mode if crypt_policy set to FIPS + ansible.builtin.command: /usr/bin/fips-mode-setup --enable + when: ( crypto_policy is defined and crypto_policy == "FIPS") and fips_mode.rc == "2" + +# 2 Services + +- name: 2.1.1 - Verify chrony is installed + ansible.builtin.dnf: + name: "chrony" + state: present + tags: + - 2.1.1 + +# Use the template module to deploy the config file for the time sync program +# The default file does not have any template variables, but it's there so +# they can be added in the future. +# Control also sets the user to chrony, but it is already default in RHEL9 +- name: 2.1.2 - Configure chrony + ansible.builtin.template: + src: "chrony.conf" + dest: /etc/chrony.conf + owner: root + group: root + mode: 0644 + notify: Restart chronyd + tags: + - 2.1.2 + +# 2.2 Special Purpose Services +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: 2.2.1 - Create empty list for unneeded packages + ansible.builtin.set_fact: + unneeded_packages: [] + +- name: 2.2.1 - Remove xorg-x11-server-common + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'xorg-x11-server-common' ] }}" + tags: + - 2.2.1 + +- name: 2.2.2 - Remove avahi; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'avahi' ] }}" + tags: + - 2.2.2 + +- name: 2.2.4 - Remove dhcp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'dhcp' ] }}" + when: dhcp_server is defined and not dhcp_server + tags: + - 2.2.4 + +- name: 2.2.5 - Remove bind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'bind' ] + [ 'unbound' ] }}" + when: dns_server is defined and not dns_server + tags: + - 2.2.5 + +- name: 2.2.6 - Remove vsftpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'vsftpd' ] }}" + when: ftp_server is defined and not ftp_server + tags: + - 2.2.6 + +- name: 2.2.7 - Remove tftp-server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'tftp-server' ] }}" + when: tftp_server is defined and not tftp_server + tags: + - 2.2.7 + +- name: 2.2.8 - Remove httpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'httpd' ] + [ 'httpd-tools' ] + [ 'mod_ssl' ] }}" + when: http_server is defined and not http_server + tags: + - 2.2.8 + +- name: 2.2.9 - Remove dovecot and cyrus-imapd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'dovecot' ] + [ 'cyrus-imapd'] }}" + when: email_server is defined and not email_server + tags: + - 2.2.9 + +- name: 2.2.10 - Remove samba; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" + when: smb_server is defined and not smb_server + tags: + - 2.2.10 + +- name: 2.2.11 - Remove squid; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" + tags: + - 2.2.11 + +- name: 2.2.12 - Remove snmp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'net-snmp' ] + [ 'net-snmp-libs'] }}" + tags: + - 2.2.12 + +- name: 2.2.13 - Remove telnet-server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'telnet-server' ] }}" + tags: + - 2.2.13 + +- name: 2.2.14 - Remove dnsmasq; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'dnsmasq' ] }}" + tags: + - 2.2.14 + +- name: 2.2.16 - Remove nfs server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'nfs-utils' ] }}" + when: nfs_server is defined and not nfs_server + tags: + - 2.2.16 + +- name: 2.2.17 - Remove rpcbind; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'rpcbind' ] }}" + tags: + - 2.2.17 + +- name: 2.2.18 - Remove rsync-daemon; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'rsync-daemon' ] }}" + tags: + - 2.2.18 + +- name: 2.3.1 - Remove telnet; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'telnet' ] }}" + tags: + - 2.3.1 + +- name: 2.3.2 - Remove openldap-clients; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'openldap-clients' ] }}" + tags: + - 2.3.2 + +- name: 2.3.3 - Remove tftp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'tftp' ] }}" + tags: + - 2.3.3 + +- name: 2.3.4 - Remove ftp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'ftp' ] }}" + tags: + - 2.3.4 + +- name: 2.3 - list of packages to remove + ansible.builtin.debug: + var: unneeded_packages + tags: + - 2.3.0 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: 2.3 - Process removal list + ansible.builtin.dnf: + name: "{{ unneeded_packages }}" + state: absent + tags: + - 2.2.0 + - 2.3.0 + +# Cups should be remove per control 2.2.16, but it may not be able to due to +# dependencies, so disable the service instead +- name: 2.2.3 - Disable cups as we my not be able to uninstall it + ansible.builtin.service: + name: "{{ item }}" + enabled: false + state: stopped + when: "'cups' in ansible_facts.packages" + loop: + - cups.service + - cups.socket + - cups-browsed.service + tags: + - 2.2.3 + +# Use the stat module to determine if the mail server config file exists. +# If it does and we are to be a mail server, then modify it per the control. +- name: 2.2.15 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) + tags: + - 2.2.15 + block: + - name: 2.2.15 - Find if we have a mail agent config file + ansible.builtin.stat: + path: /etc/postfix/main.cf + register: postfix_out + changed_when: false + + - name: 2.2.15 - If the file exists and not a mail server, then set loopback only + ansible.builtin.replace: + dest: /etc/postfix/main.cf + regexp: "^inet_interfaces = ((?!localhost).)*$" + replace: "inet_interfaces = loopback-only" + when: postfix_out.stat.exists and not email_server + notify: Restart postfix + +# Control 2.4 is a manual control, skipping + +# Section 3, Network parameters +# +# Control 3.1.1 Report on IPv6 status skipped +# Control 3.1.2 Ensure wireless interfaces are disabled is interface dependent +# skipping + +# IPv4 network parameters +- name: 3.2.0 - Create empty dictionary for unneeded IPv4 network parameters + ansible.builtin.set_fact: + unneeded_ipv4_network: {} + +- name: 3.2.0 - Create empty dictionary for unneeded IPv6 network parameters + ansible.builtin.set_fact: + unneeded_ipv6_network: {} + +- name: 3.2.1 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.ip_forward' : '0'}) }}" + tags: + - 3.2.1 + +- name: 3.2.1 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ 'net.ipv6.conf.all.forwarding' : '0'}) }}" + when: not ipv6_disable + tags: + - 3.2.1 + +- name: 3.2.2 - Ensure packet redirect sending is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.send_redirects + - net.ipv4.conf.default.send_redirects + tags: + - 3.2.2 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.accept_source_route + - net.ipv4.conf.default.accept_source_route + tags: + - 3.3.1 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + loop: + - net.ipv6.conf.all.accept_source_route + - net.ipv6.conf.default.accept_source_route + when: not ipv6_disable + tags: + - 3.3.1 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.accept_redirects + - net.ipv4.conf.default.accept_redirects + tags: + - 3.3.2 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + loop: + - net.ipv6.conf.all.accept_redirects + - net.ipv6.conf.default.accept_redirects + when: not ipv6_disable + tags: + - 3.3.2 + +- name: 3.3.3 - Ensure secure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.secure_redirects + - net.ipv4.conf.default.secure_redirects + tags: + - 3.3.3 + +- name: 3.3.4 - Ensure suspicious packets are logged + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '1' }) }}" + loop: + - net.ipv4.conf.all.log_martians + - net.ipv4.conf.default.log_martians + tags: + - 3.3.4 + +- name: 3.3.5 - Ensure broadcast ICMP requests are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.icmp_echo_ignore_broadcasts' : '1' }) }}" + tags: + - 3.3.5 + +- name: 3.3.6 - Ensure bogus ICMP responses are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.icmp_ignore_bogus_error_responses' : '1' }) }}" + tags: + - 3.3.6 + +- name: 3.3.7 - Ensure reverse path filtering is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '1' }) }}" + loop: + - net.ipv4.conf.all.rp_filter + - net.ipv4.conf.default.rp_filter + tags: + - 3.3.7 + +- name: 3.3.8 - Ensure TCP SYN Cookies is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.tcp_syncookies' : '1' }) }}" + tags: + - 3.3.8 + +- name: 3.3.9 - Ensure IPv6 router advertisements are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + when: not ipv6_disable + loop: + - net.ipv6.conf.all.accept_ra + - net.ipv6.conf.default.accept_ra + tags: + - 3.3.9 + +- name: 3.3 - list of IPv4 network settings + ansible.builtin.debug: + var: unneeded_ipv4_network + +- name: 3.3 - list of IPv6 network settings + ansible.builtin.debug: + var: unneeded_ipv6_network + +# The sysctl module will configure certain sysctl parameters. They are +# collected into a loop here to speed the implementation +# Once complete, notify the system to flush the network routes +- name: 3.3 - Process unneeded network settings for IPv4 + tags: + - 3.3.0 + block: + - name: 3.3 - Set networking parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + reload: true + state: present + sysctl_set: true + loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv4_network) }}" + notify: Flush network routes + +- name: 3.3 - Process unneeded network settings for IPv6 + tags: + - 3.3.0 + block: + - name: 3.3 - Set networking parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + reload: true + state: present + sysctl_set: true + loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv6_network) }}" + notify: Flush network routes + +- name: 3.1 - Disable uncommon network protocols + tags: + - 3.1.1 + block: + # This collection of tasks creates a empty list and save it as a fact. + # For every item that is encountered (without the tag being skipped), + # add a string to the list. + + - name: 3.1.3 - Disable TIPC + ansible.builtin.blockinfile: + path: /etc/modprobe.d/tipc.conf + create: true + block: | + install udf /bin/false + blacklist udf + tags: + - 3.1.3 +# Section 3 - Firewall +# iptables is deprecated and not covered under the CIS controls for RHEL 9 + +- name: 3.4.1.1 - ensure nftables is installed + ansible.builtin.dnf: + name: nftables + state: present + tags: + - 3.4.1 + +- name: 3.4.1.1 - Install firewalld + ansible.builtin.dnf: + name: "firewalld" + state: present + when: enable_firewall is defined and enable_firewall == "firewalld" + notify: Start firewalld # 3.4.2.1 + + +- name: 3.4.1.1 - Disable netfilters service + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: enable_firewall is defined and enable_firewall == "firewalld" + +- name: 3.4.1.1 - Disable firewalld service + ansible.builtin.systemd: + name: firewalld + state: stopped + enabled: false + masked: true + when: enable_firewall is defined and enable_firewall == "firewalld" + +- name: 3.4.2.1 - Set default zone + ansible.builtin.lineinfile: + path: "/etc/firewalld/firewalld.conf" + regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' + line: "DefaultZone={{ firewalld_default_zone }}" + when: enable_firewall is defined and enable_firewall == "firewalld" and firewalld_default_zone is defined + notify: Restart firewalld + +- name: 3.4.2.2 - Ensure netfilters has at least one table + when: enable_firewall is defined and enable_firewall == "nftables" + block: + - name: 3.4.2.2 - Find any current netfilter tables + ansible.builtin.command: nft list tables + register: tables_list + changed_when: false + + - name: 3.4.2.2 - Create a basic table if none exist + ansible.builtin.command: nft create table inet firewalld NFTables + when: not tables_list + tags: + - 3.4.2.2 + +# Control 3.4.2.[3-7] is not set as it is very machine dependant + +- name: Notify users to configure the firewall + ansible.builtin.debug: + msg: + - "3.4.2 - Firewall must be configured locally" + tags: + - 3.4.2 + +# Section 4 - Logging and Auditing + +- name: 4.1 Install and configure system auditing + when: enable_audit is defined and enable_audit + tags: + - 4.1.0 + block: + - name: 4.1.1.1 - Install Auditd + ansible.builtin.dnf: + name: + - audit + - audit-libs + state: present + tags: + - 4.1.1.1 + + - name: 4.1.1.2 - Ensure auditing for processes that start prior to auditd + # We check here because we don't know what position the audit=1 is in + # order to simply do the replace, so we are instead looking for the match in the file first. + # If it doesn't exist, then we can just insert it + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' + state: absent + check_mode: true + changed_when: false + register: audit_exist + failed_when: false + tags: + - 4.1.1.2 + + # use the replace module to add it to grub bootloader and then notify + # grub to rebuild + - name: 4.1.1.2 - enable audit service in grub + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit=1 ' + notify: Rebuild grub + when: not audit_exist.found + tags: + - 4.1.1.2 + + - name: 4.1.1.3 - Ensure audit_backlog_limit is sufficient, check if limit exists + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit_backlog_limit' + state: absent + check_mode: true + changed_when: false + register: audit_backlog_exist + failed_when: false + tags: + - 4.1.1.3 + + - name: 4.1.1.3 - Ensure audit_backlog_limit is sufficient, add audit_backlog_limit to grub + ansible.builtin.replace: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' + notify: Rebuild grub + when: not audit_backlog_exist.found + tags: + - 4.1.1.3 + + - name: 4.1.1.3 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (check) + ansible.builtin.lineinfile: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX=.*{{ audit_backlog_limit }}' + state: absent + check_mode: true + changed_when: false + register: our_limit + when: audit_backlog_exist.found + tags: + - 4.1.1.3 + + - name: 4.1.1.3 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (fix) + ansible.builtin.replace: + dest: /etc/default/grub + regexp: 'audit_backlog_limit=[\S]*' + replace: 'audit_backlog_limit={{ audit_backlog_limit }}' + notify: Rebuild grub + when: audit_backlog_exist.found and not our_limit.found + tags: + - 4.1.1.3 + + # Control is out of order to allow configuration before startup + - name: 4.1.1.4 - Enable auditd service + ansible.builtin.service: + name: auditd + enabled: true + state: started + tags: + - 4.1.1.4 + + # The replace module here is looking through file and make replacements of partial lines + + - name: 4.1.2.[1-3] - Configure audit log storage size + ansible.builtin.replace: + path: /etc/audit/auditd.conf + regexp: "{{ item.find }}" + replace: "{{ item.replace }}" + loop: + - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.2.1 + - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.2.2 + - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.2.2 + - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.2.2 + - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.2.2 + - {find: '^log_file\s+=\s+[^{{ log_file }}]', replace: 'log_file = {{ log_file }}'} # Supports 4.1.4.1 + notify: Restart auditd + tags: + - 4.1.2.1 + - 4.1.2.2 + - 4.1.2.3 + + # For the next several checks, each one is in their own file, so we are using + # the copy module to place each file independently and then motifying + # a restart of auditd if anything changes. + + # cis-security versions before 1.5.0 did not enumerate the files, so the old files + # need to be removed to make way for the new versions + - name: 4.1.3 - Remove old rules files that were not in correct order (pre v1.5.0) + ansible.builtin.file: + path: "/etc/audit/rules.d/{{ item }}" + state: absent + tags: + - 4.1.3 + loop: + - sudolog.rules + - user_emulation.rules + - datetime.rules + - network.rules + - file-system-mounts.rules + - bad-file-access.rules + - user-group-info.rules + - dac.rules + - sessions.rules + - delete.rules + - login.rules + - MAC-policy.rules + - chcon.rules + - setfacl.rules + - chacl.rules + - usermod.rules + - modules.rules + + - name: 4.1.3.1 - Ensure changes to system administration scope is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sudolog.rules + src: audit_rules/sudolog.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.1 + - 4.1.3.3 + + - name: 4.1.3.2 - Ensure actions as another user are always logged + ansible.builtin.template: + dest: /etc/audit/rules.d/00-user_emulation.rules + src: audit_rules/user_emulation.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.2 + + - name: 4.1.3.4 - Ensure to collect events that modify date/time + ansible.builtin.template: + dest: /etc/audit/rules.d/00-datetime.rules + src: audit_rules/datetime.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.4 + + # TODO, determine if we need a separate RHEL9 version of network.rules + - name: 4.1.3.5 - Ensure modifications to network environment are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-network.rules + src: audit_rules/network.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.5 + + # Control 4.1.3.6 requires a system scan, skipping + + - name: 4.1.3.[7,10] - Ensure [un]successful file system mounts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-file-system-mounts.rules + src: audit_rules/file-system-mounts.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.3.7 + 4.1.3.10 + + - name: 4.1.3.7 - Ensure unsuccessful file access attempts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-bad-file-access.rules + src: audit_rules/bad-file-access.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.3.7 + + - name: 4.1.3.8 - Ensure events that modify user/group information are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-user-group-info.rules + src: audit_rules/user-group-info.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.8 + + - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-dac.rules + src: audit_rules/dac.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.9 + + - name: 4.1.3.11 - Ensure session initiation information is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sessions.rules + src: audit_rules/sessions.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.11 + + - name: 4.1.3.12 - Ensure system logins are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-login.rules + src: audit_rules/login.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.12 + + - name: 4.1.3.13 - Ensure file deletion events by users are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-delete.rules + src: audit_rules/delete.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.13 + + - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-MAC-policy.rules + src: audit_rules/MAC-policy.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.14 + + - name: 4.1.3.15 - Ensure successful and unsuccessful attempts to use the chcon command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chcon.rules + src: audit_rules/chcon.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.15 + + - name: 4.1.3.16 - Ensure successful and unsuccessful attempts to use the setfacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-setfacl.rules + src: audit_rules/setfacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.16 + + - name: 4.1.3.17 - Ensure successful and unsuccessful attempts to use the chacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chacl.rules + src: audit_rules/chacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.17 + + - name: 4.1.3.18 - Ensure successful and unsuccessful attempts to use the usermod command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-usermod.rules + src: audit_rules/usermod.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.18 + + - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-modules.rules + src: audit_rules/modules.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.19 + + - name: 4.1.3.20 - Ensure audit configuration is immutable + ansible.builtin.copy: + dest: /etc/audit/rules.d/99-finalize.rules + content: | + -e 2 + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.20 + + # 4.1.3.21 requires manual verification and ansible won't be able to check until after handlers are run; skipping + +- name: 4.1.4.[1-2,5-7] - Set auditd files to mode 600, user root, group root + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + with_fileglob: + - "/etc/audit/auditd.conf" + - '/etc/audit/rules.d/*' + - "{{ log_file }}" + tags: + - 4.1.4.1 + - 4.1.4.2 + - 4.1.4.3 + - 4.1.4.5 + - 4.1.4.6 + - 4.1.4.7 + +# TODO 4.1.4.4 + +- name: 4.1.4.[8-10] - Ensure audit tools are 0755 or less permissive + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 'go-w' + loop: + - "/sbin/auditctl" + - "/sbin/aureport" + - "/sbin/ausearch" + - "/sbin/autrace" + - "/sbin/auditd" + - "/sbin/augenrules" + tags: + - 4.1.4.8 + - 4.1.4.9 + - 4.1.4.10 + +# Section 4, Logging +- name: 4.2.1 - Configuring Rsyslog + when: log_service and log_service == "rsyslog" + block: + - name: 4.2.1.1 - Ensure rsyslog is installed + ansible.builtin.dnf: + name: rsyslog + state: present + tags: + - 4.2.1.1 + + - name: 4.2.1.2 - Ensure Rsyslog service is running + ansible.builtin.service: + name: rsyslog + enabled: true + state: started + tags: + - 4.2.1.2 + + - name: 4.2.1.3 - Configure journald to forward logs to rsyslog + tags: + - 4.2.1.3 + block: + - name: 4.2.1.3 - Find any rsyslog files where all logs are being forwarded to a loghost + ansible.builtin.shell: /usr/bin/grep -l -s "^*.*[^I][^I]*@" /etc/rsyslog.conf /etc/rsyslog.d/*.conf + register: rsyslog_forward_out + changed_when: false + failed_when: rsyslog_forward_out.rc == "2" + check_mode: false + + - name: 4.2.1.3 - Forward journald logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + line: "ForwardToSyslog=yes" + insertafter: "#ForwardToSyslog=no" + when: rsyslog_forward_out.stdout + + - name: 4.2.1.4 - Ensure rsyslog default file permissions are configured + ansible.builtin.lineinfile: + path: /etc/rsyslog.conf + regexp: '^\$FileCreateMode\s+0640' + line: "$FileCreateMode 0640" + create: true + owner: root + group: root + mode: 0644 + state: present + notify: Restart rsyslog + tags: + - 4.2.1.4 + + - name: 4.2.1.5 - Ensure logging is configured in rsyslog + ansible.builtin.copy: + src: "{{ rsyslog_file }}" + dest: "/etc/rsyslog.d/{{ rsyslog_file }}" + owner: root + group: root + mode: 0640 + when: rsyslog_file is defined and rsyslog_file | length > 0 + tags: + - 4.2.1.5 + + # Control 4.2.1.6 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent + # skipping + + - name: 4.2.1.7 - Ensure remote rsyslog messages are only acepted on designated log hosts + tags: + - 4.2.1.7 + block: + - name: 4.2.1.7 - Find all rsyslog conf files in /etc/rsyslog.d + ansible.builtin.find: + paths: "/etc/rsyslog.d" + patterns: "*.conf" + register: rsyslog_module_found + + - name: 4.2.1.7 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable imtcp loading module on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$ModLoad\s+imtcp' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable TCP port listening on non log hosts (rsylog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$InputTCPServerRun' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Disable TCP port listening on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$InputTCPServerRun' + state: absent + when: log_host is defined and not log_host + + - name: 4.2.1.7 - Enable loading of imtcp module on log hosts + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$ModLoad\s+imtcp' + line: "$ModLoad imtcp" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + - name: 4.2.1.7 - Enable TCP Port listening on port {{ log_port }} + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$InputTCPServerRun {{ log_port }}' + line: "$InputTCPServerRun {{ log_port }}" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + +# 4.2.2 Configure journald +- name: 4.2.2.1.1 - configure journald + when: log_service and log_service == "journald" + tags: + - 4.2.2.1 + block: + - name: 4.2.2.1.1 - Ensure systemd-journald-remote is installed + ansible.builtin.dnf: + name: systemd-journal-remote + state: present + tags: + - 4.2.2.1.1 + + # Control 4.2.2.1.2 is machine dependent, skipping + # Control 4.2.2.1.3 required 4.2.2.1.2 be configured prior. skipping + + - name: 4.2.2.1.4 Ensure systemd-journal-remote.socket is masked + ansible.builtin.systemd: + name: systemd-journal-remote.socket + enabled: false + masked: true + tags: + - 4.2.2.1.4 + + - name: 4.2.2.1.4 Ensure systemd-jorunal-remote.service is masked + ansible.builtin.systemd: + name: systemd-journal-remote.service + enabled: false + masked: true + tags: + - 4.2.2.1.4 + + - name: 4.2.2.3 - Ensure journald compresses large files + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Compress=((?!yes).)*$" + line: "Compress=yes" + insertafter: "^#Compress=" + notify: Restart journald + tags: + - 4.2.2.3 + + - name: 4.2.2.4 - Ensure journald writes to peristent disk + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Storage=((?!persistent).)*$" + line: "Storage=persistent" + insertafter: "^#Storage=" + notify: Restart journald + tags: + - 4.2.2.4 + + - name: 4.2.2.5 - Ensure journald is not configured to send logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + state: absent + tags: + - 4.2.2.5 + + # Control 4.2.2.6, configure log rotation is machine specific, skipping + # TODO + # Control 4.2.2.7, Ensure journald default file permissions configured, is machine dependant, skipping + +# Control 4.2.3 is machine specific, skipping + + - name: 4.2.2.2 - Ensure journald service is enabled + ansible.builtin.systemd: + name: systemd-journald + state: started + masked: false + enabled: true + tags: + - 4.2.2.2 + +- name: 4.3 - Ensure logrotate is installed and configured + tags: + - 4.3.0 + block: + - name: 4.3.0 - Ensure logrotate is installed + ansible.builtin.package: + name: logrotate + state: present + + - name: 4.3.0 - Copy source logrotate file + ansible.builtin.copy: + src: "{{ logrotate_file }}" + dest: /etc/logrotate.conf + owner: root + group: root + mode: 0644 + setype: etc_t + when: logrotate_file and logrotate_file | length > 0 + +# Section 5 - Access and Authorization +# + +# This control is early in order to create the files. This will +# make sure they are available when cron starts +- name: 5.1.0 - Configure cron/at + when: "'cronie' in ansible_facts.packages" + tags: + - 5.1.0 + block: + - name: 5.1.8 - Ensure cron is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/cron.allow + owner: root + group: root + mode: 0600 + state: file + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.8 - Ensure cron is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/cron.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ cron_allow }}" + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.1 - Ensure cron is enabled + ansible.builtin.service: + name: crond + enabled: true + state: started + tags: + - 5.1.1 + + - name: 5.1.2 - Ensure permissions on /etc/crontab + ansible.builtin.file: + path: /etc/crontab + owner: root + group: root + mode: 0600 + tags: + - 5.1.2 + + - name: 5.1.[3-7] - Ensure permissions on crontab directories + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0700 + loop: + - /etc/cron.hourly + - /etc/cron.daily + - /etc/cron.weekly + - /etc/cron.monthly + - /etc/cron.d + tags: + - 5.1.3 + - 5.1.4 + - 5.1.5 + - 5.1.6 + - 5.1.7 + + - name: 5.1.9 - Ensure at is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/at.allow + owner: root + group: root + mode: 0600 + state: file + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + + - name: 5.1.9 - Ensure at is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/at.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ at_allow }}" + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + +- name: 5.2 - SSH File configurations + tags: + - 5.2.0 + block: + - name: 5.2.1 - Set permissions on SSH file + ansible.builtin.file: + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: 0600 + tags: + - 5.2.1 + + - name: 5.2.2 - Set Permissions on ssh private host keys + tags: + - 5.2.2 + block: + - name: 5.2.2 - Find all ssh private host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key + register: ssh_host_out + changed_when: false + + - name: 5.2.2 - Set permissions on all ssh private host keys (Red Hat set the group to ssh_keys and mode to 640) + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0600 + loop: "{{ ssh_host_out.files }}" + + - name: 5.2.3 - Set Permissions on ssh public host keys + tags: + - 5.2.3 + block: + - name: 5.2.3 - Find all ssh public host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key.pub + register: ssh_hostpub_out + changed_when: false + + - name: 5.2.3 - Set permissions on all ssh public host keys + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0644 + loop: "{{ ssh_hostpub_out.files }}" + + - name: 5.2.4 - Ensure SSH access is limited + tags: + - 5.2.4 + block: + - name: 5.2.4 - Ensure SSH access is limited (AllowedUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowUsers', value: "{{ ssh_allowed_users }}" } + when: ssh_allowed_users is defined and ssh_allowed_users + + - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowGroups', value: "{{ ssh_allowed_groups }}" } + when: ssh_allowed_groups is defined and ssh_allowed_groups + + - name: 5.2.4 - Ensure SSH access is limited (DenyUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: "^{{ item.key }}\ *{{ item.value }}" + line: "{{ item.key }} {{ item.value }}" + loop: + - { key: 'DenyUsers', value: "{{ ssh_denied_users }}" } + notify: Restart sshd + when: ssh_denied_users is defined and ssh_denied_users + + - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } + when: ssh_denied_groups is defined and ssh_denied_groups + + - name: 5.2.5 - Set LogLevel to {{ ssh_log_level }} + ansible.builtin.replace: + path: /etc/ssh/sshd_config + replace: "LogLevel {{ ssh_log_level | upper }}" + regexp: '^LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' + notify: Restart sshd + when: ssh_log_level == "INFO" or ssh_log_level == "WARN" + tags: + - 5.2.5 + + - name: 5.2.6 - Ensure SSH is configured to use PAM + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "UsePAM yes" + regexp: '^UsePAM\s+[yes|no]' + insertafter: "#UsePAM" + notify: Restart sshd + tags: + - 5.2.6 + + - name: 5.2.7 - Ensure PermitRootLogin is disbled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "PermitRootLogin no" + regexp: '^PermitRootLogin\s*[^n]' + insertafter: '^#PermitRootLogin\s*[^n]' + notify: Restart sshd + tags: + - 5.2.7 + + - name: 5.2.8 - Ensure HostbasedAuthentication is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "HostbasedAuthentication no" + regexp: '^HostbasedAuthentication\s*[^n]' + insertafter: '^#HostbasedAuthentication\s*[^n]' + notify: Restart sshd + tags: + - 5.2.8 + + - name: 5.2.9 - Ensure SSH PermitEmptyPasswords is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitEmptyPasswords\s*[^n]' + notify: Restart sshd + tags: + - 5.2.9 + + - name: 5.2.10 - Ensure PermitUserEnvironment is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitUserEnvironment\s*[^n]' + notify: Restart sshd + tags: + - 5.2.10 + + - name: 5.2.11 - Ensure IgnoreRhosts is set + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "IgnoreRhosts yes" + regexp: '^IgnoreRhosts\s*[^y]' + insertafter: '^#IgnoreRhosts\s*[^y]' + notify: Restart sshd + tags: + - 5.2.11 + + - name: 5.2.12 - Disable X11 forwarding + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^X11Forwarding\s*yes' + state: absent + notify: Restart sshd + tags: + - 5.2.12 + + - name: 5.2.13 - Disable TCP Forwarding + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "AllowTcpForwarding no" + regexp: '^AllowTcpForwarding\s+(yes|no)' + insertafter: "^#AllowTcpForwarding" + notify: Restart sshd + tags: + - 5.2.13 + + - name: 5.2.14 - Ensure system crypto policy isn't overriden in SSH + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + state: absent + regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' + notify: Restart sshd + tags: + - 5.2.14 + + - name: 5.2.15 - Ensure SSH Banner is configured + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "Banner /etc/{{ ssh_login_banner }}" + regexp: "^Banner /etc/{{ ssh_login_banner }}" + insertafter: "^#Banner none" + notify: Restart sshd + tags: + - 5.2.15 + + - name: 5.2.16 - Ensure SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "MaxAuthTries {{ ssh_max_auth_tries }}" + regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' + insertafter: "^#MaxAuthTries" + notify: Restart sshd + tags: + - 5.2.16 + + - name: 5.2.17 - Limit max unauthenticated startups + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxStartups 10:30:60" + regexp: '^MaxStartups\s+10:30:60' + insertafter: '^#MaxStartups\s+10:30:100' + notify: Restart sshd + tags: + - 5.2.17 + + - name: 5.2.18 - Limit max sessions to {{ ssh_max_sessions }} + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxSessions {{ ssh_max_sessions }}" + regexp: '^MaxSessions\s+[{{ ssh_max_sessions }}]' + insertafter: '^#MaxSessions\s+10' + notify: Restart sshd + tags: + - 5.2.18 + + - name: 5.2.19 - Ensure SSH LoginGraceTime is set to {{ ssh_grace_time }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "LoginGraceTime {{ ssh_grace_time }}" + regexp: "^LoginGraceTime {{ ssh_grace_time }}" + insertafter: "^#LoginGraceTime" + notify: Restart sshd + tags: + - 5.2.19 + + - name: 5.2.20 - Ensure SSH Idle Timeout is configured ClientAliveInterval + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveInterval {{ ssh_alive_interval }}" + regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" + insertafter: "^#ClientAliveInterval" + notify: Restart sshd + tags: + - 5.2.20 + + - name: 5.2.20 - Ensure SSH Idle Timeout is configured ClientAliveCountMax + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveCountMax {{ ssh_alive_count_max }}" + regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" + insertafter: "^#ClientAliveCountMax" + notify: Restart sshd + tags: + - 5.2.20 + +# Make sure the sudoers file includes the requirement to use pty +- name: 5.3.2 - Ensure sudo commands use pty + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*use_pty' + line: "Defaults use_pty" + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.2 + +# Make sure the sudoers file includes the requirement to log to a file +- name: 5.3.3 - Ensure sudo log file exists + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*logfile="{{ sudo_log }}"' + line: 'Defaults logfile="{{ sudo_log }}"' + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.3 + +- name: 5.3.4 - Require password for priviledge escalation + tags: + - 5.3.4 + block: + - name: 5.3.4 - Find any instances of 'NOPASSWD' in /etc/sudoers or /etc/sudoers.d/* + ansible.builtin.find: + paths: "{{ item }}" + file_type: file + contains: "NOPASSWD" + register: sudo_nopasswd + with_fileglob: + - "/etc/sudoers" + - "/etc/sudoers.d/*" + + - name: 5.3.4 - Inform the user that instances were found + ansible.builtin.debug: + msg: "NOPASSWD was found in a sudoers file, please check and remove if not needed!" + when: sudo_nopasswd + +- name: 5.3.5 - Ensure re-authentication for privilege escalation is not disabled globally + tags: + - 5.3.5 + block: + - name: 5.3.5 - Find any instances of '!authenticate' in /etc/sudoers or /etc/sudoers.d/* + ansible.builtin.find: + paths: "{{ item }}" + file_type: file + contains: '^[^#].*\!authenticate' + register: sudo_reauthenticate + with_fileglob: + - "/etc/sudoers" + - "/etc/sudoers.d/*" + + - name: 5.3.5 - Inform the user that instances were found + ansible.builtin.debug: + msg: "!authenticate was found in a sudoers file, please check and remove if not needed!" + when: sudo_reauthenticate + +# Control 5.3.6 TODO + +- name: 5.3.7 - Restrict su to wheel group + tags: + - 5.3.7 + block: + - name: 5.3.7 - Configure PAM to only allow su from wheel group + ansible.builtin.replace: + path: /etc/pam.d/su + regexp: '^#auth\s+required\s+pam_wheel.so\s+use_uid' + replace: "auth required pam_wheel.so use_uid group=wheel" + + - name: 5.3.7 - Add root to the wheel group + ansible.builtin.user: + name: root + groups: wheel + append: true + + +# Control section 5.4, authselect, cannot be used with Red Hat IPA or Microsoft AD +# Skipping until can assure we know how to test against this. + +# Control 5.4.3, Set password retention, requries file replacement +# skipping + +- name: 5.5.0 - Configure PAM files and password requirements + tags: + - 5.5.0 + block: + - name: 5.5.1 - require at least one digit in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: dcredit = -1 + regexp: "^dcredit = -1" + insertafter: "# dcredit = 0" + when: password_req_digit + + - name: 5.5.1 - require at least one uppercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ucredit = -1 + regexp: "^ucredit = -1" + insertafter: "# ucredit = 0" + when: password_req_upper + + - name: 5.5.1 - require at least one lowercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: lcredit = -1 + regexp: "^lcredit = -1" + insertafter: "^# lcredit = 0" + when: password_req_lower + + - name: 5.5.1 - Require at least one special character in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ocredit = -1 + regexp: "^ocredit = -1" + insertafter: "^# ocredit = 0" + when: password_req_digit + + - name: 5.5.1 - Require at least {{ password_min_length }} characters in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: minlen = {{ password_min_length }} + regexp: "^minlen = {{ password_min_length }}" + insertafter: "^# minlen = 8" + when: password_req_digit + +- name: 5.5.2 - Ensure lockout attempts for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" + line: "deny = {{ password_failed_attempts }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 + +- name: 5.5.2 - Ensure lockout time for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" + line: "unlock_time={{ password_failed_time }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 + +# 5.5.3 - Password retention involves configuring pam.conf, skipping + +- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 or yescrypt + ansible.builtin.replace: + path: /etc/libuser.conf + regexp: "^crypt_style\ *=\ *(sha512|yescrypt)" + replace: "crypt_style = {{ password_hash_alg | lower }}" + after: "[defaults]" + owner: root + group: root + mode: 0644 + tags: + - 5.5.4 + +- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 or yescrypt + ansible.builtin.replace: + path: /etc/login.defs + regexp: "^ENCRYPT_METHOD\ *(SHA512|YESCRYPT)" + replace: "ENCRYPT_METHOD {{ password_hash_alg | upper }}" + tags: + - 5.5.4 + +- name: 5.6.1.1 - Ensure password expiration is {{ password_expire_days }} days or less + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' + line: "PASS_MAX_DAYS {{ password_expire_days }}" + state: present + tags: + - 5.6.1.1 + +- name: 5.6.1.2 - Ensure password change days is set to {{ password_min_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' + line: "PASS_MIN_DAYS {{ password_min_days }}" + state: present + tags: + - 5.6.1.2 + +- name: 5.6.1.3 - Ensure password warning days is set to {{ password_warning_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' + line: "PASS_WARN_AGE {{ password_warning_days }}" + state: present + tags: + - 5.6.1.3 + +# We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days +# The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well +- name: 5.6.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration + ansible.builtin.replace: + path: /etc/default/useradd + regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" + replace: "INACTIVE={{ password_inactive_lock_days }}" + owner: root + group: root + mode: 0600 + tags: + - 5.6.1.4 + +# 5.6.1.5, Ensure all users last password change date is in the past, +# is not easily automated. Will revisit later + +# 5.6.2, Ensure system accounts are secured, requires manual intervention, skipping + +- name: 5.6.3 - Ensure default shell timeout is {{ shell_timeout }} seconds or less + ansible.builtin.blockinfile: + path: "{{ item }}" + block: | + TMOUT={{ shell_timeout }} + export TMOUT + marker: "# {mark} Ansible Managed CIS Timeout" + loop: + - /etc/bashrc + - /etc/profile + tags: + - 5.6.3 + +# Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod +- name: 5.6.4 - Ensure default group for root is GID 0 + ansible.builtin.command: /usr/sbin/usermod -g 0 root + changed_when: false + tags: + - 5.6.4 + +- name: 5.6.5 - Ensure default user umask is set + ansible.builtin.replace: + path: "{{ item }}" + replace: " umask {{ default_umask }}" + regexp: '^\s*umask\s*022' + loop: + - /etc/bashrc + - /etc/profile + - /etc/login.defs + tags: + - 5.6.5 + +# The user module here uses a known salt to idompotently set the password for multiple runs +# see https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#hash-filters +- name: 5.6.6 - Set root password + tags: + - 5.6.6 + block: + - name: 5.6.6 - Check if root has a password + ansible.builtin.lineinfile: + path: /etc/shadow + regexp: '^root:[*\!|*\*]*:' + state: absent + check_mode: true + changed_when: false + register: root_pw_check + failed_when: false + + - name: 5.6.6 - Set root password + ansible.builtin.user: + name: root + password: "{{ root_password | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" + when: root_pw_check.found != "0" and root_password is defined + +# Section 6 - System Maintenance + +- name: 6.1.[1-4] - Ensure permissions on /etc/passwd[-], /etc/group[-] + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0644 + loop: + - passwd + - passwd- + - group + - group- + tags: + - 6.1.1 + - 6.1.2 + - 6.1.3 + - 6.1.4 + +- name: 6.1.[5-8] - Ensure permissions on /etc/shadow[-], /etc/gshadow + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + loop: + - shadow + - shadow- + - gshadow + - gshadow- + tags: + - 6.1.5 + - 6.1.6 + - 6.1.7 + - 6.1.8 + +# Control 6.1.10, Ensure no world writable files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.9 - Ensure no world writable files exist + tags: + - 6.1.9 + block: + - name: 6.1.9 - Find any world writiable files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" + register: ww_files + changed_when: false + check_mode: false + + - name: 6.1.9 - Print any world writable files found + ansible.builtin.debug: + msg: "World writiable files found: {{ ww_files.stdout }}" + changed_when: true + when: ww_files.stdout + +# Control 6.1.10 Ensure no unowned files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.10 - Ensure no unowned files exist + tags: + - 6.1.10 + block: + - name: 6.1.10 - Find any unowned files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" + register: uo_files + changed_when: false + check_mode: false + + - name: 6.1.10 - Print any unowned files found + ansible.builtin.debug: + msg: "unowned files found: {{ uo_files.stdout }}" + changed_when: true + when: uo_files.stdout + +- name: 6.1.11 - Ensure no ungrouped files exist + tags: + - 6.1.11 + block: + - name: 6.1.11 - Find any ungrouped files + ansible.builtin.shell: "/usr/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" + register: ug_files + changed_when: false + check_mode: false + + - name: 6.1.11 - Print any ungrouped files found + ansible.builtin.debug: + msg: "ungrouped files found: {{ uo_files.stdout }}" + changed_when: true + when: ug_files.stdout + + + # Find all local filesystem directories and set the sticky bit on world writable ones +- name: 6.1.12 - Ensure sticky bit is set on world-writeable directories + ansible.builtin.shell: > + set -o pipefail ; + /usr/bin/df --local -P | awk '{if (NR!=1) print $6}' | xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null | + xargs -I '{}' chmod a+t '{}' + changed_when: false + tags: + - 6.1.12 + + +# Control 6.1.13, Audit SUID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +# Control 6.1.14, Audit SGID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +# Contorl 6.1.15, Audit system file permissions requires manual intervention, skipping + +- name: 6.2.1 - Ensure accounts in /etc/passwd use shadowed passwords + tags: + - 6.2.1 + block: + - name: 6.2.1 - Check to see if there are any accounts with empty passwords + ansible.builtin.shell: "/usr/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" + changed_when: false + register: empty_passwords + check_mode: false + + - name: 6.2.1 - Report the named users to the report + ansible.builtin.debug: + msg: "The user {{ item }} has an empty password" + when: empty_passwords.stdout + changed_when: true + loop: "{{ empty_passwords.stdout_lines }}" + +- name: 6.2.2 - /etc/shadow password fields are not empty + ansible.builtin.command: "awk -F: '($2 == \"\")' {{ item }}" + loop: + - /etc/shadow + changed_when: false + tags: + - 6.2.2 + +- name: 6.2.3 - Report on groups in /etc/passwd with a GID not in /etc/group + tags: + - 6.2.3 + block: + - name: 6.2.3 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/undefined_groups.sh + register: undefined_groups + changed_when: false + check_mode: false + + - name: 6.2.3 - Report to user any unreferenced groups + ansible.builtin.debug: + msg: "{{ undefined_groups.stdout_lines }}" + changed_when: true + when: undefined_groups.stdout + +- name: 6.2.4 - Report if shadow group exists in /etc/group + block: + - name: 6.2.4 - Determine if the shadow group exists in /etc/group + ansible.builtin.command: /bin/grep "^shadow:" /etc/group + register: shadow_out + changed_when: false + failed_when: shadow_out.rc == "2" + + - name: 6.2.4 - Print report of shadow group in /etc/group to user + ansible.builtin.debug: + msg: "Shadow group exists in /etc/group. Remove" + changed_when: true + when: shadow_out.stdout + + +- name: 6.2.4 - Ensure no duplicate UIDs exist + tags: + - 6.2.4 + block: + - name: 6.2.3 - find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc >= 2 + check_mode: false + + - name: 6.2.4 - Use script to pull the list of duplicate UIDs + ansible.builtin.script: + cmd: files/duplicate_uids.sh + register: duplicate_uids + changed_when: false + check_mode: false + + - name: 6.2.4 - Print report of duplicated UIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_uids.stdout_lines }}" + changed_when: true + when: duplicate_uids.stdout + +- name: 6.2.5 - Report on duplicate GIDs in /etc/group + tags: + - 6.2.5 + block: + - name: 6.2.5 - Use script to pull the list of duplicate GIDs + ansible.builtin.script: + cmd: files/duplicate_guids.sh + register: duplicate_guids + changed_when: false + check_mode: false + + - name: 6.2.5 - Print report of duplcate GIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_guids.stdout_lines }}" + changed_when: true + when: duplicate_guids.stdout + +- name: 6.2.6 - Report on duplicate users in /etc/passwd + tags: + - 6.2.6 + block: + - name: 6.2.6 - Use script to pull the list of users + ansible.builtin.script: + cmd: files/duplicate_users.sh + register: duplicate_users + changed_when: false + check_mode: false + + - name: 6.2.6 - Print report of duplicate users to user + ansible.builtin.debug: + msg: "{{ duplicate_users.stdout_lines }}" + changed_when: true + when: duplicate_users.stdout + +- name: 6.2.7 - Report on duplicate groups in /etc/group + tags: + - 6.2.7 + block: + - name: 6.2.7 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/duplicate_groups.sh + register: duplicate_groups + changed_when: false + check_mode: false + + - name: 6.2.7 - Print report of duplicate groups to user + ansible.builtin.debug: + msg: "{{ duplicate_groups.stdout_lines }}" + changed_when: true + when: duplicate_groups.stdout + +- name: 6.2.8 - Ensure root PATH integrity + tags: + - 6.2.8 + block: + - name: 6.2.8 - Run script on path variable + ansible.builtin.script: files/path_check.sh + changed_when: false + register: path_check + check_mode: false + + - name: 6.2.8 - Print report to user + ansible.builtin.debug: + msg: + - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" + - "that exist in root's path because of this. It should be run as root on the target machine manually." + - " {{ path_check.stdout }}" + when: path_check.stdout and not ansible_check_mode + +- name: 6.2.9 - Ensure root is the only UID 0 account + tags: + - 6.2.9 + block: + - name: 6.2.9 - find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc == 2 + check_mode: false + + - name: 6.2.9 - Report on mulitple accounts with UID of 0 + ansible.builtin.debug: + msg: + - "Accounts with UID zero in addition to root" + - " {{ rootuid.stdout_lines }}" + changed_when: true + when: rootuid.stdout != 'root' + +- name: 6.2.10 - Report on users that do not have a home directory + tags: + - 6.2.10 + block: + - name: 6.2.10 - Use script to find the users + ansible.builtin.script: + cmd: files/non_existant_homedirs.sh + register: nohomedir + changed_when: false + + - name: 6.2.10 - Print report of users that do not have a home directory + ansible.builtin.debug: + msg: "{{ nohomedir.stdout_lines }}" + changed_when: true + when: nohomedir.stdout + +# Control 6.2.11 - Ensure users own their home directories is environment dependent, skipping +# Control 6.2.12 - Ensure users home dir permissions are set is environment dependent, skipping +# Control 6.2.13 - Ensure users .netrc files are removed is environment dependent, skipping +# Control 6.2.14 - Ensure users .forward files are removed is environment dependent, skipping +# Control 6.2.15 - Ensure users .rhosts files are removed is environment dependent, skipping +# Control 6.2.16 - Ensure users dot files are not gorup or world writable is environment dependent, skipping diff --git a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml index dced8ff..116eebd 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml @@ -14,7 +14,7 @@ # Let the user know what version of the controls file is running # Use a variable so it prints out the correct version. - name: Print Header - - ansible.builtin.debug: msg="CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" + ansible.builtin.debug: msg="CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" # Collect the packages installed on the system so we can check agains them later - name: Collect package list @@ -144,6 +144,7 @@ Where=/tmp Type=tmpfs Options=mode=1777,strictatime,noexec,nodev,nosuid + mode: 0644 create: true - name: Ensure the local-fs directory is created @@ -158,12 +159,12 @@ # symbolic link is required for .wants/ folder - name: Create a symbolic link ansible.builtin.file: - src: /etc/systemd/system/tmp.mount - dest: /etc/systemd/system/local-fs.target.wants/tmp.mount + src: /usr/share/systemd/tmp.mount + dest: /etc/systemd/system/tmp.mount owner: root group: root state: link - notify: restart tmpfs + notify: Restart tmpfs tags: - 1.1.2 @@ -607,7 +608,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 @@ -629,7 +630,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 tags: @@ -697,7 +698,7 @@ - name: 1.5.3 - Set root password ansible.builtin.user: name: root - password: "{{ 'root_password' | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" + password: "{{ root_password | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" when: root_pw_check.found != "0" and root_password is defined - name: 1.5.3 - Set root password @@ -778,7 +779,7 @@ line: "Storage=none" insertafter: "#Storage=external" when: "'systemd-coredump' in ansible_facts.packages" - notify: reload systemctl + notify: Reload systemctl - name: 1.6.4 - Limit coredump processsize if systemd-coredump package is installed ansible.builtin.lineinfile: @@ -787,7 +788,7 @@ line: "ProcessSizeMax=0" insertafter: "#ProcessSizeMax=2G" when: "'systemd-coredump' in ansible_facts.packages" - notify: reload systemctl + notify: Reload systemctl tags: - 1.6.4 @@ -824,7 +825,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor ' - notify: rebuild ubuntu-grub + notify: Rebuild ubuntu-grub when: not apparmor_grub.found tags: - 1.7.1.2 @@ -941,7 +942,7 @@ group: root mode: 0644 when: time_service == "chrony" - notify: restart chronyd + notify: Restart chronyd tags: - 2.2.1.3 @@ -953,7 +954,7 @@ group: root mode: 0644 when: time_service == "chrony" or time_service == "ntp" - notify: restart {{ time_service }}d + notify: Restart {{ time_service }}d tags: - 2.2.1.3 @@ -966,7 +967,7 @@ group: root mode: 0644 when: time_service == "timesync" - notify: restart timesyncd + notify: Restart timesyncd tags: - 2.2.1.2 @@ -1152,7 +1153,7 @@ # to remove packages from the system that are not needed. - name: Process removal list ansible.builtin.package: - name: unneeded_packages + name: "{{ unneeded_packages }}" state: absent # Cups should be remove per control 2.2.16, but it may not be able to due to @@ -1207,7 +1208,7 @@ - net.ipv4.conf.all.forwarding # (3.1.1) - net.ipv4.conf.all.send_redirects # (3.1.2) - net.ipv4.conf.default.send_redirects # (3.1.2) - notify: flush network routes + notify: Flush network routes - name: 3.1 - Set ipv6 networking parameters (OFF) ansible.builtin.sysctl: @@ -1225,7 +1226,7 @@ - net.ipv6.conf.all.accept_ra # (3.2.9) - net.ipv6.conf.default.accept_ra # (3.2.9) when: not ipv6_disable - notify: flush network routes + notify: Flush network routes tags: - 3.1.0 @@ -1245,7 +1246,7 @@ - net.ipv4.conf.default.accept_redirects # (3.2.2) - net.ipv4.conf.all.secure_redirects # (3.2.3) - net.ipv4.conf.default.secure_redirects # (3.2.3) - notify: flush network routes + notify: Flush network routes - name: 3.2.[4-8] - Set ipv4 networking parameters (ON) ansible.builtin.sysctl: @@ -1262,7 +1263,7 @@ - net.ipv4.conf.all.rp_filter # (3.2.7) - net.ipv4.conf.default.rp_filter # (3.2.7) - net.ipv4.tcp_syncookies # ( 3.2.8) - notify: flush network routes + notify: Flush network routes - name: 3.2 - Set ipv6 networking parameters (OFF) ansible.builtin.sysctl: @@ -1278,7 +1279,7 @@ - net.ipv6.conf.default.accept_redirects # (3.2.2) - net.ipv6.conf.all.accept_ra # (3.2.9) - net.ipv6.conf.default.accept_ra # (3.2.9) - notify: flush network routes + notify: Flush network routes when: not ipv6_disable tags: - 3.2.0 @@ -1370,7 +1371,7 @@ ansible.builtin.package: name: "ufw" state: present - notify: start ufw # 3.5.2.1 + notify: Start ufw # 3.5.2.1 - ansible.builtin.debug: msg: @@ -1460,7 +1461,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' - notify: rebuild ubuntu-grub + notify: Rebuild ubuntu-grub when: not ipv6_disable_grub.found and ipv6_disable tags: @@ -1509,7 +1510,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="audit=1 ' - notify: rebuild ubuntu-grub + notify: Rebuild ubuntu-grub when: not audit_exist.found tags: - 4.1.1.3 @@ -1531,7 +1532,7 @@ dest: /etc/default/grub regexp: '^\s*GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' - notify: rebuild ubuntu-grub + notify: Rebuild ubuntu-grub when: not audit_backlog_exist.found tags: - 4.1.1.4 @@ -1553,7 +1554,7 @@ dest: /etc/default/grub regexp: 'audit_backlog_limit=[\S]*' replace: 'audit_backlog_limit={{ audit_backlog_limit }}' - notify: rebuild ubuntu-grub + notify: Rebuild ubuntu-grub when: audit_backlog_exist.found and not our_limit.found tags: - 4.1.1.4 @@ -1582,7 +1583,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.3 @@ -1593,7 +1594,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.4 @@ -1609,7 +1610,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.5 @@ -1620,7 +1621,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.6 @@ -1631,7 +1632,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.7 @@ -1642,7 +1643,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.8 @@ -1654,7 +1655,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.9 @@ -1665,7 +1666,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.10 @@ -1679,7 +1680,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.12 @@ -1690,7 +1691,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.13 @@ -1703,7 +1704,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.14 4.1.15 @@ -1715,7 +1716,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.16 @@ -1727,7 +1728,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.17 when: enable_audit is defined and enable_audit @@ -2004,7 +2005,7 @@ path: /etc/ssh/sshd_config replace: "LogLevel {{ ssh_log_level | upper }}" regexp: '^LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' - notify: restart sshd + notify: Restart sshd when: ssh_log_level == "INFO" or ssh_log_level == "WARN" tags: - 5.2.5 @@ -2016,7 +2017,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^X11Forwarding\s*yes' - notify: restart sshd + notify: Restart sshd tags: - 5.2.6 @@ -2026,7 +2027,7 @@ line: "MaxAuthTries {{ ssh_max_auth_tries }}" regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' insertafter: "^#MaxAuthTries" - notify: restart sshd + notify: Restart sshd tags: - 5.2.7 @@ -2035,7 +2036,7 @@ path: /etc/ssh/sshd_config line: "IgnoreRhosts yes" regexp: '^IgnoreRhosts\s*[^y]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.8 @@ -2044,7 +2045,7 @@ path: /etc/ssh/sshd_config line: "HostbasedAuthentication no" regexp: '^HostbasedAuthentication\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.9 @@ -2053,7 +2054,7 @@ path: /etc/ssh/sshd_config line: "PermitRootLogin no" regexp: '^PermitRootLogin\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.10 @@ -2062,7 +2063,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.11 @@ -2071,7 +2072,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.12 @@ -2080,7 +2081,7 @@ path: /etc/ssh/sshd_config line: "Ciphers {{ ssh_ciphers_list }}" regexp: '^Ciphers ((?!{{ ssh_ciphers_list }}).)*$ ' - notify: restart sshd + notify: Restart sshd tags: - 5.2.13 @@ -2090,7 +2091,7 @@ line: "MACs {{ ssh_mac_list }}" insertafter: EOF regexp: '^MACs ((?!{{ ssh_mac_list }}).)*$ ' - notify: restart sshd + notify: Restart sshd tags: - 5.2.14 @@ -2100,7 +2101,7 @@ line: "KexAlgorithms {{ ssh_kex_list }}" insertafter: EOF regexp: '^KexAlgorithms ((?!{{ ssh_kex_list }}).)*$ ' - notify: restart sshd + notify: Restart sshd tags: - 5.2.14 @@ -2110,7 +2111,7 @@ line: "ClientAliveInterval {{ ssh_alive_interval }}" regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" insertafter: "^#ClientAliveInterval" - notify: restart sshd + notify: Restart sshd tags: - 5.2.16 @@ -2120,7 +2121,7 @@ line: "ClientAliveCountMax {{ ssh_alive_count_max }}" regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" insertafter: "^#ClientAliveCountMax" - notify: restart sshd + notify: Restart sshd tags: - 5.2.16 @@ -2130,7 +2131,7 @@ line: "LoginGraceTime {{ ssh_grace_time }}" regexp: "^LoginGraceTime {{ ssh_grace_time }}" insertafter: "^#LoginGraceTime" - notify: restart sshd + notify: Restart sshd tags: - 5.2.17 @@ -2141,7 +2142,7 @@ path: "/etc/ssh/sshd_config" line: "Banner /etc/{{ ssh_login_banner }}" regexp: "^Banner /etc/{{ ssh_login_banner }}" - notify: restart sshd + notify: Restart sshd tags: - 5.2.19 @@ -2150,7 +2151,7 @@ path: "/etc/ssh/sshd_config" line: "UsePAM yes" regexp: '^UsePAM\s+[yes|no]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.20 @@ -2160,7 +2161,7 @@ line: "AllowTcpForwarding no" regexp: '^AllowTcpForwarding\s+(yes|no)' insertafter: "^#AllowTcpForwarding" - notify: restart sshd + notify: Restart sshd tags: - 5.2.21 @@ -2169,7 +2170,7 @@ path: "/etc/ssh/sshd_config" line: "maxstartups 10:30:60" regexp: '^maxstartups\s+10:30:60' - notify: restart sshd + notify: Restart sshd tags: - 5.2.22 @@ -2178,7 +2179,7 @@ path: "/etc/ssh/sshd_config" line: "maxsessions {{ ssh_max_sessions }}" regexp: '^maxsessions\s+[{{ ssh_max_sessions }}]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.23 diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml new file mode 100644 index 0000000..cf41bd1 --- /dev/null +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -0,0 +1,3319 @@ +--- +# Task file for CIS Controls +# This file is commented to help view what Ansible Automation is doing +# and under what circumstances. + +# Some blocks below have tasks with tags and some without. Blocks of tasks that +# contain multiple controls have tasks with tags. Blocks that consist of a +# single control and are just put together for convience sake, do not have +# sub-block tasks with tags. + +# Comments about how the modules are used will become more infrequent as +# the file goes along to avoid repeating oneself. + +# Let the user know what version of the controls file is running +# Use a variable so it prints out the correct version. +- name: Print Header + ansible.builtin.debug: + msg: "CIS Controls for {{ ansible_distribution }} {{ ansible_distribution_major_version }}" + +# Collect the packages installed on the system so we can check agains them later +- name: Collect package list + ansible.builtin.package_facts: + manager: auto + tags: + - always + +# Find the minimum UID of the machine for normal acocunts. This varies +# between machines and environments, so we pull it from the file it +# is supposed to exist in. +- name: Determine the Minimum UID for new, non-system, accounts + ansible.builtin.command: "/usr/bin/awk '/^s*UID_MIN/{print $2}' /etc/login.defs" + register: min_uid + changed_when: min_uid.rc == "2" + tags: + - always + +# Update the system with security packages using the system's package manager +# Only update the system if the 'update_system' variable is set to true +- name: 1.9.0 - Ensure updated system + ansible.builtin.package: + name: "*" + state: latest + security: true + when: update_system + tags: + - 1.9.0 + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: 1.1 - Disable unused filesystems + ansible.builtin.set_fact: + unused_filesystems: [] + +- name: 1.1.1.1 - Add cramfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + [ 'cramfs' ] }}" + tags: + - 1.1.1.1 + +- name: 1.1.1.2 - Add squashfs to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + [ 'squashfs' ] }}" + tags: + - 1.1.1.2 + +- name: 1.1.1.3 - Add udf to list of unused filesystems + ansible.builtin.set_fact: + unused_filesystems: "{{ unused_filesystems + [ 'udf' ] }}" + tags: + - 1.1.1.3 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: 1.1 - Process unused_filesystem list + ansible.builtin.package: + name: "{{ unused_filesystems }}" + state: absent + tags: + - 1.1.0 + +- name: 1.1 - Add unused_filesystems to /etc/modprobe.d/CIS.conf + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install {{ item }} /bin/false" + state: present + create: true + owner: root + group: root + mode: 0644 + loop: + - unused_filesystems + tags: + - 1.1.0 + +# Create and configure the local-fs systemd service file +- name: 1.1.[2-5] - Ensure /tmp is configured + tags: + - 1.1.2 + - 1.1.3 + - 1.1.4 + - 1.1.5 + block: + # Create a file to hold the system specific local-fs service information + # be sure to set the selinux security context. Even if selinux is disabled, + # it's a good idea to make sure it is set on files + - name: Ensure the local-fs directory is created + ansible.builtin.file: + path: /etc/systemd/system/local-fs.target.wants + state: directory + owner: root + group: root + mode: 0755 + setype: etc_t + + # Add content to the file we created using the blockinfile command. + # Notify systemd to reload its daemons and start the local-fs service + - name: 1.1.[2-5] - Configure config file for tmpfs + when: ansible_distribution != "Ubuntu" + ansible.builtin.blockinfile: + path: /etc/systemd/system/local-fs.target.wants/tmp.mount + block: | + [Mount] + What=tmpfs + Where=/tmp + Type=tmpfs + Options=mode=1777,strictatime,noexec,nodev,nosuid + mode: 0644 + create: true + + # symbolic link is required for .wants/ folder + - name: Create a symbolic link + when: ansible_distribution != "Ubuntu" + ansible.builtin.file: + dest: /etc/systemd/system/tmp.mount + src: /etc/systemd/system/local-fs.target.wants/tmp.mount + owner: root + group: root + state: link + notify: Restart tmpfs + + - name: Configure tmpfs service on Ubuntu + when: ansible_distribution == "Ubuntu" + ansible.builtin.systemd_service: + name: /usr/share/systemd/tmp.mount + enabled: true + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.3 - Configure /var + tags: + - 1.1.3 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.3 - Set/reset /var mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.3.1 - Determine if /var is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var" + with_items: + - "{{ ansible_mounts }}" + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.3.1 - Report to user if /var is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.3.1 + + - name: 1.1.3.2 - Report to user if /var does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.3.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.3.3 Report to user if /var does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.3.3 +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.7 - /var/tmp partition and mount options + tags: + - 1.1.4 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.4 - Set/reset /var/tmp mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.4.1 - Determine if /var/tmp is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/var/tmp" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.4.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.4.1 - Report to user if not on separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.4.1 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.2 - Report to user if /var/tmp does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have noexec set" + when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.4.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.3 - Report to user if /var/tmp does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.4.3 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.4.4 - Report to user if /var/tmp does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/tmp/ does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.4.4 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.5 - Configure /var/log + tags: + - 1.1.5 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.5 - Set/reset /var/log mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.5 - Determine if /var/log is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var/log" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.5.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.5.1 - Report to user if /var/log is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.5.1 + + - name: 1.1.5.2 - Report to user if /var/log does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.5.2 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.5.3 Report to user if /var/log does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.5.3 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.6 - Configure /var/log/audit + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.6 - Set/reset mount /var/log/audit counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.6.1 - Determine if /var/log/audit is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + when: item.mount == "/var/log/audit" + with_items: + - "{{ ansible_mounts }}" + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.6.1 - Report to user if /var/log/audit is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /var/log/audit is not on a separate partition" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.6.1 + +# Determine if a filesystem is on a separate partition, if so, then +# check to see if various filesystem options exist for the filesystem +- name: 1.1.7 - Configure /home + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.7 - Set/reset /home mount counter + ansible.builtin.set_fact: + mount_count: 0 + + # Examine the ansible_mounts variable which includes all of the system mounts + # on machine. Search for the appropriate mount information. If it exists, + # increment the integer variable by '1' and save the filesystems options to a + # new variable called mount_options. + - name: 1.1.7.1 - Determine if /home is on a separate partition + ansible.builtin.set_fact: + mount_count: "addition{{ mount_count + 1 }}" + mount_options: "{{ item.options }}" + when: item.mount == "/home" + with_items: + - "{{ ansible_mounts }}" + tags: + - 1.1.7.1 + + # If the number in mount_count variable is > 0, then we found the mount. If not, + # then report to the user that the given filesystem was not on a separate partition. + - name: 1.1.7.1 - Report to user if /home is not on a separate partition + ansible.builtin.debug: + msg: "FAILED CONTROL: /home is not on a separate partition. Skipping mount option checks" + when: mount_count == 0 + changed_when: true + tags: + - 1.1.7.1 + + # Look through the mount_options variable for the given filesystem option. if it is + # not found, or if the filesystem is not on a separate partition (therefore has no mount options) + # let the user know. + - name: 1.1.7.2 - Report to user if /home does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nodev set" + when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.7.2 + + - name: 1.1.7.3 Report to user if /home does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /home does not have nosuid set" + when: mount_options is defined and "nodsuid" not in mount_options and mount_count == 0 + changed_when: true + tags: + - 1.1.7.3 + +# /dev/shm does not exist in ansible_mounts so we have to check the +# mount command directly. This requires the use of the shell command which +# is not ideal. +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8 - Configure /dev/shm + tags: + - 1.1.8 + block: + - name: 1.1.8.1 - Determine if /dev/shm has nodev set + ansible.builtin.shell: cat /proc/mounts | /bin/grep /dev/shm | /bin/grep -v nodev + register: devshm_nodev_out + failed_when: devshm_nodev_out == "2" + changed_when: false + check_mode: false + +# Let the user know if we did not find the option set. + - name: 1.1.8.1 - Report to user if /dev/shm does not have nodev set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nodev set" + when: devshm_nodev_out is defined and devshm_nodev_out.stdout + changed_when: true + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8.3 - Report if /dev/shm does not have nosuid set + tags: + - 1.1.8.3 + # This whole block can be turned off by excluding the following tag(s) + block: + - name: 1.1.8.3 - Determine if /dev/shm has nosuid set + ansible.builtin.shell: cat /proc/mounts | /bin/grep /dev/shm | /bin/grep -v nosuid + register: devshm_nosuid_out + failed_when: devshm_nosuid_out == "2" + changed_when: false + check_mode: false + + - name: 1.1.8.3 - Report to user if /dev/shm does not have nosuid set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have nosuid set" + when: devshm_nosuid_out is defined and devshm_nosuid_out.stdout + changed_when: true + +# Grep out /dev/shm and see if the given option is set. +- name: 1.1.8.3 - Report if /dev/shm does not have noexec set + tags: + # This whole block can be turned off by excluding the following tag(s) + - 1.1.8.3 + block: + - name: 1.1.8.3 - Determine if /dev/shm has noexec set + ansible.builtin.shell: cat /proc/mounts | /bin/grep /dev/shm | /bin/grep -v noexec + register: devshm_noexec_out + failed_when: devshm_noexec_out == "2" + changed_when: false + check_mode: false + tags: + - 1.1.8.3 + +# Let the user know if we did not find the option set. + - name: 1.1.8.3 - Report to user if /dev/shm does not have noexec set + ansible.builtin.debug: + msg: "FAILED CONTROL: /dev/shm does not have noexec set" + when: devexec_nosuid_out is defined and devshm_noexec_out.stdout + changed_when: true + +# Control 1.1.18, 1.1.19, 1.1.20 are for removable media + +# Find all local filesystem directories and set the sticky bit on world writable ones +# The default shell might be dash instead of bash, so make sure we fire off bash, if not pipefail does not work +# ignore errors because on ubuntu you can still get permission denied +- name: 1.1.21 - Ensure sticky bit is set on world-writeable directories + ansible.builtin.shell: > + set -o pipefail ; /bin/df --local -P | awk '{if (NR!=1) print $6}' | + xargs -I '{}' find '{}' -xdev -type d \( -perm -0002 -a ! -perm -1000 \) 2>/dev/null | xargs -I '{}' chmod a+t '{}' + args: + executable: /bin/bash + changed_when: false + ignore_errors: true + tags: + - 1.1.21 + +# Turn off and disable the autofs service using the service module. +# We check to see if the package that autofs belongs to (convienently called autofs) +# exists in the ansible_facts.packages list we gathered early in the play +- name: 1.1.9 - disable automounting + ansible.builtin.service: + name: autofs + enabled: false + state: stopped + when: "'autofs' in ansible_facts.packages" + tags: + - 1.1.9 + +- name: 1.1.10 - Disable USB storage module + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install usb-storage /bin/false" + state: present + create: true + owner: root + group: root + mode: 0644 + tags: + - 1.1.10 + +# Control 1.2.1 is system updating. Make sure system is set for some kind of system software update + +# GPGKeys are used to sign packages. enabling them will mean that all packages +# from a given repo must be signed with the appropriate key +- name: 1.2.2 - Ensure GPG keys are configured + ansible.builtin.debug: + msg: "Currently this control doesn't do anything except to check currently installed keys" + tags: + - 1.2.2 + +# AIDE is a file system integrity checker which will document all +# filesystem changes. It's very noisy on busy systems and should be +# enabled when you have the sapce and need for it. +- name: 1.3 - Filesystem integrity checking w/AIDE + tags: + - 1.3.0 + block: + # use the system package manager to install AIDE + - name: 1.3.1 - Ensure aide is installed + ansible.builtin.package: + name: aide + state: present + tags: + - 1.3.1 + + # AIDE requires initialization the first time and it takes time on a large system. + # DUse stat module on the file that should be there if it is set up. + - name: 1.3.1 - Determine if AIDE has already been initialized + ansible.builtin.stat: + path: /var/lib/aide/aide.db.gz + register: aide_path + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database file location + ansible.builtin.replace: + dest: /etc/aide/aide.conf + regexp: "^database_in=file:((?!{{ aide_db_name }}).)*$" + replace: "database_in=file:{{ aide_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - Set up database_out file location + ansible.builtin.replace: + dest: /etc/aide/aide.conf + regexp: "^database_out=file:((?!{{ aide_new_db_name }}).)*$" + replace: "database_out=file:{{ aide_new_db_name }}" + tags: + - 1.3.1 + + - name: 1.3.1 - enable gzip compression for database + ansible.builtin.lineinfile: + dest: /etc/aide/aide.conf + regexp: '^gzip_dbout\s*=\s*((?!{{ aide_gzip }}).)*$' + line: "gzip_dbout={{ aide_gzip }}" + state: present + tags: + - 1.3.1 + + # stat returns a lot of information. 'exists' is true if the file exists and 'isreg' + # is true if the file is a regular file. If either of these are not true, then + # run the initializatoin again. + + - name: 1.3.1 - Initialize AIDE if it hasn't been already (/usr/sbin/aideinit) + ansible.builtin.command: /usr/sbin/aideinit + when: ( not aide_path.stat.exists or not aide_path.stat.isreg ) + tags: + - 1.3.1 + + # AIDE creates the new database as a different name. Use the copy module with + # the remote_src argument to copy the file on the remote machine to another location + # on the remote machine. + - name: 1.3.1 - Move the newly created database into place + ansible.builtin.copy: + src: /var/lib/aide/aide.db.new.gz + remote_src: true + dest: /var/lib/aide/aide.db.gz + mode: preserve + when: not aide_path.stat.exists or not aide_path.stat.isreg + changed_when: false + tags: + - 1.3.1 + + # Copy in the already configured systemd service file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the service + - name: 1.3.2 - Ensure File integrity is regularly checked (aidecheck service) + tags: + - 1.3.2 + notify: Restart aidecheck + block: + - name: 1.3.2 - Template in aidecheck.service file + ansible.builtin.template: + src: aidecheck.service + dest: /etc/systemd/system/aidecheck.service + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + + - name: 1.3.2 - Enable aidecheck.service + ansible.builtin.systemd: + name: aidecheck.service + enabled: true + + # Copy in the already configured systemd timer file using the copy module. + # Be sure to set the selinux context. + # Notify systemd to reload its daemons and start the timer + - name: 1.3.2 - Ensure File integrity is regulary checked (aidecheck timer) + ansible.builtin.template: + src: aidecheck.timer + dest: /etc/systemd/system/aidecheck.timer + owner: root + group: root + mode: 0644 + setype: systemd_unit_file_t + + - name: 1.3.2 - Enable aidecheck.timer + ansible.builtin.systemd: + name: aidecheck.timer + enabled: true + + - name: 1.3.3 Create aidecheck config.d dir + ansible.builtin.file: + path: /etc/aide.conf.d/ + owner: root + group: root + mode: 0644 + state: directory + tags: + - 1.3.3 + +# 1.4 Secure Boot settings +# Use file module to set permissions on grub files + +- name: 1.4.0 - Check if the EFI directory exists + ansible.builtin.stat: + path: "/boot/efi/EFI/{{ ansible_distribution | lower }}/grub2.cfg" + register: efidir + +- name: 1.4.0 - set variable for grub.cfg in EFI location + ansible.builtin.set_fact: + grub_cfg_path: "{{ efidir.stat.path }}" + when: efidir.stat.path is defined + +- name: 1.4.0 - Check if the EFI directory exists + ansible.builtin.stat: + path: "/boot/grub/grub.cfg" + register: grubdir + +- name: 1.4.0 - set variable for grub.cfg in EFI location + ansible.builtin.set_fact: + grub_cfg_path: "{{ grubdir.stat.path }}" + when: grubdir.stat.path +# Control 1.4.1, Grub bootloader password - skipped + +# Use file module to set permissions on grub files +- name: 1.4.2 - Set permissions on grub.cfg + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - "{{ grub_cfg_path }}" + - /boot/grub/grubenv + tags: + - 1.4.2 + + +# Use replace module to add the requirement to enter password on single user startup +- name: 1.4.3 - Set single user password + tags: + - 1.4.3 + block: + - name: 1.4.3 - Check if root has a password + ansible.builtin.lineinfile: + path: /etc/shadow + regexp: '^root:[*\!|*\*]:' + state: absent + check_mode: true + changed_when: false + register: root_pw_check + failed_when: false + + - name: 1.4.3 - Set root password + ansible.builtin.user: + name: root + password: "{{ root_password | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}" + when: root_pw_check.found != "0" and root_password is defined + + - name: 1.4.3 - Set root password + ansible.builtin.debug: + msg: "Root password is not set and no password provided. Set root_password variable per instructions and restart." + when: root_pw_check.found != "0" and root_password is not defined + +# 1.6 Additional Process Hardening + + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'yes'. +- name: 1.5.1 - Ensure address space layout reandomization (ASLR) is enabled + ansible.posix.sysctl: + name: kernel.randomize_va_space + value: "2" + reload: true + state: present + sysctl_set: true + tags: + - 1.5.1 + +# Use system package manager to remove the prelink package +- name: 1.5.2 - Remove prelink package + ansible.builtin.package: + name: prelink + state: absent + tags: + - 1.5.2 + +- name: 1.5.3 - Ensure Apport Error Reporting Service is disabled + tags: + - 1.5.3 + block: + - name: 1.5.3 - Ensure Apport Error Reporting Service is disabled + ansible.builtin.systemd: + name: apport.service + enabled: false + state: stopped + + - name: 1.5.3 - Ensure that it does not start again + ansible.builtin.lineinfile: + dest: "/etc/default/apport" + regexp: '^\ *enabled\ *=\ *[^0]\b' + line: "enabled=0" + state: present + +- name: 1.5.4 - Ensure core dumps are restricted + tags: + - 1.5.4 + block: + # The sysctl module will set variables in /etc/sysctl.conf and tell sysctl + # to reload them immediately if 'reload' is set to 'yes'. + - name: 1.5.4 - Ensure core dumps are restricted + ansible.posix.sysctl: + name: fs.suid_dumpable + value: "0" + state: present + reload: true + + # The pam_limits module will configure the lines in the limits files. + - name: 1.5.4 - Ensure core limits are set + community.general.pam_limits: + dest: /etc/security/limits.d/CIS.conf + domain: "*" + limit_type: hard + limit_item: core + value: "0" + + - name: 1.5.4 - Limit coredump storage if systemd-coredump package is installed + ansible.builtin.lineinfile: + dest: /etc/systemd/coredump.conf + regexp: "^Storage=((?!none).)*$" + line: "Storage=none" + insertafter: "#Storage=external" + when: "'systemd-coredump' in ansible_facts.packages" + notify: Reload systemctl + + - name: 1.5.4 - Limit coredump processsize if systemd-coredump package is installed + ansible.builtin.lineinfile: + dest: /etc/systemd/coredump.conf + regexp: "^ProcessSizeMax=((?!0).)*$" + line: "ProcessSizeMax=0" + insertafter: "#ProcessSizeMax=2G" + when: "'systemd-coredump' in ansible_facts.packages" + notify: Reload systemctl + +## 1.5.5 - Ensure ptrace_scope is restricted + +# 1.6.0 Mandatory Access Control + +- name: 1.6.0 - Install and configure Apparmor + tags: + - 1.6.0 + block: + - name: 1.6.1.1 - Ensure AppArmor is installed + ansible.builtin.package: + name: "{{ item }}" + state: present + loop: + - apparmor + - apparmor-utils + tags: + - 1.6.1.1 + + # this control wants to check /boot/grub/grub.conf, but it's possible that it exists in the config file, but + # not the boot file due to a failed grub rebuild. We should check the grub build file instead + - name: 1.6.1.2 - check to see if apparmor is in grub configuration + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*apparmor=1' + state: absent + check_mode: true + changed_when: false + register: apparmor_grub + failed_when: false + tags: + - 1.6.1.2 + + - name: 1.6.1.2 - add apparmor to grub config file + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor ' + notify: Rebuild ubuntu-grub + when: not apparmor_grub.found + tags: + - 1.6.1.2 + + # The controls here are a bit broken, 1.7.1.3 says to put them in either enforce or complain, and then 1.7.1.4 + # says to put them in enforcing (one is scored, the other is not though). Both ore enforced here, disable the tag for + # 1.6.1.4 if you don't want them all in enforcing + # + # The control is broken in that it wants the aa-[enforce|complain] script ran against all directory contents, but + # they are not all app armor profiles, so it will error. We are ignoring it at this point. + - name: 1.6.1.3 - Ensure all apparmor profiles are in enforce or complain mode + ansible.builtin.command: aa-{{ apparmor_level }} /etc/apparmor.d/* + when: apparmor_level == "enforce" or apparmor_level == "complain" + failed_when: false + changed_when: false + tags: + - 1.6.1.3 + + - name: 1.6.1.4 - Ensure all AppArmor profiles are enforcing + ansible.builtin.command: aa-enforce /etc/apparmor.d/* + when: apparmor_level == "enforce" + failed_when: false + changed_when: false + tags: + - 1.6.1.4 + +# 1.7 Warning Banners + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.1 - Install motd banners + ansible.builtin.copy: + src: "{{ motd_file }}" + dest: /etc/motd + owner: root + group: root + mode: 0644 + when: motd_use is defined and motd_use + tags: + - 1.7.1 + - 1.7.4 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.2 - Install issue banners + ansible.builtin.copy: + src: "{{ issue_file }}" + dest: /etc/issue + owner: root + group: root + mode: 0644 + when: issue_use is defined and issue_use + tags: + - 1.7.2 + - 1.7.5 + +# Use copy module to copy in the appropriate files based on variable and set permissions +- name: 1.7.3 - Install issue.net banners + ansible.builtin.copy: + src: "{{ issue_net_file }}" + dest: /etc/issue.net + owner: root + group: root + mode: 0644 + when: issue_net_use is defined and issue_net_use + tags: + - 1.7.3 + - 1.7.6 + +# 1.8 GDM + +# Disable GDM +- name: 1.8.1 - Remove GDM display manager + tags: + - 1.8.1 + block: + - name: 1.8.1 - Remove the GNOME display manager + ansible.builtin.apt: + name: gdm3 + state: absent + purge: true + when: "'gdm3' in ansible_facts.packages and not graphical_interface" + +# add a banner to the login screen if the graphical_interface variable is set to true +- name: 1.8.[2-3] Ensure GDM banner set up + when: graphical_interface is defined and graphical_interface + tags: + - 1.8.2 + - 1.8.3 + - 1.8.4 + block: + - name: 1.8.[2-4] - Make sure gdm settings files have been created + ansible.builtin.file: + path: "/etc/dconf/db/gdm.d" + owner: root + group: root + mode: 0755 + setype: etc_t + state: directory + + - name: 1.8.[2-4] - Set up dconf profile for gdm + ansible.builtin.blockinfile: + path: /etc/dconf/profile/gdm + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + block: | + user-db:user + system-db:gdm + file-db:/usr/share/gdm/greeter-dconf-defaults + + - name: 1.8.[2-3] - Create the defaults file and populate group + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + marker: "# {mark} Ansible Managed login-screen block" + block: | + [org/gnome/login-screen] + tags: + - 1.8.2 + - 1.8.3 + + - name: 1.8.2 - Enable login screen for gdm + ansible.builtin.blockinfile: + # Add our required pieces to the greeter defaults file + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + marker: "# {mark} Ansible Managed banner-message" + block: | + banner-message-enable=true + banner-message-text='Authorized users only. All activity may be monitored and reported.' + tags: + - 1.8.2 + + - name: 1.8.3 Ensure GDM disable-user list is enabled + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/01-banner-message + owner: root + group: root + mode: 0644 + setype: etc_t + insertafter: "[org/gnome/login-screen]" + marker: "# {mark} Ansible Managed disable-user-list" + block: | + disable-user-list=true + tags: + - 1.8.3 + + - name: 1.8.4 Set gdm timeouts + ansible.builtin.blockinfile: + path: /etc/dconf/db/gdm.d/00-screensaver + owner: root + group: root + mode: 0644 + setype: etc_t + create: true + marker: "# {mark} Ansible Managed Timeouts" + block: | + # Specify the dconf path + [org/gnome/desktop/session] + + # Number of seconds of inactivity before the screen goes blank + # Set to 0 seconds if you want to deactivate the screensaver. + idle-delay=uint32 {{ idle_delay }} + # Specify the dconf path + [org/gnome/desktop/screensaver] + # Number of seconds after the screen is blank before locking the screen + lock-delay=uint32 {{ lock_delay }} + tags: + - 1.8.4 + +# 1.8.5 TODO +# 1.8.6 TODO +# 1.8.7 TODO +# 1.8.8 TODO +# 1.8.9 TODO +# 1.8.10 - Ensure XDCMP is not enabled, skipping + +# 2 Services +# Ubuntu since 16.04 does not recommend NTP so we are only checking for timesyncd or chrony +- name: 2.1.1.1 - Ensure a single time sync daemon is in use + ansible.builtin.apt: + name: "{{ item }}" + state: absent + purge: true + loop: + - ntp + - systemd-timesyncd + when: time_service == "chrony" + +- name: 2.1.2.1 - Verify chrony is installed if selected + ansible.builtin.package: + name: "chrony" + state: present + when: time_service == "chrony" + tags: + - 2.1.2.1 + +# Use the template module to deploy the config file for the time sync program +- name: 2.1.2.1 - Configure chrony + ansible.builtin.template: + src: "chrony.conf" + dest: /etc/chrony/chrony.conf + owner: root + group: root + mode: 0644 + when: time_service == "chrony" + tags: + - 2.1.2.1 + +- name: 2.1.2.1 - Configure chrony - create conf dir + ansible.builtin.file: + path: /etc/chrony.d/ + owner: root + group: root + state: directory + mode: 0755 + when: time_service == "chrony" + tags: + - 2.1.2.1 + +- name: 2.1.2.2 - Ensure Chrony is running as user {{ time_user }} + ansible.builtin.copy: + dest: /etc/chrony.d/chrony.conf + owner: root + group: root + mode: 0644 + content: ! + user {{ time_user }} + when: time_service == "chrony" + tags: + - 2.1.2.1 + + +- name: 2.1.2.3 - Enable and run chronyd + ansible.builtin.systemd: + name: chrony + state: started + masked: false + enabled: true + when: time_service == "chrony" + tags: + - 2.1.2.3 + +- name: 2.2.3.1 - Configure timesyncd + ansible.builtin.template: + src: "timesyncd.conf" + dest: /etc/timesyncd.conf + owner: root + group: root + mode: 0644 + when: time_service == "timesyncd" + notify: Restart timesyncd + tags: + - 2.1.3.1 + +- name: 2.1.3.2 - Enable and start timesyncd + ansible.builtin.systemd: + name: systemd-timesyncd + masked: false + enabled: true + state: started + when: time_service == "timesyncd" + tags: + - 2.1.3.2 + +# 2.2.4.[1-4] - Ensure NTP configured is skipped as it is not recommended on ubuntu + +- name: 2.2.1 - Ensure x-window system is not installed + ansible.builtin.apt: + name: xserer-org + state: absent + purge: true + +# This collection of tasks creates a empty list and save it as a fact. +# For every item that is encountered (without the tag being skipped), +# add a string to the list. +- name: 2.2.2 - Create empty list for unneeded packages + ansible.builtin.set_fact: + unneeded_packages: [] + +- name: 2.2.2 - Remove avahi; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'avahi' ] }}" + tags: + - 2.2.3 + +- name: 2.2.4 - Remove dhcp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'isc-dhcp-server' ] }}" + when: dhcp_server is defined and not dhcp_server + tags: + - 2.2.4 + +- name: 2.2.5 - Remove slapd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'slapd' ] }}" + tags: + - 2.2.5 + +- name: 2.2.6,2.3.6 - Remove nfs server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'nfs-kernel-server' ] + ['rpcbind'] }}" + when: nfs_server is defined and not nfs_server + tags: + - 2.2.6 + +- name: 2.2.7 - Remove bind9; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'bind9' ] }}" + when: dns_server is defined and not dns_server + tags: + - 2.2.7 + +- name: 2.2.8 - Remove vsftpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'vsftpd' ] }}" + when: ftp_server is defined and not ftp_server + tags: + - 2.2.8 + +- name: 2.2.9 - Remove httpd; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'apache2'] }}" + when: http_server is defined and not http_server + tags: + - 2.2.9 + +- name: 2.2.10 - Remove dovecot; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'dovecot-imapd' ] + [ 'dovecot-pop3d'] }}" + when: email_server is defined and not email_server + tags: + - 2.2.10 + +- name: 2.2.11 - Remove samba; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" + when: smb_server is defined and not smb_server + tags: + - 2.2.11 + +- name: 2.2.12 - Remove squid; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" + tags: + - 2.2.12 + +- name: 2.2.13 - Remove snmp; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'snmp' ] }}" + tags: + - 2.2.13 + +- name: 2.2.14,2.3.1 - Remove nis; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'nis' ] }}" + tags: + - 2.2.14 + - 2.3.1 + +- name: 2.2.15 - Remove dnsmasq; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'dnsmasq' ] }}" + tags: + - 2.2.15 + +- name: 2.2.17 - Remove rsync; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'rsync' ] }}" + tags: + - 2.2.17 + + +- name: 2.3.2 - Remove rsh; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'rsh-client' ] }}" + tags: + - 2.3.2 + +- name: 2.3.3 - Remove talk; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'talk' ] }}" + tags: + - 2.3.3 + +- name: 2.3.4 - Remove telnet; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'telnet' ] }}" + tags: + - 2.3.4 + +- name: 2.3.5 - Remove ldap-utils; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'ldap-utils' ] }}" + tags: + - 2.3.5 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. +- name: Process removal list + ansible.builtin.apt: + name: "{{ unneeded_packages }}" + state: absent + purge: true + tags: + - 2.2.0 + - 2.3.0 + +# Cups should be remove per control 2.2.16, but it may not be able to due to +# dependencies, so disable the service instead +- name: 2.2.3 - Disable cups as we my not be able to uninstall it + ansible.builtin.service: + name: "{{ item }}" + enabled: false + state: stopped + when: "'cups' in ansible_facts.packages" + loop: + - cups.service + - cups.socket + - cups-browsed.service + tags: + - 2.2.3 + +# Use the stat module to determine if the mail server config file exists. +# If it does and we are to be a mail server, then modify it per the control. +- name: 2.2.15 - Configure email for local-only mode if mail software is installed and not intending to be an external email relay (mail_server=false) + tags: + - 2.2.15 + block: + - name: 2.2.15 - Find if we have a mail agent config file + ansible.builtin.stat: + path: /etc/postfix/main.cf + register: postfix_out + changed_when: false + + - name: 2.2.15 - If the file exists and not a mail server, then set loopback only + ansible.builtin.replace: + dest: /etc/postfix/main.cf + regexp: "^inet_interfaces = ((?!localhost).)*$" + replace: "inet_interfaces = loopback-only" + when: postfix_out.stat.exists and not email_server + notify: Restart postfix + +# Control 2.4 is a manual control, skipping + +# Section 3, Network parameters +# +# Control 3.1.1 Report on IPv6 status skipped +# Control 3.1.2 Ensure wireless interfaces are disabled is interface dependent +# skipping + +- name: 3.1.3 - Ensure bluetooth is disabled + ansible.builtin.systemd: + name: bluetooth + enabled: false + masked: true + state: stopped + when: "'bluez' in ansible_facts.packages and ( service_bluetooth is defined and not service_bluetooth )" + tags: + - 3.1.3 + +- name: 3.1.0 - Disable uncommon network protocols + tags: + - 3.1.0 + block: + # This collection of tasks creates a empty list and save it as a fact. + # For every item that is encountered (without the tag being skipped), + # add a string to the list. + - name: 3.1.0 - Create empty list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: [] + + - name: 3.4.1 - Add dccp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + [ 'dccp' ] }}" + tags: + - 3.4.1 + + - name: 3.4.2 - Add sctp to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + [ 'sctp' ] }}" + tags: + - 3.4.2 + + - name: 3.4.3 - Add rds to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + [ 'rds' ] }}" + tags: + - 3.4.3 + + - name: 3.4.4 - Add tipc to list of uncommon network protocols to disable + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + [ 'tipc' ] }}" + tags: + - 3.4.4 + +# With the list complete, use it with the system's package manager +# to remove packages from the system that are not needed. + - name: 3.1.0 - Process uncommon network list + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "install {{ item }} /bin/false" + state: present + create: true + owner: root + group: root + mode: 0644 + loop: + - uncommon_network + + - name: 3.1.3 - Add to blacklist + ansible.builtin.lineinfile: + dest: /etc/modprobe.d/CIS.conf + line: "blacklist {{ item }}" + state: present + create: true + owner: root + group: root + mode: 0644 + with_items: + - "{{ uncommon_network }}" + +# IPv4 network parameters +- name: 3.2.0 - Create empty dictionary for unneeded IPv4 network parameters + ansible.builtin.set_fact: + unneeded_ipv4_network: {} + +- name: 3.2.0 - Create empty dictionary for unneeded IPv6 network parameters + ansible.builtin.set_fact: + unneeded_ipv6_network: {} + +- name: 3.2.2 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.ip_forward' : '0'}) }}" + tags: + - 3.2.2 + +- name: 3.2.2 - Ensure IP forwarding is disabled + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ 'net.ipv6.conf.all.forwarding' : '0'}) }}" + when: not ipv6_disable + tags: + - 3.2.2 + +- name: 3.2.1 - Ensure packet redirect sending is disabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.send_redirects + - net.ipv4.conf.default.send_redirects + tags: + - 3.2.1 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.accept_source_route + - net.ipv4.conf.default.accept_source_route + tags: + - 3.3.1 + +- name: 3.3.1 - Ensure source routed packets are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + loop: + - net.ipv6.conf.all.accept_source_route + - net.ipv6.conf.default.accept_source_route + when: not ipv6_disable + tags: + - 3.3.1 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.accept_redirects + - net.ipv4.conf.default.accept_redirects + tags: + - 3.3.2 + +- name: 3.3.2 - Ensure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + loop: + - net.ipv6.conf.all.accept_redirects + - net.ipv6.conf.default.accept_redirects + when: not ipv6_disable + tags: + - 3.3.2 + +- name: 3.3.3 - Ensure secure ICMP redirects are not accepted + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + loop: + - net.ipv4.conf.all.secure_redirects + - net.ipv4.conf.default.secure_redirects + tags: + - 3.3.3 + +- name: 3.3.4 - Ensure suspicious packets are logged + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '1' }) }}" + loop: + - net.ipv4.conf.all.log_martians + - net.ipv4.conf.default.log_martians + tags: + - 3.3.4 + +- name: 3.3.5 - Ensure broadcast ICMP requests are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.icmp_echo_ignore_broadcasts' : '1' }) }}" + tags: + - 3.3.5 + +- name: 3.3.6 - Ensure bogus ICMP responses are ignored + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.icmp_ignore_bogus_error_responses' : '1' }) }}" + tags: + - 3.3.6 + +- name: 3.3.7 - Ensure reverse path filtering is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '1' }) }}" + loop: + - net.ipv4.conf.all.rp_filter + - net.ipv4.conf.default.rp_filter + tags: + - 3.3.7 + +- name: 3.3.8 - Ensure TCP SYN Cookies is enabled + ansible.builtin.set_fact: + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.tcp_syncookies' : '1' }) }}" + tags: + - 3.3.8 + +- name: 3.3.9 - Ensure IPv6 router advertisements are not accepted + ansible.builtin.set_fact: + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + when: not ipv6_disable + loop: + - net.ipv6.conf.all.accept_ra + - net.ipv6.conf.default.accept_ra + tags: + - 3.3.9 + +- name: 3.3 - list of IPv4 network settings + ansible.builtin.debug: + var: unneeded_ipv4_network + +- name: 3.3 - list of IPv6 network settings + ansible.builtin.debug: + var: unneeded_ipv6_network + +# The sysctl module will configure certain sysctl parameters. They are +# collected into a loop here to speed the implementation +# Once complete, notify the system to flush the network routes +- name: 3.3 - Process unneeded network settings for IPv4 + tags: + - 3.3.0 + block: + - name: 3.3 - Set networking parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + reload: true + state: present + sysctl_set: true + loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv4_network) }}" + notify: Flush network routes + + +# Section 3 - Firewall + +- name: 3.5.1 - Install firewall package + when: enable_firewall is defined and enable_firewall == "ufw" + tags: + - 3.5.1 + block: + - name: 3.5.1.1 - Install ufw + ansible.builtin.package: + name: "ufw" + state: present + notify: Start ufw # 3.5.1.2 + + - name: 3.5.1.2 - Ensure iptbles-persistent is not installed with ufw + ansible.builtin.package: + name: "iptables-persistent" + state: absent + + - name: 3.5.1.4 - implement loopback rules (1/3) + community.general.ufw: + rule: allow + direction: in + interface: lo + notify: Start ufw # 3.5.1.2 + + - name: 3.5.1.4 - implment loopback rules (2/3) + community.general.ufw: + rule: deny + direction: in + from_ip: 127.0.0.0/8 + notify: Start ufw # 3.5.1.2 + + - name: 3.5.1.4 - implment loopback rules (3/3) + community.general.ufw: + rule: deny + direction: in + from_ip: '::1' + notify: Start ufw # 3.5.1.2 + + - name: Display upcoming skips + ansible.builtin.debug: + msg: | + 3.5.1.5 - Configure outbound connections must be handled locally + 3.5.1.6 - Ensure firewall rules exist for all open ports must be handled locally" - "3.4.1.7 - Ensure ufw default deny firewall policy + +- name: 3.5.2 - Install firewall package - nftables + when: enable_firewall is defined and enable_firewall == "nftables" + tags: + - 3.5.2 + block: + - name: 3.5.2.1 - ensure nftables is installed + ansible.builtin.package: + name: nftables + state: present + tags: + - 3.5.2 + + - name: 3.5.2.2 - Ensure ufw is uninstalled with nftables + ansible.builtin.package: + name: ufw + state: absent + tags: + - 3.5.2.2 + + - name: 3.5.2.3 - Flush IPTables + ansible.builtin.iptables: + flush: true + tags: + - 3.5.2.3 + + - name: 3.5.2.4 - Configure nftables + tags: + - 3.5.2.4 + - 3.5.2.5 + - 3.5.2.6 + - 3.5.2.7 + - 3.5.2.8 + block: + - name: 3.5.2.4 - Find any current netfilter tables + ansible.builtin.command: nft list tables + register: tables_list + changed_when: false + + - name: 3.5.2.4 - Create a basic table if none exist + ansible.builtin.command: nft create table inet firewalld NFTables + when: not tables_list + + - name: 3.5.2.5 - Create base chains - input chain + ansible.builtin.command: nft list ruleset | grep 'hook input' + register: "nftables_chains_input" + when: not tables_list + + - name: 3.5.2.5 - Create chains if they don't exist - input chain + ansible.builtin.command: nft create chain inet {{ tables_list|split }}.1 + when: nftables_chians_output and nftables_chians_output | length > 0 and not tables_list + tags: + - 3.5.2.5 + + - name: 3.5.2.5 - Create base chains - output chain + ansible.builtin.command: nft list ruleset | grep 'hook output' + register: "nftables_chains_output" + when: not tables_list + + - name: 3.5.2.5 - Create chains if they don't exist - output chain + ansible.builtin.command: nft create chain inet {{ tables_list|split}}.1 + when: nftables_chians_input and nftables_chians_input | length > 0 and not tables_list + tags: + - 3.5.2.5 + + - name: 3.5.2.5 - Create base chains - forward chain + ansible.builtin.command: nft list ruleset | grep 'hook forward' + register: "nftables_chains_forward" + when: not tables_list + + - name: 3.5.2.5 - Create chains if they don't exist + ansible.builtin.command: nft create chain inet {{ tables_list|split }}.1 + when: nftables_chians_input and nftables_chians_input | length > 0 and not tables_list + tags: + - 3.5.2.5 + + - name: 3.5.2.6 - Configure loopback interface + ansible.builtin.command: "{{ item }}" + when: not tables_list + loop: + - "nft add rule inet filter input iif lo accept" + - "nft create rule inet filter input ip saddr 127.0.0.0/8 counter drop" + tags: + - 3.5.2.6 + + - name: 3.5.2.7 - Configure outbound and established connections + when: no tables_list + ansible.builtin.command: "{{ item }}" + loop: + - "nft add rule inet filter input ip protocol tcp ct state established accept" + - "nft add rule inet filter input ip protocol udp ct state established accept" + - "nft add rule inet filter input ip protocol icmp ct state established accept" + - "nft add rule inet filter output ip protocol tcp ct state new,related,established accept" + - "nft add rule inet filter output ip protocol udp ct state new,related,established accept" + - "nft add rule inet filter output ip protocol icmp ct state new,related,established accept" + + # 3.5.2.8 - Ensure nftables default deny firewall - easy to mess up, skipping + + - name: 3.5.2.9 - Ensure nftables service is enabled + ansible.builtin.systemd: + name: nftables + state: started + enabled: true + + # 3.5.2.9 requires manual review - skipping + +- name: Configure iptables + tags: + - 3.5.3 + when: enable_firewall is defined and enable_firewall == "nftables" + block: + - name: 3.5.3.1.1 - Ensure iptables packages are installed + ansible.builtin.package: + name: "{{ item }}" + state: present + loop: + - iptables + - iptables-persistent + tags: + - 3.5.3.1.1 + + - name: 3.5.3.1.[2-3] - Ensure other firewall proucts are not installed with iptables + ansible.builtin.package: + name: "{{ item }}" + state: absent + loop: + - nftables + - ufw + tags: + - 3.5.3.1.2 + - 3.5.3.1.3 + + # 3.5.3.2.1-4 are machine and policy dependent, skipping + # 3.5.3.3 1-4 are machine and policy dependent, skipping + +# Section 4 - Logging and Auditing + +- name: 4.1.1 - Configure Auditd + tags: + - 4.1.1 + when: enable_audit is defined and enable_audit + block: + - name: 4.1.1.1 - Install Audit + ansible.builtin.package: + name: + - auditd + - audispd-plugins + state: present + tags: + - 4.1.1.1 + + - name: 4.1.1.2 - Enable auditd service + ansible.builtin.service: + name: auditd + enabled: true + state: started + tags: + - 4.1.1.2 + + - name: 4.1.1.3 - Ensure auditing for processes that start prior to auditd + # We check here because we don't know what position the audit=1 is in + # order to simply do the replace, so we are instead looking for the match in the file first. + # If it doesn't exist, then we can just insert it + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit=1' + state: absent + check_mode: true + changed_when: false + register: audit_exist + failed_when: false + tags: + - 4.1.1.3 + + # use the replace module to add it to grub bootloader and then notify + # grub to rebuild + - name: 4.1.1.3 - enable audit service in grub + ansible.builtin.replace: + path: /etc/default/grub + regexp: '^GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit=1 ' + notify: Rebuild ubuntu-grub + when: not audit_exist.found + tags: + - 4.1.1.3 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, check if limit exists + ansible.builtin.lineinfile: + path: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX.*audit_backlog_limit' + state: absent + check_mode: true + changed_when: false + register: audit_backlog_exist + failed_when: false + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, add audit_backlog_limit to grub + ansible.builtin.replace: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX="' + replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' + notify: Rebuild ubuntu-grub + when: not audit_backlog_exist.found + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (check) + ansible.builtin.lineinfile: + dest: /etc/default/grub + regexp: '^\s*GRUB_CMDLINE_LINUX=.*{{ audit_backlog_limit }}' + state: absent + check_mode: true + changed_when: false + register: our_limit + when: audit_backlog_exist.found + tags: + - 4.1.1.4 + + - name: 4.1.1.4 - Ensure audit_backlog_limit is sufficient, update limit if it already exists (fix) + ansible.builtin.replace: + dest: /etc/default/grub + regexp: 'audit_backlog_limit=[\S]*' + replace: 'audit_backlog_limit={{ audit_backlog_limit }}' + notify: Rebuild ubuntu-grub + when: audit_backlog_exist.found and not our_limit.found + tags: + - 4.1.1.4 + + # The replace module here is looking through file and make replacements of partial lines + - name: 4.1.1.[1-2] - Configure audit log storage size + ansible.builtin.replace: + path: /etc/audit/auditd.conf + regexp: "{{ item.find }}" + replace: "{{ item.replace }}" + loop: + - {find: '^max_log_file\s+=\s+[^{{ log_file_size }}]', replace: 'max_log_file = {{ log_file_size }}'} # 4.1.2.1 + - {find: '^max_log_file_action\s+=\s+((?!keep_logs).)*$', replace: 'max_log_file_action = keep_logs'} # 4.1.2.2 + - {find: '^space_left_action\s+=\s+((?!email).)*$', replace: 'space_left_action = email'} # 4.1.2.2 + - {find: '^action_mail_acct\s+=\s+((?!root).)*$', replace: 'action_mail_acct = root'} # 4.1.2.2 + - {find: '^admin_space_left_action\s+=\s+((?!suspend).)*$', replace: 'admin_space_left_action = suspend'} # 4.1.2.2 + notify: Restart auditd + tags: + - 4.1.2.1 + - 4.1.2.2 + - 4.1.2.3 + + # For the next several checks, each one is in their own file, so we are using + # the copy module to place each file independently and then motifying + # a restart of auditd if anything changes. + + # cis-security versions before 1.5.0 did not enumerate the files, so the old files + # need to be removed to make way for the new versions + - name: 4.1.3 - Remove old rules files that were not in correct order (pre v1.5.0) + ansible.builtin.file: + path: "/etc/audit/rules.d/{{ item }}" + state: absent + tags: + - 4.1.3 + loop: + - sudolog.rules + - sudoers.rules + - user_emulation.rules + - datetime.rules + - network.rules + - file-system-mounts.rules + - bad-file-access.rules + - user-group-info.rules + - dac.rules + - sessions.rules + - delete.rules + - login.rules + - MAC-policy.rules + - chcon.rules + - setfacl.rules + - chacl.rules + - usermod.rules + - modules.rules + + - name: 4.1.3 - Remove default rule that comes with package + ansible.builtin.file: + path: "/etc/audit/rules.d/audit.rules" + state: absent + notify: Restart auditd + tags: + - 4.1.3 + + # This isn't the best way to do this since users can modify the line and it will + # break this control check. It needs to be re-evaluated. + - name: 4.1.3.[1,3] - Ensure changes to sudoers is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sudolog.rules + src: audit_rules/sudolog.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.1 + - 4.1.3.3 + + - name: 4.1.3.4 Ensure to collect events that modify date/time + ansible.builtin.template: + dest: /etc/audit/rules.d/00-datetime.rules + src: audit_rules/datetime.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.4 + + - name: 4.1.3.5 - Ensure modifications to network environment are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-network.rules + src: audit_rules/network.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.5 + + # Control 4.1.3.6 - Ensure use of privileged commands is collected, is machine dependent + # skipping + + - name: 4.1.3.7 - Ensure unsuccessful unauthorized file access attempts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-bad-file-access.rules + src: audit_rules/bad-file-access.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.7 + + - name: 4.1.3.8 - Ensure events that modify user/group information are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-user-group-info.rules + src: audit_rules/user-group-info.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.8 + + # For the next several checks, each one is in their own file, so we are using + # the copy module to place each file independently and then motifying + # a restart of auditd if anything changes. + + + - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-dac.rules + src: audit_rules/dac.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.9 + + - name: 4.1.3.10 - Ensure successful file system mounts are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-file-system-mounts.rules + src: audit_rules/file-system-mounts.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.10 + + - name: 4.1.3.11 - Ensure session initiation information is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-sessions.rules + src: audit_rules/sessions.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.11 + + - name: 4.1.3.12 - Ensure system logins are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-login.rules + src: audit_rules/login.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.12 + + - name: 4.1.3.13 - Ensure file deletion events by users are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-delete.rules + src: audit_rules/delete.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.13 + + - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-MAC-policy.rules + src: audit_rules/MAC-policy.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.14 + + - name: 4.1.3.15 - Ensure successful and unsuccessful attempts to use the chcon command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chcon.rules + src: audit_rules/chcon.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.15 + + - name: 4.1.3.16 - Ensure successful and unsuccessful attempts to use the setfacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-setfacl.rules + src: audit_rules/setfacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.16 + + - name: 4.1.3.17 - Ensure successful and unsuccessful attempts to use the chacl command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-chacl.rules + src: audit_rules/chacl.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.17 + + - name: 4.1.3.18 - Ensure successful and unsuccessful attempts to use the usermod command are recorded + ansible.builtin.template: + dest: /etc/audit/rules.d/00-usermod.rules + src: audit_rules/usermod.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.18 + + - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected + ansible.builtin.template: + dest: /etc/audit/rules.d/00-modules.rules + src: audit_rules/modules.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.19 + + - name: 4.1.3.20 - Ensure audit configuration is immutable + ansible.builtin.copy: + dest: /etc/audit/rules.d/99-finalize.rules + content: | + -e 2 + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + - 4.1.3.20 + + # 4.1.3.21 - Ensure running and on disk configuration is the same requires manual review - skipping + + - name: 4.1.4.[1-2,4-7] - Set auditd files to mode 600, user root, group root + ansible.builtin.copy: + dest: "{{ item }}" + owner: root + group: root + mode: 0600 + force: false + content: "" + loop: + - "/etc/audit/auditd.conf" + - "{{ log_file }}" + tags: + - 4.1.4.1 + - 4.1.4.2 + - 4.1.4.3 + - 4.1.4.4 + - 4.1.4.5 + - 4.1.4.6 + - 4.1.4.7 + + - name: 4.1.4.[8-10] - Ensure audit tools are 0755 or less permissive + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 'go-w' + loop: + - "/sbin/auditctl" + - "/sbin/aureport" + - "/sbin/ausearch" + - "/sbin/autrace" + - "/sbin/auditd" + - "/sbin/augenrules" + tags: + - 4.1.4.8 + - 4.1.4.9 + - 4.1.4.10 + + - name: 4.1.4.11 Ensure cryptographic mechanisms are used to protect the integrity of audit tools + ansible.builtin.blockinfile: + path: /etc/aide.conf.d/crypt.conf + owner: root + group: root + create: true + mode: 0644 + block: | + # Audit Tools + /sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 + /sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 + tags: + - 4.1.4.11 + +- name: 5.1 - Configure time-based job schedulers + tags: + - 5.1.0 + block: + - name: 5.1.0 - Create the cron/at allow files + ansible.builtin.copy: + dest: "{{ item }}" + content: "" + force: false + owner: root + group: root + mode: 0644 + with_items: + - /etc/cron.allow + - /etc/at.allow + + - name: 5.1.2 - Ensure permissions on /etc/crontab + ansible.builtin.file: + path: /etc/crontab + owner: root + group: root + mode: 0600 + tags: + - 5.1.2 + + - name: 5.1.[3-7] - Ensure permissions on crontab directories + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0700 + state: directory + loop: + - /etc/cron.hourly + - /etc/cron.daily + - /etc/cron.weekly + - /etc/cron.monthly + - /etc/cron.d + tags: + - 5.1.3 + - 5.1.4 + - 5.1.5 + - 5.1.6 + - 5.1.7 + + - name: 5.1.8 - Ensure cron is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/cron.allow + owner: root + group: root + mode: 0600 + state: file + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.8 - Ensure cron is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/cron.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ cron_allow }}" + when: cron_allow and cron_allow | length > 0 + tags: + - 5.1.8 + + - name: 5.1.9 - Ensure at is restricted to authorized users - Create file + ansible.builtin.file: + path: /etc/at.allow + owner: root + group: root + mode: 0600 + state: file + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + + - name: 5.1.9 - Ensure at is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/at.allow + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" + owner: root + group: root + mode: 0600 + create: true + loop: + - "{{ at_allow }}" + when: at_allow and at_allow | length > 0 + tags: + - 5.1.9 + + - name: 5.1.1 - Ensure cron daemon is enabled an activated + ansible.builtin.systemd: + name: cron + enabled: true + state: started + masked: false + tags: + - 5.1.1 + +# If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag +- name: 5.2 - SSH File configurations + tags: + - 5.2.0 + block: + - name: 5.2.1 - Set permissions on SSH file + ansible.builtin.file: + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: 0600 + tags: + - 5.2.1 + + - name: 5.2.2 - Set Permissions on ssh private host keys + tags: + - 5.2.2 + block: + - name: 5.2.2 - Find all ssh private host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key + register: ssh_host_out + changed_when: false + + - name: 5.2.2 - Set permissions on all ssh private host keys (Red Hat set the group to ssh_keys and mode to 640) + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0600 + loop: "{{ ssh_host_out.files }}" + + - name: 5.2.3 - Set Permissions on ssh public host keys + tags: + - 5.2.3 + block: + - name: 5.2.3 - Find all ssh public host keys + ansible.builtin.find: + paths: /etc/ssh + file_type: file + patterns: ssh_host_*_key.pub + register: ssh_hostpub_out + changed_when: false + + - name: 5.2.3 - Set permissions on all ssh public host keys + ansible.builtin.file: + dest: "{{ item.path }}" + owner: root + group: root + mode: 0644 + loop: "{{ ssh_hostpub_out.files }}" + + - name: 5.2.4 - Ensure SSH access is limited + tags: + - 5.2.4 + block: + - name: 5.2.4 - Ensure SSH access is limited (AllowedUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowUsers', value: "{{ ssh_allowed_users }}" } + when: ssh_allowed_users is defined and ssh_allowed_users + + - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'AllowGroups', value: "{{ ssh_allowed_groups }}" } + when: ssh_allowed_groups is defined and ssh_allowed_groups + + - name: 5.2.4 - Ensure SSH access is limited (DenyUsers) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: "^{{ item.key }}\ *{{ item.value }}" + line: "{{ item.key }} {{ item.value }}" + loop: + - { key: 'DenyUsers', value: "{{ ssh_denied_users }}" } + notify: Restart sshd + when: ssh_denied_users is defined and ssh_denied_users + + - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" + notify: Restart sshd + loop: + - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } + when: ssh_denied_groups is defined and ssh_denied_groups + + - name: 5.2.5 - Set LogLevel to {{ ssh_log_level }} + ansible.builtin.replace: + path: /etc/ssh/sshd_config + replace: "LogLevel {{ ssh_log_level | upper }}" + regexp: '^LogLevel\s*(QUIET|FATAL|ERROR|DEBUG)*$' + notify: Restart sshd + when: ssh_log_level == "INFO" or ssh_log_level == "WARN" + tags: + - 5.2.5 + + - name: 5.2.6 - Ensure SSH is configured to use PAM + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "UsePAM yes" + regexp: '^UsePAM\s+[yes|no]' + insertafter: "#UsePAM" + notify: Restart sshd + tags: + - 5.2.6 + + - name: 5.2.7 - Ensure PermitRootLogin is disbled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "PermitRootLogin no" + regexp: '^PermitRootLogin\s*[^n]' + insertafter: '^#PermitRootLogin\s*[^n]' + notify: Restart sshd + tags: + - 5.2.7 + + - name: 5.2.8 - Ensure HostbasedAuthentication is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "HostbasedAuthentication no" + regexp: '^HostbasedAuthentication\s*[^n]' + insertafter: '^#HostbasedAuthentication\s*[^n]' + notify: Restart sshd + tags: + - 5.2.8 + + - name: 5.2.9 - Ensure SSH PermitEmptyPasswords is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitEmptyPasswords\s*[^n]' + notify: Restart sshd + tags: + - 5.2.9 + + - name: 5.2.10 - Ensure PermitUserEnvironment is disabled + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + state: absent + regexp: '^PermitUserEnvironment\s*[^n]' + notify: Restart sshd + tags: + - 5.2.10 + + - name: 5.2.11 - Ensure IgnoreRhosts is set + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "IgnoreRhosts yes" + regexp: '^IgnoreRhosts\s*[^y]' + insertafter: '^#IgnoreRhosts\s*[^y]' + notify: Restart sshd + tags: + - 5.2.11 + + - name: 5.2.12 - Disable X11 forwarding + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^X11Forwarding\s*yes' + state: absent + notify: Restart sshd + tags: + - 5.2.12 + + - name: 5.2.13 - Ensure correct cipherlist + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "Ciphers {{ ssh_ciphers_list }}" + regexp: '^Ciphers ((?!{{ ssh_ciphers_list }}).)*$ ' + notify: Restart sshd + tags: + - 5.2.13 + + - name: 5.2.14 - Set approved MAC algorithms + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "MACs {{ ssh_mac_list }}" + insertafter: EOF + regexp: '^MACs ((?!{{ ssh_mac_list }}).)*$ ' + notify: Restart sshd + tags: + - 5.2.14 + +# - name: 5.2.15 - Set approved KEX algorithms +# ansible.builtin.lineinfile: +# path: /etc/ssh/sshd_config +# line: "KexAlgorithms {{ ssh_kex_list }}" +# insertafter: EOF +# regexp: '^KexAlgorithms ((?!{{ ssh_kex_list }}).)*$ ' +# notify: Restart sshd +# tags: +# - 5.2.15 + + - name: 5.2.16 - Disable TCP Forwarding + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "AllowTcpForwarding no" + regexp: '^AllowTcpForwarding\s+(yes|no)' + insertafter: "^#AllowTcpForwarding" + notify: Restart sshd + tags: + - 5.2.16 + + - name: 5.2.17 - Ensure SSH Banner is configured + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "Banner /etc/{{ ssh_login_banner }}" + regexp: "^Banner /etc/{{ ssh_login_banner }}" + insertafter: "^#Banner none" + notify: Restart sshd + tags: + - 5.2.17 + + - name: 5.2.18 - Ensure SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "MaxAuthTries {{ ssh_max_auth_tries }}" + regexp: '^MaxAuthTries\s*[^1-{{ ssh_max_auth_tries | int + 1 }}]' + insertafter: "^#MaxAuthTries" + notify: Restart sshd + tags: + - 5.2.18 + + - name: 5.2.19 - Limit max unauthenticated startups + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxStartups 10:30:60" + regexp: '^MaxStartups\s+10:30:60' + insertafter: '^#MaxStartups\s+10:30:100' + notify: Restart sshd + tags: + - 5.2.19 + + - name: 5.2.20 - Ensure SSH LoginGraceTime is set to {{ ssh_grace_time }} + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "LoginGraceTime {{ ssh_grace_time }}" + regexp: "^LoginGraceTime {{ ssh_grace_time }}" + insertafter: "^#LoginGraceTime" + notify: Restart sshd + tags: + - 5.2.20 + + - name: 5.2.21 - Limit max sessions to {{ ssh_max_sessions }} + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + line: "MaxSessions {{ ssh_max_sessions }}" + regexp: '^MaxSessions\s+[{{ ssh_max_sessions }}]' + insertafter: '^#MaxSessions\s+10' + notify: Restart sshd + tags: + - 5.2.21 + + - name: 5.2.22 - Ensure SSH Idle Timeout is configured ClientAliveInterval + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveInterval {{ ssh_alive_interval }}" + regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" + insertafter: "^#ClientAliveInterval" + notify: Restart sshd + tags: + - 5.2.22 + + - name: 5.2.22 - Ensure SSH Idle Timeout is configured ClientAliveCountMax + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + line: "ClientAliveCountMax {{ ssh_alive_count_max }}" + regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" + insertafter: "^#ClientAliveCountMax" + notify: Restart sshd + tags: + - 5.2.22 + +# use the system package module to ensure sudo is installed +- name: 5.3 - Configure sudo/su + tags: + - 5.3.0 + block: + - name: 5.3.1 - Ensure sudo is installed + ansible.builtin.package: + name: sudo + state: present + tags: + - 5.3.1 + + # Make sure the sudoers file includes the requirement to use pty + - name: 5.3.2 - Ensure sudo commands use pty + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*use_pty' + line: "Defaults use_pty" + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.2 + + # Make sure the sudoers file includes the requirement to log to a file + - name: 5.3.3 - Ensure sudo log file exists + ansible.builtin.lineinfile: + path: /etc/sudoers + regexp: '^Defaults\s*logfile="{{ sudo_log }}"' + line: 'Defaults logfile="{{ sudo_log }}"' + insertafter: "^# Defaults specification" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.3 + + # 5.3.4 - Ensure users must provide passwords for priv escalation will + # break ansible, skipped + + - name: 5.3.5 - Ensure reauthticate for priv escalation is not enabled globally + ansible.builtin.replace: + path: /etc/sudoers + regexp: '^[^#].*\!authenticate' + replace: "" + validate: /usr/sbin/visudo -cf %s + tags: + - 5.3.5 + + # 5.3.6 - sudo timeout is hard to set via ansible, skipping + + - name: 5.3.7 - Restrict su to wheel group + tags: + - 5.3.7 + block: + - name: 5.3.7 - Configure PAM to only allow su from wheel group + ansible.builtin.replace: + path: /etc/pam.d/su + regexp: '^#\s*auth\s+required\s+pam_wheel.so' + replace: "auth required pam_wheel.so" + + - name: 5.3.7 - Create wheel group if it doesn't exist + ansible.builtin.group: + name: wheel + system: true + state: present + + - name: 5.3.7 - Add root to the wheel group + ansible.builtin.user: + name: root + groups: wheel + append: true + +- name: 5.4.0 - Configure PAM files and password requirements + tags: + - 5.4.0 + block: + - name: 5.4.1 - Configure PAM files and password requirements + ansible.builtin.package: + name: libpam-pwquality + state: present + tags: + - 5.4.1 + - 5.4.2 + + - name: 5.4.1 - require at least one digit in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: dcredit = -1 + regexp: "^dcredit = -1" + insertafter: "# dcredit = 0" + when: password_req_digit + tags: + - 5.4.1 + + - name: 5.4.1 - require at least one uppercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ucredit = -1 + regexp: "^ucredit = -1" + insertafter: "# ucredit = 0" + when: password_req_upper + tags: + - 5.4.1 + + - name: 5.4.1 - require at least one lowercase letter in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: lcredit = -1 + regexp: "^lcredit = -1" + insertafter: "^# lcredit = 0" + when: password_req_lower + tags: + - 5.4.1 + + - name: 5.4.1 - Require at least one special character in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: ocredit = -1 + regexp: "^ocredit = -1" + insertafter: "^# ocredit = 0" + when: password_req_digit + tags: + - 5.4.1 + + - name: 5.4.2 - Require at least {{ password_min_length }} characters in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: minlen = {{ password_min_length }} + regexp: "^minlen = {{ password_min_length }}" + insertafter: "^# minlen = 8" + when: password_req_digit + tags: + - 5.4.2 + + - name: 5.4.2 - Ensure lockout attempts for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" + line: "deny = {{ password_failed_attempts }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.4.2 + + - name: 5.4.2 - Ensure lockout time for failed password attempts is configured + ansible.builtin.lineinfile: + path: /etc/security/faillock.conf + regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" + line: "unlock_time={{ password_failed_time }}" + insertafter: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.4.2 + + # 5.4.3 - Password retention involves configuring pam.conf, skipping + + - name: 5.4.4 - Ensure password hashing algorithm is SHA-512 or yescrypt - check common-password + ansible.builtin.lineinfile: + path: /etc/pam.d/common-password + regexp: '^\s*[sha512|yescrypt]*' + state: absent + check_mode: true + changed_when: false + register: pwcommon_exist + failed_when: false + tags: + - 5.4.4 + + - name: 5.4.4 - Ensure password hashing alogrithm is SHA-512 or yescrypt - Tell user if it doesn't exist in common-password + ansible.builtin.debug: + msg: "/etc/pam.d/common-password does not include sha512 or yescrypt as a hashing algorithm. Please review and check" + when: pwcommon_exist and not pwcommon_exist.found + tags: + - 5.4.4 + + - name: 5.4.4 - Ensure password hashing algorithm is SHA-512 or yescrypt + ansible.builtin.replace: + path: /etc/login.defs + regexp: "^ENCRYPT_METHOD\ *(SHA512|YESCRYPT)" + replace: "ENCRYPT_METHOD {{ password_hash_alg | upper }}" + tags: + - 5.4.4 + +- name: 5.5.1.2 - Ensure password expiration is {{ password_expire_days }} days or less + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MAX_DAYS\s*((?!{{ password_expire_days }}).)*$' + line: "PASS_MAX_DAYS {{ password_expire_days }}" + state: present + tags: + - 5.5.1.2 + +- name: 5.5.1.1 - Ensure password change days is set to {{ password_min_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_MIN_DAYS\s*((?!{{ password_min_days }}).)*$' + line: "PASS_MIN_DAYS {{ password_min_days }}" + state: present + tags: + - 5.5.1.1 + +- name: 5.5.1.3 - Ensure password warning days is set to {{ password_warning_days }} + ansible.builtin.lineinfile: + dest: /etc/login.defs + regexp: '^PASS_WARN_AGE\s*((?!{{ password_warning_days }}).)*$' + line: "PASS_WARN_AGE {{ password_warning_days }}" + state: present + tags: + - 5.5.1.3 + +# We need to do this the hard way because the user module that calls /usr/sbin/useradd does not support setting inactive days +# The defaults perms are 0644 on the file, but after useradd is run against it, it changes to 0600, so we'll change it as well +- name: 5.5.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration + ansible.builtin.replace: + path: /etc/default/useradd + regexp: "^INACTIVE=((?!{{ password_inactive_lock_days }}).)*$" + replace: "INACTIVE={{ password_inactive_lock_days }}" + owner: root + group: root + mode: 0600 + tags: + - 5.5.1.4 + +# 5.5.1.5, Ensure all users last password change date is in the past, +# is not easily automated. Will revisit later + +# 5.5.1.6 - TODO +# 5.5.1.7 - TODO + +# 5.5.2, Ensure system accounts are secured, is machine dependent. +# skipping + +# Control is actually setting to GID of 0 and the user module takes a group name, not a GID, so have to use usermod +- name: 5.5.3 - Ensure default group for root is GID 0 + ansible.builtin.command: /usr/sbin/usermod -g 0 root + changed_when: false + tags: + - 5.5.3 + +- name: 5.5.4 - Ensure umask is set + ansible.builtin.replace: + path: "{{ item }}" + replace: " umask {{ default_umask }}" + regexp: '^\s*umask\s*022' + loop: + - /etc/bash.bashrc + - /etc/profile + tags: + - 5.5.4 + +- name: 5.5.5 - Ensure default shell timeout is {{ shell_timeout }} seconds or less + ansible.builtin.blockinfile: + path: "{{ item }}" + block: "TMOUT={{ shell_timeout }}" + marker: "# {mark} Ansible Managed CIS Timeout" + loop: + - /etc/bash.bashrc + - /etc/profile + tags: + - 5.5.5 + +- name: 5.5.6 - Remove nologin from /etc/shells + ansible.builtin.lineinfile: + dest: /etc/shells + regexp: '/nologin\b' + state: absent + tags: + - 5.5.6 + +- name: 5.5.7 - require at least one digit in passwords + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + line: "maxrepeat = {{ password_max_repeat }}" + regexp: "^maxrepeat\ *=*" + insertafter: "# maxrepeat = 0" + when: password_max_repeat + tags: + - 5.5.7 + +- name: 5.1.1 - Configure journald + when: log_service and log_service == "journald" + tags: + - 5.1.1 + block: + - name: 5.1.1.1.1 - Ensure systemd-journald-remote is installed + ansible.builtin.package: + name: systemd-journal-remote + state: present + tags: + - 5.1.1.1.1 + + # Control 5.1.1.1.2 is machine dependent, skipping + # Control 5.1.1.1.3 required 5.1.1.1.2 be configured prior. skipping + + - name: 5.1.1.1.4 - Ensure systemd-jornal-remote.socket is masked + ansible.builtin.systemd: + name: systemd-journal-remote.socket + enabled: false + masked: true + tags: + - 5.1.1.1.4 + + - name: 5.1.1.2 Ensure systemd-journal-remote.socket service is masked + ansible.builtin.systemd: + name: systemd-journal-remote.service + enabled: false + masked: true + tags: + - 5.1.1.2 + + - name: 5.1.1.3 - Ensure journald compresses large files + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Compress=((?!yes).)*$" + line: "Compress=yes" + insertafter: "^#Compress=" + notify: Restart journald + tags: + - 5.1.1.3 + + - name: 5.1.1.4 - Ensure journald writes to peristent disk + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^Storage=((?!persistent).)*$" + line: "Storage=persistent" + insertafter: "^#Storage=" + notify: Restart journald + tags: + - 5.1.1.4 + + - name: 5.1.1.5 - Ensure journald is not configured to send logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + state: absent + tags: + - 5.1.1.5 + + # Control 5.1.1.6, configure log rotation is machine specific, skipping + # TODO + # Control 5.1.1.7, Ensure journald default file permissions configured, is machine dependant, skipping + + + - name: 5.1.1.1.3 - Ensure journald service is enabled + ansible.builtin.systemd: + name: systemd-journald + state: started + masked: false + enabled: true + tags: + - 5.1.1.1.3 + +- name: 5.1.2 - Configure rsyslog + when: log_service and log_service == "rsyslog" + tags: + - 5.1.2 + block: + - name: 5.1.2.3 - Configure journald to forward logs to rsyslog + tags: + - 5.1.2.3 + block: + - name: 5.1.2.3 - Find any rsyslog files where all logs are being forwarded to a loghost + ansible.builtin.shell: /usr/bin/grep -l -s "^*.*[^I][^I]*@" /etc/rsyslog.conf /etc/rsyslog.d/*.conf + register: rsyslog_forward_out + changed_when: false + failed_when: rsyslog_forward_out.rc == "2" + check_mode: false + + - name: 5.1.2.3 - Forward journald logs to rsyslog IF rsyslog is sending logs to a log host + ansible.builtin.lineinfile: + dest: /etc/systemd/journald.conf + regexp: "^ForwardToSyslog=((?!yes).)*$" + line: "ForwardToSyslog=yes" + insertafter: "#ForwardToSyslog=no" + when: rsyslog_forward_out.stdout + + - name: 5.1.2.4 - Ensure rsyslog default file permissions are configured + ansible.builtin.lineinfile: + path: /etc/rsyslog.conf + regexp: '^\$FileCreateMode\s+0640' + line: "$FileCreateMode 0640" + create: true + owner: root + group: root + mode: 0644 + state: present + notify: Restart rsyslog + tags: + - 5.1.2.4 + + - name: 5.1.2.5 - Ensure logging is configured in rsyslog + ansible.builtin.copy: + src: "{{ rsyslog_file }}" + dest: "/etc/rsyslog.d/{{ rsyslog_file }}" + owner: root + group: root + mode: 0640 + when: rsyslog_file is defined and rsyslog_file | length > 0 + tags: + - 5.1.2.5 + + - name: 5.1.2.1 - Ensure rsyslog is installed + ansible.builtin.package: + name: rsyslog + state: present + tags: + - 5.1.2.1 + + - name: 5.1.2.2 - Enable Rsyslog + ansible.builtin.service: + name: rsyslog + enabled: true + tags: + - 5.1.2.2 + + # Control 5.1.2.6 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent + # skipping + + - name: 5.1.2.7 - Ensure remote rsyslog messages are only acepted on designated log hosts + tags: + - 5.1.2.7 + block: + - name: 5.1.2.7 - Find all rsyslog conf files in /etc/rsyslog.d + ansible.builtin.find: + paths: "/etc/rsyslog.d" + patterns: "*.conf" + register: rsyslog_module_found + + - name: 5.1.2.7 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable imtcp loading module on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$ModLoad\s+imtcp' + state: absent + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable TCP port listening on non log hosts (rsylog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$InputTCPServerRun' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable TCP port listening on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$InputTCPServerRun' + state: absent + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Enable loading of imtcp module on log hosts + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$ModLoad\s+imtcp' + line: "$ModLoad imtcp" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + - name: 5.1.2.7 - Enable TCP Port listening on port {{ log_port }} + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$InputTCPServerRun {{ log_port }}' + line: "$InputTCPServerRun {{ log_port }}" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + # Control 5.1.2.6 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent + # skipping + + - name: 5.1.2.7 - Ensure remote rsyslog messages are only acepted on designated log hosts + tags: + - 5.1.2.7 + block: + - name: 5.1.2.7 - Find all rsyslog conf files in /etc/rsyslog.d + ansible.builtin.find: + paths: "/etc/rsyslog.d" + patterns: "*.conf" + register: rsyslog_module_found + + - name: 5.1.2.7 - Disable imtcp loading module on non log hosts (rsyslog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable imtcp loading module on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$ModLoad\s+imtcp' + state: absent + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable TCP port listening on non log hosts (rsylog.d conf files) + ansible.builtin.lineinfile: + dest: "{{ item.path }}" + regexp: '^\$InputTCPServerRun' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Disable TCP port listening on non log hosts (main rsyslog conf file) + ansible.builtin.lineinfile: + dest: "/etc/rsyslog.conf" + regexp: '^\$InputTCPServerRun' + state: absent + when: log_host is defined and not log_host + + - name: 5.1.2.7 - Enable loading of imtcp module on log hosts + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$ModLoad\s+imtcp' + line: "$ModLoad imtcp" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + - name: 5.1.2.7 - Enable TCP Port listening on port {{ log_port }} + ansible.builtin.lineinfile: + dest: /etc/rsyslog.d/CIS.conf + regexp: '^\$InputTCPServerRun {{ log_port }}' + line: "$InputTCPServerRun {{ log_port }}" + create: true + owner: root + group: root + mode: 0644 + when: log_host is defined and log_host + + +# Section 6 - System Maintenance + +# Control 6.1.1 - Audit system file permissions, the report is time consuming and requires manual review +# skipping + +- name: 6.1.[1,3] - Ensure permissions on /etc/passwd /etc/group /etc/shells + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0644 + loop: + - passwd + - group + tags: + - 6.1.1 + - 6.1.3 + +- name: 6.1.[5,7] - Ensure permissions on /etc/shadow /etc/gshadow + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + loop: + - shadow + - gshadow + tags: + - 6.1.5 + - 6.1.7 + +- name: 6.1.[2,4,6,8] - Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- + ansible.builtin.file: + path: /etc/{{ item }} + owner: root + group: root + mode: 0000 + with_items: + - passwd- + - shadow- + - group- + - gshadow- + tags: + - 6.1.2 + - 6.1.4 + - 6.1.6 + - 6.1.8 + +# Control 6.1.9, Ensure no world writable files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.9 - Ensure no world writable files exist + tags: + - 6.1.9 + block: + - name: 6.1.9 - Find any world writiable files + ansible.builtin.shell: "/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -type f -perm -0002" + register: ww_files + changed_when: false + check_mode: false + + - name: 6.1.9 - Print any world writable files found + ansible.builtin.debug: + msg: "World writiable files found: {{ ww_files.stdout }}" + changed_when: true + when: ww_files.stdout + +# Control 6.1.10, Ensure no unowned files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.10 - Ensure no unowned files exist + tags: + - 6.1.10 + block: + - name: 6.1.10 - Find any unowned files + ansible.builtin.shell: "/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nouser" + register: uo_files + changed_when: false + + - name: 6.1.10 - Print any unowned files found + ansible.builtin.debug: + msg: "unowned files found: {{ uo_files.stdout }}" + changed_when: true + when: uo_files.stdout + +# Control 6.1.10, Enscure no ungrouped files exist, is system dependent so we are only +# providing a list to the user here. +- name: 6.1.11 - Ensure no ungrouped files exist + tags: + - 6.1.11 + block: + - name: 6.1.11 - Find any ungrouped files + ansible.builtin.shell: "/bin/df --local -P | /usr/bin/awk {' if (NR!=1) print $6'} | /usr/bin/xargs -I '{}' find '{}' -xdev -nogroup" + register: ug_files + changed_when: false + check_mode: false + + - name: 6.1.11 - Print any ungrouped files found + ansible.builtin.debug: + msg: "ungrouped files found: {{ uo_files.stdout }}" + changed_when: true + when: ug_files.stdout + + +# Control 6.1.12, Audit SUID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +# Control 6.1.13, Audit SGID executables, is a verification and is system dependent. +# Not implementing because it will always return some SUID files +# Manually review the control + +- name: 6.2.1 - Ensure accounts in /etc/passwd use shadowed passwords + tags: + - 6.2.1 + block: + - name: 6.2.1 - Check to see if there are any accounts with empty passwords + ansible.builtin.shell: "/bin/cat /etc/shadow | awk -F: '($2 == \"\" ) { print $1 }'" + changed_when: false + register: empty_passwords + check_mode: false + + - name: 6.2.1 - Report the named users to the report + ansible.builtin.debug: + msg: "The user {{ item }} has an empty password" + when: empty_passwords.stdout + changed_when: true + loop: "{{ empty_passwords.stdout_lines }}" + +- name: 6.2.2 - /etc/shadow password fields are not empty + ansible.builtin.command: "awk -F: '($2 == \"\")' {{ item }}" + loop: + - /etc/shadow + changed_when: false + tags: + - 6.2.2 + +- name: 6.2.3 - Report on groups in /etc/passwd with a GID not in /etc/group + tags: + - 6.2.3 + block: + - name: 6.2.3 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/undefined_groups.sh + register: undefined_groups + changed_when: false + check_mode: false + + - name: 6.2.3 - Report to user any unreferenced groups + ansible.builtin.debug: + msg: "{{ undefined_groups.stdout_lines }}" + changed_when: true + when: undefined_groups.stdout + +- name: 6.2.4 - Report if shadow group exists in /etc/group + block: + - name: 6.2.4 - Determine if the shadow group exists in /etc/group + ansible.builtin.command: /bin/grep "^shadow:" /etc/group + register: shadow_out + changed_when: false + failed_when: shadow_out.rc == "2" + + - name: 6.2.4 - Print report of shadow in /etc/group to user + ansible.builtin.debug: + msg: "Shadow group exists in /etc/group. Remove" + changed_when: true + when: shadow_out.stdout + +- name: 6.2.5 - Ensure no duplicate UIDs exist + tags: + - 6.2.5 + block: + - name: 6.2.5 - find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc >= 2 + check_mode: false + + - name: 6.2.5 - Use script to pull the list of duplicate UIDs + ansible.builtin.script: + cmd: files/duplicate_uids.sh + register: duplicate_uids + changed_when: false + check_mode: false + + - name: 6.2.5 - Print report of duplicated UIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_uids.stdout_lines }}" + changed_when: true + when: duplicate_uids.stdout + +- name: 6.2.6 - Report on duplicate GIDs in /etc/group + tags: + - 6.2.6 + block: + - name: 6.2.6 - Use script to pull the list of duplicate GIDs + ansible.builtin.script: + cmd: files/duplicate_guids.sh + register: duplicate_guids + changed_when: false + check_mode: false + + - name: 6.2.6 - Print report of duplcate GIDs to user + ansible.builtin.debug: + msg: "{{ duplicate_guids.stdout_lines }}" + changed_when: true + when: duplicate_guids.stdout + +- name: 6.2.7 - Report on duplicate users in /etc/passwd + tags: + - 6.2.7 + block: + - name: 6.2.7 - Use script to pull the list of users + ansible.builtin.script: + cmd: files/duplicate_users.sh + register: duplicate_users + changed_when: false + check_mode: false + + - name: 6.2.7 - Print report of duplicate users to user + ansible.builtin.debug: + msg: "{{ duplicate_users.stdout_lines }}" + changed_when: true + when: duplicate_users.stdout + +- name: 6.2.8 - Report on duplicate groups in /etc/group + tags: + - 6.2.8 + block: + - name: 6.2.8 - Use script to pull the list of groups + ansible.builtin.script: + cmd: files/duplicate_groups.sh + register: duplicate_groups + changed_when: false + check_mode: false + + - name: 6.2.8 - Print report of duplicate groups to user + ansible.builtin.debug: + msg: "{{ duplicate_groups.stdout_lines }}" + changed_when: true + when: duplicate_groups.stdout + +- name: 6.2.9 - Ensure root PATH integrity + tags: + - 6.2.9 + block: + - name: 6.2.9 - Run script on path variable + ansible.builtin.script: files/path_check.sh + changed_when: false + register: path_check + check_mode: false + + - name: 6.2.9 - Print report to user + ansible.builtin.debug: + msg: + - "Note, Ansible runs this as SUDO with the ansible user's PATH variable. The script may not print issues" + - "that exist in root's path because of this. It should be run as root on the target machine manually." + - " {{ path_check.stdout }}" + when: path_check.stdout + +- name: 6.2.10 - Ensure root is the only UID 0 account + tags: + - 6.2.10 + block: + - name: 6.2.10 - find accounts with UID of 0 + ansible.builtin.shell: "/usr/bin/cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'" + register: rootuid + changed_when: rootuid.rc == 2 + check_mode: false + + - name: 6.2.10 - Report on mulitple accounts with UID of 0 + ansible.builtin.debug: + msg: + - "Accounts with UID zero in addition to root" + - " {{ rootuid.stdout_lines }}" + changed_when: true + when: rootuid.stdout != 'root' + +- name: 6.2.10 - Report on users that do not have a home directory + tags: + - 6.2.10 + block: + - name: 6.2.10 - Use script to find the users + ansible.builtin.script: + cmd: files/non_existant_homedirs.sh + register: nohomedir + changed_when: false + + - name: 6.2.10 - Print report of users that do not have a home directory + ansible.builtin.debug: + msg: "{{ nohomedir.stdout_lines }}" + changed_when: true + when: nohomedir.stdout diff --git a/roles/cis_security/templates/aidecheck.service b/roles/cis_security/templates/aidecheck.service index 77f211c..94f2d25 100644 --- a/roles/cis_security/templates/aidecheck.service +++ b/roles/cis_security/templates/aidecheck.service @@ -3,8 +3,10 @@ Description=Aide Check [Service] Type=simple -{% if ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or ansible_distribution == "Oracle" %} +{% if ansible_distribution == "RedHat" or ansible_distribution == "CentOS" or ansible_distribution == "Oracle" %} ExecStart=/usr/sbin/aide --check +{% elif ansible_distribution == "Ubuntu" and ansible_distribution_major_version|int >= 22 %} +ExecStart=/usr/bin/aide --check {% else %} ExecStart=/usr/bin/aide.wrapper -C {% endif %} diff --git a/roles/cis_security/templates/audit_rules/bad-file-access.rules b/roles/cis_security/templates/audit_rules/bad-file-access.rules index 756eb21..da1e6d7 100644 --- a/roles/cis_security/templates/audit_rules/bad-file-access.rules +++ b/roles/cis_security/templates/audit_rules/bad-file-access.rules @@ -1,4 +1,4 @@ --a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k access --a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k access --a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k access --a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k access +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>={{ min_uid.stdout }} -F auid!=-1 -k access +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>={{ min_uid.stdout }} -F auid!=-1 -k access +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>={{ min_uid.stdout }} -F auid!=-1 -k access +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>={{ min_uid.stdout }} -F auid!=-1 -k access diff --git a/roles/cis_security/templates/audit_rules/chacl.rules b/roles/cis_security/templates/audit_rules/chacl.rules new file mode 100644 index 0000000..72aaf33 --- /dev/null +++ b/roles/cis_security/templates/audit_rules/chacl.rules @@ -0,0 +1 @@ +-a always,exit -F path=/usr/bin/chacl -F perm=x -F auid>={{ min_uid.stdout }} -F auid!=-1 -k perm_chng diff --git a/roles/cis_security/templates/audit_rules/chcon.rules b/roles/cis_security/templates/audit_rules/chcon.rules new file mode 100644 index 0000000..e1503b3 --- /dev/null +++ b/roles/cis_security/templates/audit_rules/chcon.rules @@ -0,0 +1 @@ +-a always,exit -F path=/usr/bin/chcon -F perm=x -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_chng diff --git a/roles/cis_security/templates/audit_rules/dac.rules b/roles/cis_security/templates/audit_rules/dac.rules index 6b7200f..e02a6a6 100644 --- a/roles/cis_security/templates/audit_rules/dac.rules +++ b/roles/cis_security/templates/audit_rules/dac.rules @@ -1,6 +1,6 @@ --a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod --a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod --a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod --a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod --a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod --a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod +-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod +-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod +-a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod +-a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod +-a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_mod diff --git a/roles/cis_security/templates/audit_rules/datetime.rules b/roles/cis_security/templates/audit_rules/datetime.rules index 7f79962..a86a024 100644 --- a/roles/cis_security/templates/audit_rules/datetime.rules +++ b/roles/cis_security/templates/audit_rules/datetime.rules @@ -1,5 +1,3 @@ --a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change --a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change --a always,exit -F arch=b64 -S clock_settime -k time-change --a always,exit -F arch=b32 -S clock_settime -k time-change +-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -F key=time-change +-a always,exit -F arch=b32 -S adjtimex,settimeofday,clock_settime -F key=time-change -w /etc/localtime -p wa -k time-change diff --git a/roles/cis_security/templates/audit_rules/delete.rules b/roles/cis_security/templates/audit_rules/delete.rules index 6597bab..b308ca0 100644 --- a/roles/cis_security/templates/audit_rules/delete.rules +++ b/roles/cis_security/templates/audit_rules/delete.rules @@ -1,2 +1,2 @@ --a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k delete --a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>={{ min_uid.stdout }} -F auid!=4294967295 -k delete +-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>={{ min_uid.stdout }} -F auid!=unset -k delete +-a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>={{ min_uid.stdout }} -F auid!=unset -k delete diff --git a/roles/cis_security/templates/audit_rules/login.rules b/roles/cis_security/templates/audit_rules/login.rules index dda9d98..962c97a 100644 --- a/roles/cis_security/templates/audit_rules/login.rules +++ b/roles/cis_security/templates/audit_rules/login.rules @@ -1,2 +1,3 @@ -w /var/log/faillog -p wa -k logins -w /var/log/lastlog -p wa -k logins +-w /var/run/faillock -p wa -k logins diff --git a/roles/cis_security/templates/audit_rules/modules.rules b/roles/cis_security/templates/audit_rules/modules.rules index 5fae54e..fbd68a9 100644 --- a/roles/cis_security/templates/audit_rules/modules.rules +++ b/roles/cis_security/templates/audit_rules/modules.rules @@ -1,4 +1,5 @@ -w /sbin/insmod -p x -k modules -w /sbin/rmmod -p x -k modules -w /sbin/modprobe -p x -k modules --a always,exit -F arch=b64 -S init_module -S delete_module -k modules +-a always,exit -F path=/usr/bin/kmod -F perm=x -F auid>={{ min_uid.stdout }} -F auid!=unset -k modules +-a always,exit -F arch=b64 -S init_module -S delete_module -S finit_module -S query_module -F auid>={{ min_uid.stdout }} -F auid!=unset -k modules diff --git a/roles/cis_security/templates/audit_rules/network.rules b/roles/cis_security/templates/audit_rules/network.rules index 63d590e..3dd3d84 100644 --- a/roles/cis_security/templates/audit_rules/network.rules +++ b/roles/cis_security/templates/audit_rules/network.rules @@ -1,6 +1,7 @@ --a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale --a always,exit -F arch=b32 -S sethostname -S setdomainname -k system-locale +-a always,exit -F arch=b64 -S sethostname,setdomainname -k -F system-locale +-a always,exit -F arch=b32 -S sethostname,setdomainname -k -F system-locale -w /etc/issue -p wa -k system-locale -w /etc/issue.net -p wa -k system-locale -w /etc/hosts -p wa -k system-locale -w /etc/sysconfig/network -p wa -k system-locale +-w /etc/sysconfig/network-scripts -p wa -k system-locale \ No newline at end of file diff --git a/roles/cis_security/templates/audit_rules/sessions.rules b/roles/cis_security/templates/audit_rules/sessions.rules index 51d7254..f82963d 100644 --- a/roles/cis_security/templates/audit_rules/sessions.rules +++ b/roles/cis_security/templates/audit_rules/sessions.rules @@ -1,3 +1,3 @@ -w /var/run/utmp -p wa -k session --w /var/log/wtmp -p wa -k logins --w /var/log/btmp -p wa -k logins +-w /var/log/wtmp -p wa -k session +-w /var/log/btmp -p wa -k session diff --git a/roles/cis_security/templates/audit_rules/setfacl.rules b/roles/cis_security/templates/audit_rules/setfacl.rules new file mode 100644 index 0000000..c17b756 --- /dev/null +++ b/roles/cis_security/templates/audit_rules/setfacl.rules @@ -0,0 +1 @@ +-a always,exit -F path=/usr/bin/setfacl -F perm=x -F auid>={{ min_uid.stdout }} -F auid!=unset -k perm_chng diff --git a/roles/cis_security/templates/audit_rules/sudolog.rules b/roles/cis_security/templates/audit_rules/sudolog.rules index 2c1b8e8..6cd3c82 100644 --- a/roles/cis_security/templates/audit_rules/sudolog.rules +++ b/roles/cis_security/templates/audit_rules/sudolog.rules @@ -1,3 +1,3 @@ -w /etc/sudoers -p wa -k scope --w /etc/sudoers.d/ -p wa -k scope --w {{ sudo_log }} -p wa -k actions +-w /etc/sudoers.d -p wa -k scope +-w {{ sudo_log }} -p wa -k sudo_log_file diff --git a/roles/cis_security/templates/audit_rules/user_emulation.rules b/roles/cis_security/templates/audit_rules/user_emulation.rules new file mode 100644 index 0000000..268004a --- /dev/null +++ b/roles/cis_security/templates/audit_rules/user_emulation.rules @@ -0,0 +1,2 @@ +-a always,exit -F arch=b64 -C euid!=uid -F auid!=unset -S execve -k user_emulation +-a always,exit -F arch=b32 -C euid!=uid -F auid!=unset -S execve -k user_emulation diff --git a/roles/cis_security/templates/audit_rules/usermod.rules b/roles/cis_security/templates/audit_rules/usermod.rules new file mode 100644 index 0000000..d57e09f --- /dev/null +++ b/roles/cis_security/templates/audit_rules/usermod.rules @@ -0,0 +1 @@ +-a always,exit -F path=/usr/sbin/usermod -F perm=x -F auid>={{ min_uid.stdout }} -F auid!=unset -k usermod