From 4f0226695f10c5e76d6cb64ce15ecb128b9dd8b2 Mon Sep 17 00:00:00 2001 From: Kirkland <56662961+Kirkland-gh@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:37:46 -0500 Subject: [PATCH 01/68] Update ubuntu-18-type.yml Fix ERROR! no module/action detected in task.This causes the playbook to fail on ubuntu serves. --- roles/cis_security/tasks/type-files/ubuntu-18-type.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..5e5afdf 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 From 2f7efe4333cbfbcebfe43f5a43f72ad8978d8469 Mon Sep 17 00:00:00 2001 From: Conundrum2k Date: Thu, 5 Jan 2023 14:23:18 -0500 Subject: [PATCH 02/68] Changed the name of the CIS-Oracle8.yml file to CIS-OracleLinux-8.yml to match the value passed by ansible-distribution --- .../tasks/{CIS-Oracle-8.yml => CIS-OracleLinux-8.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename roles/cis_security/tasks/{CIS-Oracle-8.yml => CIS-OracleLinux-8.yml} (100%) 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 From 3bdc41504eb1e8d854c933d5d3ee6d32a5ff2bb7 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 30 Mar 2023 13:07:48 -0400 Subject: [PATCH 03/68] cleaning up before starting RHEL9 work --- roles/cis_security/tasks/type-files/SLES-addons.yml | 2 +- roles/cis_security/tasks/type-files/redhat-8-type.yml | 2 +- roles/cis_security/tasks/type-files/ubuntu-18-type.yml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/roles/cis_security/tasks/type-files/SLES-addons.yml b/roles/cis_security/tasks/type-files/SLES-addons.yml index 392a0fa..417aec6 100644 --- a/roles/cis_security/tasks/type-files/SLES-addons.yml +++ b/roles/cis_security/tasks/type-files/SLES-addons.yml @@ -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-8-type.yml b/roles/cis_security/tasks/type-files/redhat-8-type.yml index fd27e3a..33c3d32 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -585,7 +585,7 @@ - 1.4.1 - name: Wait for AIDE initialization to complete - ansible.builtin.async_status: jid={{ aide.ansible_job_id }} + 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" 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..bd093c9 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 From 4bdc66018e1ddcdb2c2354a2ce2f7e34a2dbe1e5 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 30 Mar 2023 15:58:29 -0400 Subject: [PATCH 04/68] starting RHEL9 udpates --- galaxy.yml | 4 +- roles/cis_security/tasks/CIS-RedHat-9.yml | 4 + .../tasks/type-files/redhat-9-type.yml | 2634 +++++++++++++++++ 3 files changed, 2640 insertions(+), 2 deletions(-) create mode 100644 roles/cis_security/tasks/CIS-RedHat-9.yml create mode 100644 roles/cis_security/tasks/type-files/redhat-9-type.yml diff --git a/galaxy.yml b/galaxy.yml index f574047..1249072 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -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.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md @@ -42,7 +42,7 @@ 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: +dependencies: "community.general": "*" "community.windows": "*" 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/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml new file mode 100644 index 0000000..33c3d32 --- /dev/null +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -0,0 +1,2634 @@ +--- +# 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 + + # 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 + + # 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 + +# 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 + + # 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: 3.4.1.1 - Install firewalld + ansible.builtin.dnf: + name: "firewalld" + state: present + notify: start firewalld # 3.4.2.1 + + - name: 3.4.2.2 - Disable iptables service + ansible.builtin.service: + name: iptables + state: stopped + enabled: false + masked: true + ignore_errors: true + failed_when: false + + - name: 3.4.2.3 - Disable netfilters service + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: "'nftables' in ansible_facts.packages" + + - name: 3.4.2.4 - 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 + + # 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 + + - name: Notify users to configure the firewall + ansible.builtin.debug: + msg: + - "3.4.2.7 - Ensure default firewalld policy must be handled locally" + tags: + - 3.4.2 + when: enable_firewall is defined and enable_firewall == "firewalld" + tags: + - 3.4.1 + - 3.4.2 + + # Control 3.4.3 Configure nftables, skipping + + - name: 3.4.4.1 - Install iptables + block: + - name: 3.4.4.1 - Install iptables + ansible.builtin.dnf: + name: + - "iptables" + - "iptables-services" + state: present + notify: start iptables + tags: + - 3.4.4.1 + + - name: 3.4.4.1 - Disable firewalld + ansible.builtin.service: + name: firewalld + state: stopped + enabled: false + ignore_errors: true + tags: + - 3.4.4.1 + + - name: Notify user to configure firewall + ansible.builtin.debug: + msg: + - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" + tags: + - 3.4.4.1 + + 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 From 41fcf6281119d3a94c5d4a1801ef6f38dbbe0911 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 6 Apr 2023 14:41:04 -0400 Subject: [PATCH 05/68] Changes due to handler name changes --- roles/cis_security/handlers/main.yml | 59 +++++----- .../tasks/type-files/SLES-addons.yml | 2 +- .../tasks/type-files/ubuntu-18-type.yml | 102 +++++++++--------- 3 files changed, 84 insertions(+), 79 deletions(-) diff --git a/roles/cis_security/handlers/main.yml b/roles/cis_security/handlers/main.yml index 92602ac..7eb9787 100644 --- a/roles/cis_security/handlers/main.yml +++ b/roles/cis_security/handlers/main.yml @@ -5,104 +5,109 @@ # command rather than the service module (which uses systemd) # https://access.redhat.com/solutions/2664811 -- name: restart auditd +- name: Restart auditd ansible.builtin.service: name: auditd state: restarted use: service 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 -- 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 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/type-files/SLES-addons.yml b/roles/cis_security/tasks/type-files/SLES-addons.yml index 417aec6..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 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 bd093c9..3ce6a40 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml @@ -164,7 +164,7 @@ owner: root group: root state: link - notify: restart tmpfs + notify: Restart tmpfs tags: - 1.1.2 @@ -608,7 +608,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 @@ -630,7 +630,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 tags: @@ -779,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: @@ -788,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 @@ -825,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 @@ -942,7 +942,7 @@ group: root mode: 0644 when: time_service == "chrony" - notify: restart chronyd + notify: Restart chronyd tags: - 2.2.1.3 @@ -954,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 @@ -967,7 +967,7 @@ group: root mode: 0644 when: time_service == "timesync" - notify: restart timesyncd + notify: Restart timesyncd tags: - 2.2.1.2 @@ -1208,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: @@ -1226,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 @@ -1246,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: @@ -1263,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: @@ -1279,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 @@ -1371,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: @@ -1461,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: @@ -1510,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 @@ -1532,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 @@ -1554,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 @@ -1583,7 +1583,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.3 @@ -1594,7 +1594,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.4 @@ -1610,7 +1610,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.5 @@ -1621,7 +1621,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.6 @@ -1632,7 +1632,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.7 @@ -1643,7 +1643,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.8 @@ -1655,7 +1655,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.9 @@ -1666,7 +1666,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.10 @@ -1680,7 +1680,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.12 @@ -1691,7 +1691,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.13 @@ -1704,7 +1704,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.14 4.1.15 @@ -1716,7 +1716,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.16 @@ -1728,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 @@ -2005,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 @@ -2017,7 +2017,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^X11Forwarding\s*yes' - notify: restart sshd + notify: Restart sshd tags: - 5.2.6 @@ -2027,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 @@ -2036,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 @@ -2045,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 @@ -2054,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 @@ -2063,7 +2063,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.11 @@ -2072,7 +2072,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.12 @@ -2081,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 @@ -2091,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 @@ -2101,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 @@ -2111,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 @@ -2121,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 @@ -2131,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 @@ -2142,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 @@ -2151,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 @@ -2161,7 +2161,7 @@ line: "AllowTcpForwarding no" regexp: '^AllowTcpForwarding\s+(yes|no)' insertafter: "^#AllowTcpForwarding" - notify: restart sshd + notify: Restart sshd tags: - 5.2.21 @@ -2170,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 @@ -2179,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 From 190143e9d50194412063e2fa42616e0e50e2fa30 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 6 Apr 2023 14:42:44 -0400 Subject: [PATCH 06/68] Updates to rules files due to RHEL9 CIS controls --- .../templates/audit_rules/bad-file-access.rules | 8 ++++---- roles/cis_security/templates/audit_rules/chcon.rules | 1 + roles/cis_security/templates/audit_rules/dac.rules | 12 ++++++------ .../cis_security/templates/audit_rules/delete.rules | 4 ++-- roles/cis_security/templates/audit_rules/login.rules | 1 + .../cis_security/templates/audit_rules/modules.rules | 3 ++- .../templates/audit_rules/sessions.rules | 4 ++-- .../cis_security/templates/audit_rules/setfacl.rules | 1 + .../templates/audit_rules/user_emulation.rules | 2 ++ .../cis_security/templates/audit_rules/usermod.rules | 1 + 10 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 roles/cis_security/templates/audit_rules/chcon.rules create mode 100644 roles/cis_security/templates/audit_rules/setfacl.rules create mode 100644 roles/cis_security/templates/audit_rules/user_emulation.rules create mode 100644 roles/cis_security/templates/audit_rules/usermod.rules 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/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/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/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/user_emulation.rules b/roles/cis_security/templates/audit_rules/user_emulation.rules new file mode 100644 index 0000000..5152868 --- /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 \ No newline at end of file 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 From 3456d061b8244d01e60da1f28ca7b859d0af1338 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 6 Apr 2023 14:44:05 -0400 Subject: [PATCH 07/68] Update for handler name change --- roles/cis_security/tasks/type-files/MS-Server-type.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 3e3b72e6c713445a668763b45bae00a056ad9c01 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 6 Apr 2023 14:44:47 -0400 Subject: [PATCH 08/68] new variales for RHEL9, update for linting rules --- roles/cis_security/defaults/main.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index 8f7d067..d87e6a7 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -11,7 +11,7 @@ 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 +time_service: "chrony" # Linux: Option for RHEL7/Ubuntu only. 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 @@ -21,6 +21,10 @@ time_operators: # Windows: Users that can set the system time # 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,6 +52,7 @@ 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' +log_service: "journald" # journald or rsyslog for logging. Choose one. Currently only implemented in RHEL 9! # Rsyslog 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) @@ -61,15 +66,19 @@ enable_firewall: firewalld # Linux: supported values are firewalld or iptab firewalld_default_zone: public # Linux: default firwall zone motd_file: "banner" # Linux: File location by default in 'files' directory 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_file: "issue" # Linux: File location by default in 'files' diretory ipv6_disable: true # Common: Set to true to disable ipv6 support on host # 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 @@ -86,6 +95,8 @@ password_min_days: 7 # Common: Windows has this control listed as 1 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. account_lockout_duration: 15 # Windows: account_lockout_threshold: 10 # Windows: @@ -93,8 +104,8 @@ 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" From 54e77a3ec5bf9b69a32db727d9f2f5a51cf727bd Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 6 Apr 2023 14:47:47 -0400 Subject: [PATCH 09/68] change to include_tasks for updated ansible --- roles/cis_security/tasks/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 28403823ef22c3b3647f17994a84d0a4b830cb64 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Sun, 9 Apr 2023 21:29:42 -0400 Subject: [PATCH 10/68] changed handler lines due to name change --- .../tasks/type-files/redhat-7-type.yml | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) 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..79c29a7 100644 --- a/roles/cis_security/tasks/type-files/redhat-7-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-7-type.yml @@ -636,7 +636,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.3.2 @@ -658,7 +658,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.3.2 tags: @@ -810,7 +810,7 @@ - selinux=0 - enforcing=0 when: selinux is defined and selinux != "Disabled" - notify: rebuild grub + notify: Rebuild grub tags: - 1.6.1.1 @@ -842,7 +842,7 @@ mode: 0644 state: touch when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" - notify: reboot + notify: Reboot tags: - 1.6.1.2 @@ -931,7 +931,7 @@ # 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 }}" + src: "{{ issue_net_file }}" dest: /etc/issue.net owner: root group: root @@ -1020,7 +1020,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.2 - 2.2.1.3 @@ -1033,7 +1033,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 @@ -1293,7 +1293,7 @@ regexp: "^inet_interfaces = ((?!localhost).)*$" replace: "inet_interfaces = loopback-only" when: postfix_out.stat.exists and not email_server - notify: restart postfix + notify: Restart postfix tags: - 2.2.15 @@ -1317,7 +1317,7 @@ - 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 + notify: Flush network routes - name: 3.1 - Set ipv6 networking parameters (OFF) ansible.builtin.sysctl: @@ -1329,7 +1329,7 @@ loop: - net.ipv6.conf.all.forwarding # (3.1.1) when: not ipv6_disable - notify: flush network routes + notify: Flush network routes tags: - 3.1.0 @@ -1349,7 +1349,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: @@ -1366,7 +1366,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: @@ -1382,7 +1382,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 @@ -1401,7 +1401,7 @@ - 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 + notify: Flush network routes when: not ipv6_disable tags: - 3.3.1 @@ -1429,7 +1429,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' - notify: rebuild grub + notify: Rebuild grub when: not ipv6_disable_grub.found and ipv6_disable tags: - 3.3.3 @@ -1529,7 +1529,7 @@ ansible.builtin.package: name: "firewalld" state: present - notify: start firewalld + notify: Start firewalld - name: 3.6.1 - Disable iptables ansible.builtin.service: @@ -1551,7 +1551,7 @@ regexp: '^DefaultZone\s*((?!{{ firewalld_default_zone }}).)*$' line: "DefaultZone={{ firewalld_default_zone }}" when: firewalld_default_zone is defined - notify: restart firewalld + notify: Restart firewalld when: enable_firewall is defined and enable_firewall == "firewalld" tags: @@ -1565,7 +1565,7 @@ - "iptables" - "iptables-services" state: present - notify: start iptables + notify: Start iptables tags: - 3.6.1 @@ -1610,7 +1610,7 @@ - {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 + notify: Restart auditd tags: - 4.1.1.2 - 4.1.1.3 @@ -1645,7 +1645,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="audit=1 ' - notify: rebuild grub + notify: Rebuild grub when: not audit_exist.found tags: - 4.1.1.3 @@ -1660,7 +1660,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.4 @@ -1671,7 +1671,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.5 @@ -1682,7 +1682,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.6 @@ -1693,7 +1693,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.7 @@ -1704,7 +1704,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.8 @@ -1715,7 +1715,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.9 @@ -1727,7 +1727,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.10 @@ -1739,7 +1739,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.11 @@ -1753,7 +1753,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.13 @@ -1764,7 +1764,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.14 @@ -1775,7 +1775,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.15 4.1.16 @@ -1787,7 +1787,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.17 @@ -1799,7 +1799,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.18 when: enable_audit is defined and enable_audit @@ -1999,7 +1999,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.3 @@ -2011,7 +2011,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^X11Forwarding\s*yes' - notify: restart sshd + notify: Restart sshd tags: - 5.2.4 @@ -2021,7 +2021,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 +2030,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 +2039,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 +2048,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 +2057,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.9 @@ -2066,7 +2066,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.10 @@ -2076,7 +2076,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 +2086,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 +2096,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 +2106,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 +2118,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,7 +2130,7 @@ line: "AllowTcpForwarding no" regexp: '^AllowTcpForwarding\s+(yes|no)' insertafter: "^#AllowTcpForwarding" - notify: restart sshd + notify: Restart sshd tags: - 5.2.17 From 2be51d5cf24fad2b7680a6d7c39c8c636e8a395c Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 15:48:50 -0400 Subject: [PATCH 11/68] new variables for RHEL 9 --- roles/cis_security/defaults/main.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index d87e6a7..39944a0 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -64,11 +64,18 @@ tcpwrappers: false # Linux: Configure tcpwrappers controls. RHEL 7 tcpwrappers_pkg: "tcp_wrappers" # Linux: Name of tcp wrappers package in repository enable_firewall: firewalld # Linux: supported values are firewalld or iptables 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_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 @@ -84,7 +91,10 @@ 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: "adm" # 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 password_req_digit: true # Linux From e35a959235efebc94adb64080cad135a3304d284 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 15:49:10 -0400 Subject: [PATCH 12/68] Removed reboot timeout --- roles/cis_security/handlers/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/cis_security/handlers/main.yml b/roles/cis_security/handlers/main.yml index 7eb9787..a0b44cb 100644 --- a/roles/cis_security/handlers/main.yml +++ b/roles/cis_security/handlers/main.yml @@ -17,7 +17,7 @@ # needs to do a selinux relabel - name: Reboot ansible.builtin.reboot: - reboot_timeout: 600 +# reboot_timeout: 600 - name: Restart ntpd ansible.builtin.service: From 8266d16cd9df64632f5eb762a5cff83231a35bf5 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 15:49:32 -0400 Subject: [PATCH 13/68] Updated Notify service names --- .../tasks/type-files/redhat-8-type.yml | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) 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 33c3d32..2c3160e 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -124,7 +124,7 @@ owner: root group: root mode: 0644 - notify: restart tmpfs + notify: Restart tmpfs tags: - 1.1.2 - 1.1.3 @@ -618,7 +618,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 @@ -639,7 +639,7 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: restart aidecheck + notify: Restart aidecheck tags: - 1.4.2 tags: @@ -802,7 +802,7 @@ - selinux=0 - enforcing=0 when: selinux is defined and selinux != "Disabled" - notify: rebuild grub + notify: Rebuild grub tags: - 1.7.1.2 @@ -826,7 +826,7 @@ mode: 0644 state: touch when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" - notify: reboot + notify: Reboot tags: - 1.7.1.3 @@ -837,7 +837,7 @@ 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 + notify: Reboot tags: - 1.7.1.4 @@ -909,7 +909,7 @@ # 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 }}" + src: "{{ issue_net_file }}" dest: /etc/issue.net owner: root group: root @@ -951,7 +951,7 @@ path: /etc/crypto-policies/config regexp: "^(LEGACY|FUTURE|FIPS|DEFAULT)" line: "{{ crypto_policy | upper | default('DEFAULT', true) }}" - notify: update crypto_policy + 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 @@ -996,7 +996,7 @@ owner: root group: root mode: 0644 - notify: restart chronyd + notify: Restart chronyd tags: - 2.2.1.2 @@ -1007,7 +1007,7 @@ owner: root group: root mode: 0644 - notify: restart {{ time_service }}d + notify: Restart {{ time_service }}d tags: - 2.2.1.3 @@ -1226,7 +1226,7 @@ regexp: "^inet_interfaces = ((?!localhost).)*$" replace: "inet_interfaces = loopback-only" when: postfix_out.stat.exists and not email_server - notify: restart postfix + notify: Restart postfix tags: - 2.2.18 @@ -1248,7 +1248,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: @@ -1260,7 +1260,7 @@ loop: - net.ipv6.conf.all.forwarding # (3.1.1) when: not ipv6_disable - notify: flush network routes + notify: Flush network routes tags: - 3.1.0 @@ -1280,7 +1280,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: @@ -1297,7 +1297,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: @@ -1313,7 +1313,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 @@ -1375,7 +1375,7 @@ 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: @@ -1400,7 +1400,7 @@ 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 @@ -1426,7 +1426,7 @@ - "iptables" - "iptables-services" state: present - notify: start iptables + notify: Start iptables tags: - 3.4.4.1 @@ -1482,7 +1482,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="ipv6.disable=1 ' - notify: rebuild grub + notify: Rebuild grub when: not ipv6_disable_grub.found and ipv6_disable tags: @@ -1531,7 +1531,7 @@ path: /etc/default/grub regexp: '^GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="audit=1 ' - notify: rebuild grub + notify: Rebuild grub when: not audit_exist.found tags: - 4.1.1.3 @@ -1553,7 +1553,7 @@ dest: /etc/default/grub regexp: '^\s*GRUB_CMDLINE_LINUX="' replace: 'GRUB_CMDLINE_LINUX="audit_backlog_limit={{ audit_backlog_limit }} ' - notify: rebuild grub + notify: Rebuild grub when: not audit_backlog_exist.found tags: - 4.1.1.4 @@ -1575,7 +1575,7 @@ dest: /etc/default/grub regexp: 'audit_backlog_limit=[\S]*' replace: 'audit_backlog_limit={{ audit_backlog_limit }}' - notify: rebuild grub + notify: Rebuild grub when: audit_backlog_exist.found and not our_limit.found tags: - 4.1.1.4 @@ -1593,7 +1593,7 @@ - {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 + notify: Restart auditd tags: - 4.1.2.1 - 4.1.2.2 @@ -1609,7 +1609,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.4 @@ -1620,7 +1620,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.5 @@ -1631,7 +1631,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.6 @@ -1642,7 +1642,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.7 @@ -1653,7 +1653,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.8 @@ -1665,7 +1665,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.9 @@ -1676,7 +1676,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.10 @@ -1687,7 +1687,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.11 @@ -1698,7 +1698,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.12 @@ -1712,7 +1712,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.14 @@ -1723,7 +1723,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: 4.1.15 @@ -1734,7 +1734,7 @@ owner: root group: root mode: 0600 - notify: restart auditd + notify: Restart auditd tags: - 4.1.16 - 4.1.3 @@ -1747,7 +1747,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 @@ -2023,7 +2023,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 @@ -2035,7 +2035,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^X11Forwarding\s*yes' - notify: restart sshd + notify: Restart sshd tags: - 5.2.6 @@ -2045,7 +2045,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 @@ -2054,7 +2054,7 @@ path: /etc/ssh/sshd_config line: "IgnoreRhosts yes" regexp: '^IgnoreRhosts\s*[^y]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.8 @@ -2063,7 +2063,7 @@ path: /etc/ssh/sshd_config line: "HostbasedAuthentication no" regexp: '^HostbasedAuthentication\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.9 @@ -2072,7 +2072,7 @@ path: /etc/ssh/sshd_config line: "PermitRootLogin no" regexp: '^PermitRootLogin\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.10 @@ -2081,7 +2081,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitEmptyPasswords\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.11 @@ -2090,7 +2090,7 @@ path: /etc/ssh/sshd_config state: absent regexp: '^PermitUserEnvironment\s*[^n]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.12 @@ -2100,7 +2100,7 @@ line: "ClientAliveInterval {{ ssh_alive_interval }}" regexp: "^ClientAliveInterval {{ ssh_alive_interval }}" insertafter: "^#ClientAliveInterval" - notify: restart sshd + notify: Restart sshd tags: - 5.2.13 @@ -2110,7 +2110,7 @@ line: "ClientAliveCountMax {{ ssh_alive_count_max }}" regexp: "^ClientAliveCountMax {{ ssh_alive_count_max }}" insertafter: "^#ClientAliveCountMax" - notify: restart sshd + notify: Restart sshd tags: - 5.2.13 @@ -2120,7 +2120,7 @@ line: "LoginGraceTime {{ ssh_grace_time }}" regexp: "^LoginGraceTime {{ ssh_grace_time }}" insertafter: "^#LoginGraceTime" - notify: restart sshd + notify: Restart sshd tags: - 5.2.14 @@ -2129,7 +2129,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 @@ -2138,7 +2138,7 @@ path: "/etc/ssh/sshd_config" line: "UsePAM yes" regexp: '^UsePAM\s+[yes|no]' - notify: restart sshd + notify: Restart sshd tags: - 5.2.16 @@ -2148,7 +2148,7 @@ line: "AllowTcpForwarding no" regexp: '^AllowTcpForwarding\s+(yes|no)' insertafter: "^#AllowTcpForwarding" - notify: restart sshd + notify: Restart sshd tags: - 5.2.17 @@ -2157,7 +2157,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.18 @@ -2166,7 +2166,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.19 @@ -2175,7 +2175,7 @@ path: "/etc/ssh/sshd_config" state: absent regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' - notify: restart sshd + notify: Restart sshd tags: - 5.2.20 tags: From 8f1e00eff932117347b0d6e4c471648990bbee58 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 15:49:55 -0400 Subject: [PATCH 14/68] New RHEL9 commit file - testing --- .../tasks/type-files/redhat-9-type.yml | 5469 +++++++++-------- 1 file changed, 2880 insertions(+), 2589 deletions(-) diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 33c3d32..3d7bf56 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -11,2624 +11,2915 @@ # 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 +# 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 - 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: 644 + 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-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 + 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.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 + 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 /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 + +# 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 + 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 + 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 + 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 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 + 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 + 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 + 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: devshm_noexec_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 -# - 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 - -# 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 +# 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 + +# 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 + - 1.2.4 + 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.3 is just to ensure repositores are configured correctly. 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: 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 + + - name: 1.3.2 - Enable aidecheck.timer + ansible.builtin.systemd: + name: aidecheck.service + enabled: true + state: started + + - 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.1 + +- name: 1.4.0 - Check if the grub user.cfg exists + ansible.builtin.stat: + path: "/boot/grub2/user.cfg" + register: usercfgdir + tags: + 1.4.1 + +# 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.2 - Set permissions on user.cfg (if exists) + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - "{{ usercfgdir.stat.path}}" + when: usercfgdir.stat.exists + tags: + - 1.4.2 + +# 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: | + Storeage=none + state: present + 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 + 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 + when: selinux is defined and selinux != "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: Regather facts + ansible.builtin.setup: + 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 != "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 != "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" + 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 + 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 + 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.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: + - 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: 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: + - 2.2.2 + + - 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: + - 2.2.2 + + # 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: 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: 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: 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 uses 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/local.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 TODO + +# 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: 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.18 - Remove rsync-daemon; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'rsync-daemon' ] }}" + tags: + - 2.2.18 + +- 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.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.11 - Remove squid; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" + tags: + - 2.2.11 + +- name: 2.2.7 - Remove tftp-server; add to removal list + ansible.builtin.set_fact: + unneeded_packages: "{{ unneeded_packages + [ 'tftp-server' ] }}" + tags: + - 2.2.7 + +- 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.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.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.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.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.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.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.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.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 + +# 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.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({ 'net.ipv4.conf.all.send_redirects' : '0' }) }}" + 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({ 'net.ipv4.conf.all.accept_redirects' : '0' }) }}" + 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({ 'net.ipv6.conf.all.accept_redirects' : '0' }) }}" + 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 + 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 +# with_dict: +# - unneeded_ipv4_network + loop: + - "{{ lookup( 'dict' , unneeded_ipv4_network) }}" +# - "{{ lookup('dict' , unneeded_ipv6_network) }}" + notify: Flush network routes + +- name: 3.1.3 - Disable uncommon network protocols + tags: + - 3.1.3 + 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.1 - Disable TIPC + ansible.builtin.set_fact: + uncommon_network: "{{ uncommon_network + [ 'tipc' ] }}" + + - name: 3.1.3 - 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 }}" + + - 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 }}" + +# 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 + +- name: 3.4.1.1 - Disable netfilters service + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: "'nftables' in ansible_facts.packages" + +- name: 3.4.1.1 - Install firewall package + tags: + - 3.4.1 + - 3.4.2 + block: - name: 3.4.1.1 - Install firewalld ansible.builtin.dnf: name: "firewalld" state: present - notify: start firewalld # 3.4.2.1 + when: enable_firewall is defined and enable_firewall == "firewalld" + notify: Start firewalld # 3.4.2.1 - - name: 3.4.2.2 - Disable iptables service - ansible.builtin.service: - name: iptables - state: stopped - enabled: false - masked: true - ignore_errors: true - failed_when: false - - - name: 3.4.2.3 - Disable netfilters service - ansible.builtin.systemd: - name: nftables - state: stopped - enabled: false - masked: true - when: "'nftables' in ansible_facts.packages" - - - name: 3.4.2.4 - Set default zone + - 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: firewalld_default_zone is defined - 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 + 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 + + # Benchmark 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.7 - Ensure default firewalld policy must be handled locally" + - "3.4.2 - Firewall must be configured locally" tags: - 3.4.2 - when: enable_firewall is defined and enable_firewall == "firewalld" - tags: - - 3.4.1 - - 3.4.2 - # Control 3.4.3 Configure nftables, skipping +# Section 4 - Logging and Auditing - - name: 3.4.4.1 - Install iptables - block: - - name: 3.4.4.1 - Install iptables +- name: 4.1 Install and configure system auditing + tags: + - 4.1.3.0 + when: enable_audit is defined and enable_audit + block: + - name: 4.1.1.1 - Install Auditd ansible.builtin.dnf: name: - - "iptables" - - "iptables-services" + - audit + - audit-libs state: present - notify: start iptables tags: - - 3.4.4.1 + - 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 - - name: 3.4.4.1 - Disable firewalld + - name: 4.1.1.4 - Enable auditd service ansible.builtin.service: - name: firewalld - state: stopped + 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 + 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.3.1 - Ensure changes to system administration scope is 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.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/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/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/network.rules + src: audit_rules/network.rules + owner: root + group: root + mode: 0600 + notify: Restart auditd + tags: + 4.1.3.5 + + - name: 4.1.3.6 - 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.3.6 + + - name: 4.1.3.7 - 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.3.7 + + - name: 4.1.3.8 - 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.3.8 + + - name: 4.1.3.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.3.9 + + - name: 4.1.3.10 - 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.3.10 + + - name: 4.1.3.11 - 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.3.11 + + - name: 4.1.3.12 - 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.3.12 + + - name: 4.1.3.13 - 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.3.13 + + - name: 4.1.3.14 - 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.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/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/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/setfacl.rules + src: audit_rules/setfacl.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/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/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 + +## Control is 640 or less restrictive, so chose 600 +#- name: 4.1.4.[1-3,5-7] - Set autid 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/*' +## TODO, find the log_file in the audit.conf file and set it too +# 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: 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 + 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 + 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 + when: log_service and log_service == "journald" + 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-jornal-remote.socket is masked + ansible.builtin.systemd: + name: systemd-journal-remote.socket enabled: false - ignore_errors: true + masked: true + when: log_service and log_service == "journald" + tags: + - 4.2.2.1.4 + + - name: 4.2.2.2 Ensure jorunald service is enabled + ansible.builtin.systemd: + name: systemd-journal-remote.service + enabled: true + when: log_service and log_service == "journald" + tags: + - 4.2.2.2 + + - 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 + when: log_service and log_service == "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 + when: log_service and log_service == "journald" + tags: + - 4.2.2.4 + + - name: 4.2.2.5 - 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" + state: absent + when: log_service and log_service == "journald" + tags: + - 4.2.2.5 + + # Control 4.2.2.6, configure log rotation is machine specific, skipping + # TODO + # Control 4.2.2.7, Ensure permissions on log files are configured, is machine dependant, skipping + +# Control 4.2.3 is machine specific, 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: 5.1.0 - Configure cron/at + when: "'cronie' in ansible_facts.packages" + tags: + - 5.1.0 + block: + - 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 + 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.8 - Ensure cron is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/cron.allow + regexp: "^{{ item }}" + line: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - cron_allow + tags: + - 5.1.8 + + - name: 5.1.9 - Ensure at is restricted to authorized users + ansible.builtin.lineinfile: + path: /etc/at.allow + regexp: "^{{ item }}" + line: "{{ item }}" + owner: root + group: root + mode: 0600 + loop: + - at_allow + tags: + - 5.1.9 + +# If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag +- name: 5.2 - SSH File configurations + when: "'openssh-server' in ansible_facts.packages" + 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: ssh_keys + mode: 0640 + 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 + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\s+{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - [ "AllowUsers", ssh_allowed_users ] + when: ssh_allowed_users is defined and ssh_allowed_users + tags: + - 5.2.4 + + - name: 5.2.4 - Ensure SSH access is limited + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\s+{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - [ AllowGroups, "{{ ssh_allowed_groups }}" ] + when: ssh_allowed_groups is defined and ssh_allowed_groups + tags: + - 5.2.4 + + - name: 5.2.4 - Ensure SSH access is limited + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\s+{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - [ DenyUsers, "{{ ssh_denied_users }}" ] + when: ssh_denied_users is defined and ssh_denied_users + tags: + - 5.2.4 + + - name: 5.2.4 - Ensure SSH access is limited + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\s+{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - [ DenyGroups, "{{ ssh_denied_groups }}" ] + when: ssh_denied_groups is defined and ssh_denied_groups + tags: + - 5.2.4 + + - 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]' + 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\snone" + 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: - - 3.4.4.1 + - 5.2.20 - - name: Notify user to configure firewall + - 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: - - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" - tags: - - 3.4.4.1 - - 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 + 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.1 - Configure PAM files and password requirements + tags: + - 5.5.1 + 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 - # 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.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.replace: + path: /etc/security/faillock.conf + regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" + replace: "deny={{ password_failed_attempts }}" + after: "#\ *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.replace: + path: /etc/security/faillock.conf + regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" + replace: "unlock_time={{ password_failed_time }}" + after: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 + + +- 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 + +# Control 5.6.2, Ensure system accounts are secured, requires manual intervention, skipping + +# Control 5.5.4, Ensure password hashing algorithm is SHA-512, requires file replacement +# 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 + 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.11, 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 + +# 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.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 - Ensure no legacy "+" entries exist in password files + ansible.builtin.command: "awk -F: '($2 == \"\")' {{ item }}" + loop: + - /etc/shadow + changed_when: false + tags: + - 6.2.2 + +# Contorl 6.2.3, requires manual intervention, skipping + +- name: 6.2.4 - Report on duplicate UIDs in /etc/passwd + tags: + - 6.2.4 + block: + - 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 - Report on multiple accounts with UID of 0 + 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' + +# Control 6.2.10 is environment dependent, skipping +# Control 6.2.11 is environment dependent, skipping +# Control 6.2.12 is environment dependent, skipping +# Control 6.2.13 is environment dependent, skipping +# Control 6.2.14 is environment dependent, skipping +# Control 6.2.15 is environment dependent, skipping +# Control 6.2.16 is environment dependent, skipping + +# Controls 6.2.[9-13,20] are recommended to be handled by monitoring software From 16018d9914afaace62138104dc7583880e55a7b3 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 16:04:35 -0400 Subject: [PATCH 15/68] fixed network settings issue --- .../tasks/type-files/redhat-9-type.yml | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 3d7bf56..c6d8668 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -1350,7 +1350,7 @@ - name: 3.3.9 - Ensure IPv6 router advertisements are not accepted ansible.builtin.set_fact: - unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : : '0' }) }}" + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" when: not ipv6_disable loop: - net.ipv6.conf.all.accept_ra @@ -1369,7 +1369,7 @@ # 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 +- name: 3.3 - Process unneeded network settings for IPv4 tags: - 3.3.0 block: @@ -1380,11 +1380,21 @@ reload: true state: present sysctl_set: true -# with_dict: -# - unneeded_ipv4_network - loop: - - "{{ lookup( 'dict' , unneeded_ipv4_network) }}" -# - "{{ lookup('dict' , unneeded_ipv6_network) }}" + 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.3 - Disable uncommon network protocols @@ -2349,7 +2359,7 @@ path: "/etc/ssh/sshd_config" line: "Banner /etc/{{ ssh_login_banner }}" regexp: "^Banner /etc/{{ ssh_login_banner }}" - insertafter: "^#Banner\snone" + insertafter: "^#Banner none" notify: Restart sshd tags: - 5.2.15 From 9f3e5800d129734df3255893c545fb39bdb89cf1 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 16:06:47 -0400 Subject: [PATCH 16/68] updated for linter --- galaxy.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/galaxy.yml b/galaxy.yml index 1249072..6e838d6 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -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.04 LTS, Ubuntu 20.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' @@ -43,17 +45,17 @@ tags: [system, security] # 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": "*" + "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 From 62b0c21310ea4b08f6a4a9a099c94fd656f93936 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 23:57:53 -0400 Subject: [PATCH 17/68] Syncing with RHEL 8 as needed --- .../tasks/type-files/redhat-9-type.yml | 110 +++++++++--------- 1 file changed, 57 insertions(+), 53 deletions(-) diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index c6d8668..d87ee74 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -73,7 +73,7 @@ create: true owner: root group: root - mode: 644 + mode: 0644 setype: modules_conf_t block: | install udf /bin/false @@ -122,7 +122,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.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: @@ -576,6 +575,7 @@ group: root mode: 0644 setype: systemd_unit_file_t + notify: Restart aidecheck - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: @@ -859,7 +859,7 @@ path: /etc/systemd/system/default.target register: default_runlevel_out tags: - - 2.2.2 + - 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 @@ -869,7 +869,7 @@ changed_when: isolate_out.changed when: default_runlevel_out.stat.lnk_target is search("graphical.target") and not graphical_interface tags: - - 2.2.2 + - 1.8.1 - name: 1.8.1 - Set current runlevel (graphical) ansible.builtin.command: /usr/bin/systemctl isolate graphical.target @@ -877,7 +877,7 @@ changed_when: isolate_out.changed when: default_runlevel_out.stat.lnk_target is search("multi-user.target") and graphical_interface tags: - - 2.2.2 + - 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) @@ -952,7 +952,7 @@ insertafter: "[org/gnome/login-screen]" block: | banner-message-enable=true - banner-message-text='Authorized uses only. All activity may be monitored and reported.' + banner-message-text='Authorized users only. All activity may be monitored and reported.' tags: - 1.8.2 @@ -1068,29 +1068,32 @@ tags: - 2.2.1 -- 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.2.2 - Remove avahi; add to removal list ansible.builtin.set_fact: unneeded_packages: "{{ unneeded_packages + [ 'avahi' ] }}" tags: - 2.2.2 -- name: 2.2.12 - Remove snmp; add to removal list +- name: 2.2.4 - Remove dhcp; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'net-snmp' ] + [ 'net-snmp-libs'] }}" + unneeded_packages: "{{ unneeded_packages + [ 'dhcp' ] }}" + when: dhcp_server is defined and not dhcp_server tags: - - 2.2.12 + - 2.2.4 -- name: 2.2.11 - Remove squid; add to removal list +- name: 2.2.5 - Remove bind; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" + unneeded_packages: "{{ unneeded_packages + [ 'bind' ] + [ 'unbound' ] }}" + when: dns_server is defined and not dns_server tags: - - 2.2.11 + - 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: @@ -1098,12 +1101,12 @@ tags: - 2.2.7 -- name: 2.2.10 - Remove samba; add to removal list +- name: 2.2.8 - Remove httpd; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" - when: smb_server is defined and not smb_server + unneeded_packages: "{{ unneeded_packages + [ 'httpd' ] + [ 'httpd-tools' ] + [ 'mod_ssl' ] }}" + when: http_server is defined and not http_server tags: - - 2.2.10 + - 2.2.8 - name: 2.2.9 - Remove dovecot and cyrus-imapd; add to removal list ansible.builtin.set_fact: @@ -1112,26 +1115,36 @@ tags: - 2.2.9 -- name: 2.2.8 - Remove httpd; add to removal list +- name: 2.2.10 - Remove samba; 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 + unneeded_packages: "{{ unneeded_packages + [ 'samba' ] }}" + when: smb_server is defined and not smb_server tags: - - 2.2.8 + - 2.2.10 -- name: 2.2.6 - Remove vsftpd; add to removal list +- name: 2.2.11 - Remove squid; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'vsftpd' ] }}" - when: ftp_server is defined and not ftp_server + unneeded_packages: "{{ unneeded_packages + [ 'squid' ] }}" tags: - - 2.2.6 + - 2.2.11 -- name: 2.2.5 - Remove bind; add to removal list +- name: 2.2.12 - Remove snmp; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'bind' ] + [ 'unbound' ] }}" - when: dns_server is defined and not dns_server + unneeded_packages: "{{ unneeded_packages + [ 'net-snmp' ] + [ 'net-snmp-libs'] }}" tags: - - 2.2.5 + - 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: @@ -1146,24 +1159,11 @@ tags: - 2.2.17 -- 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.4 - Remove dhcp; add to removal list +- name: 2.2.18 - Remove rsync-daemon; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'dhcp' ] }}" - when: dhcp_server is defined and not dhcp_server + unneeded_packages: "{{ unneeded_packages + [ 'rsync-daemon' ] }}" tags: - - 2.2.4 + - 2.2.18 - name: 2.3.1 - Remove telnet; add to removal list ansible.builtin.set_fact: @@ -1267,7 +1267,10 @@ - name: 3.2.2 - Ensure packet redirect sending is disabled ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.conf.all.send_redirects' : '0' }) }}" + 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 @@ -1397,9 +1400,9 @@ loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv6_network) }}" notify: Flush network routes -- name: 3.1.3 - Disable uncommon network protocols +- name: 3.1 - Disable uncommon network protocols tags: - - 3.1.3 + - 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), @@ -1436,6 +1439,7 @@ with_items: - "{{ uncommon_network }}" +# 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 From 9b7ae002abbecd099c18d5a681b0e5a2a4300775 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 10 Apr 2023 23:58:03 -0400 Subject: [PATCH 18/68] started to rework to v2.0.0 --- .../tasks/type-files/redhat-8-type.yml | 5260 +++++++++-------- 1 file changed, 2733 insertions(+), 2527 deletions(-) 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 2c3160e..c486804 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -11,401 +11,388 @@ # 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 +# 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 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: 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 + setype: modules_conf_t + with_items: + - "{{ unused_filesystems }}" + +# 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: 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 - Report if /var is not on a separate partition + tags: + - 1.1.3 + 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.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 + 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.4 - /var/tmp partition and mount options + tags: + - 1.1.4.1 + - 1.1.4.2 + - 1.1.4.3 + - 1.1.4.4 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.3.1 - 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.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 - - 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 + # 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.1.3 - Add squashfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'squashfs' ] }}" - tags: - - 1.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.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 - - 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 + # 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 - - 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 + # 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 + +# 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.1 - Report if /var/log is not on a separate partition + tags: + - 1.1.5.1 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.5.1 - 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.5.1 - 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.5.1 - 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.6.1 - Report if /var/log/audit is not on a separate partition + tags: + - 1.1.12 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.6.1 - 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.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 + 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.7.1 - Report if /home is not on a separate partition + tags: + - 1.1.7.1 + block: + # Create a empty integer variable and set it as a fact on the managed + # machine. + - name: 1.1.7.1 - 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.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 }}" + + # 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 + + # 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 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.1 - Report if /dev/shm does not have nodev set + tags: + - 1.1.8.1 + 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.1 - 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.8.3 - Report if /dev/shm does not have nosuid set + tags: + - 1.1.8.3 + 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.8.3 - 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.8.2 - Report if /dev/shm does not have noexec set + tags: + - 1.1.8.2 + block: + - name: 1.1.8.2 - 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.8.2 - 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 # Control 1.1.18, 1.1.19, 1.1.20 are for removable media @@ -416,2219 +403,2438 @@ # 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 +# 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 # 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 +# 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 + - 1.2.4 + 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.3 is just to ensure repositores are configured correctly. 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 + +# use the system package module to ensure sudo is installed +- 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 + 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 - # Control 1.2.5 is a manual review to ensure repos are configured per site needs + - 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 - # 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 + - 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 - # 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 + - 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 + 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 - # 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 + # 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.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.service + enabled: true + state: started + + +# 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 + +- 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.2 - 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.2 + +# 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.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: | + Storeage=none + state: present + 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 + 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 + when: selinux is defined and selinux != "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: Regather facts + ansible.builtin.setup: + 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 != "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 != "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" + 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 + 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 + 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.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.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 + - 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: 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: 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: 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 - # 1.7 Mandatory Access Control +# 1.8.4 - XDMCP is not enabled, skipped +# 1.8.5 - Diable media automount - # 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 +# 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" ) - # 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 + - 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. +- 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 + 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 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 + # 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: 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_net_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 + - 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.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 + +# 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.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 +# +# 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({ 'net.ipv4.conf.all.accept_redirects' : '0' }) }}" + 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({ 'net.ipv6.conf.all.accept_redirects' : '0' }) }}" + 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 - ### 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 + - 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 - tags: - - 2.1.1 +# Control 3.1.4 - disable wireless interfaces are machine dependent, skipping - # 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 +# 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 - Install firewall package + when: enable_firewall is defined and enable_firewall == "firewalld" + tags: + - 3.4.1 + - 3.4.2 + block: + - name: 3.4.1.1 - Install firewalld ansible.builtin.dnf: - name: "chrony" + name: "firewalld" 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 + notify: Start firewalld # 3.4.2.1 - - 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: 3.4.2.2 - Disable iptables service + ansible.builtin.service: + name: iptables + state: stopped + enabled: false + masked: true + ignore_errors: true + failed_when: false + + - name: 3.4.1.2 - Remove iptables packages + ansible.builtin.package: + name: iptables-services + state: absent + + - name: 3.4.1.2 - Disable netfilters service + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: "'nftables' in ansible_facts.packages" - - 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: 3.4.1.2 - 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: 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 + # 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 - - name: 2.2.13 - Remove rpcbind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'rpcbind' ] }}" + - name: Notify users to configure the firewall + ansible.builtin.debug: + msg: + - "3.4.2.7 - Ensure default firewalld policy must be handled locally" tags: - - 2.2.13 + - 3.4.2 - # control 2.2.14 skipped. RHEL uses LDAP implemented in SSSD by default +# Control 3.4.3 Configure nftables, skipping - - 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 +- name: 3.4.4.1 - Install iptables + block: + - name: 3.4.4.1 - Install iptables + ansible.builtin.dnf: + name: + - "iptables" + - "iptables-services" + state: present + notify: Start iptables tags: - - 2.2.15 + - 3.4.4.1 - - name: 2.2.17 - Remove ypserv; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypserv' ] }}" + - name: 3.4.4.1 - Disable firewalld + ansible.builtin.service: + name: firewalld + state: stopped + enabled: false + ignore_errors: true tags: - - 2.2.17 + - 3.4.4.1 - - name: 2.3.1 - Remove ypbind; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'ypbind' ] }}" - when: not ypbind + - name: Notify user to configure firewall + ansible.builtin.debug: + msg: + - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" tags: - - 2.3.1 + - 3.4.4.1 - - name: 2.3.2 - Remove telnet; add to removal list - ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'telnet' ] }}" - tags: - - 2.3.2 + when: enable_firewall is defined and enable_firewall == "iptables" + tags: + - 3.4.4 - - 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 +- 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 - # 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 +# Control 3.5 Ensure wireless interfaces are disabled is interface dependent +# skipping - # 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: 3.4.1.1 - Install firewalld +- 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: "firewalld" + name: + - audit + - audit-libs state: present - notify: Start firewalld # 3.4.2.1 + tags: + - 4.1.1.1 - - name: 3.4.2.2 - Disable iptables service + - name: 4.1.1.2 - Enable auditd service ansible.builtin.service: - name: iptables - state: stopped - enabled: false - masked: true - ignore_errors: true + 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: 3.4.2.3 - Disable netfilters service - ansible.builtin.systemd: - name: nftables - state: stopped - enabled: false - masked: true - when: "'nftables' in ansible_facts.packages" + - 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: 3.4.2.4 - Set default zone + - 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: - 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 + dest: "{{ item.path }}" + regexp: '^\$ModLoad\s+imtcp' + state: absent + loop: "{{ rsyslog_module_found.files }}" + when: log_host is defined and not log_host - # 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 + - 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: Notify users to configure the firewall - ansible.builtin.debug: - msg: - - "3.4.2.7 - Ensure default firewalld policy must be handled locally" - tags: - - 3.4.2 - when: enable_firewall is defined and enable_firewall == "firewalld" - tags: - - 3.4.1 - - 3.4.2 + - 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 - # Control 3.4.3 Configure nftables, skipping + - 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: 3.4.4.1 - Install iptables - block: - - name: 3.4.4.1 - Install iptables - ansible.builtin.dnf: - name: - - "iptables" - - "iptables-services" - state: present - notify: Start iptables + - 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: - - 3.4.4.1 - - - name: 3.4.4.1 - Disable firewalld - ansible.builtin.service: - name: firewalld - state: stopped - enabled: false - ignore_errors: true + - 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: - - 3.4.4.1 + - 5.2.5 - - name: Notify user to configure firewall - ansible.builtin.debug: - msg: - - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" + # 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: - - 3.4.4.1 + - 5.2.6 - when: enable_firewall is defined and enable_firewall == "iptables" - tags: - - 3.4.4 + - 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: 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 + - 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 - 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: 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: 4.2.1.2 - Enable Rsyslog - ansible.builtin.service: - name: rsyslog - enabled: true - tags: - - 4.2.1.2 + - 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: 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: 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: 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: 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: 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 + - 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 - # Control 4.2.3, Ensure permissions on log files are configured, is machine dependant - # skipping + - 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: 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.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.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 + - 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 - # Control 5.4.2, Ensure lockout for failed password attempts, requires file replacement - # skipping + - 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 - # Control 5.4.3, Set password retention, requries file replacement - # skipping + - 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 - # Control 5.4.4, Ensure password hashing algorithm is SHA-512, requires file replacement - # skipping + - 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.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.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 + +- 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 + + +# 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.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.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.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 + - 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 - # 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 + - 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 +# 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 - - 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 +# 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: 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 From d0e22699d1390b5b2d9de8f68ce12f671251098c Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 13 Apr 2023 11:27:14 -0400 Subject: [PATCH 19/68] Added new variables for RHEL9 and RHEL8 --- roles/cis_security/defaults/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index 39944a0..7228519 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -53,11 +53,13 @@ ypbind: false graphical_interface: false # Whether to disable the GDM greeter service. The service will disabled on 'false' log_service: "journald" # journald or rsyslog for logging. Choose one. Currently only implemented in RHEL 9! +remote_log_service: false # Whether to configure journald to start systemd-journal-remote.service # Rsyslog 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_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 +logrotate_file: # Linux: RHEL 8/9, Copy file listed for logrotate # network security settings tcpwrappers: false # Linux: Configure tcpwrappers controls. RHEL 7 control only @@ -101,6 +103,7 @@ 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' From 808dae4ec5fbe883bcb5ba00d0a0fe6180067970 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 13 Apr 2023 11:27:31 -0400 Subject: [PATCH 20/68] New rules file for RHEL 8/9 --- roles/cis_security/templates/audit_rules/chacl.rules | 1 + 1 file changed, 1 insertion(+) create mode 100644 roles/cis_security/templates/audit_rules/chacl.rules 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 From 87a93f37c86b0e85d2060deac1d8ff7d6b1729fc Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 13 Apr 2023 11:27:58 -0400 Subject: [PATCH 21/68] New RHEL 9 v1.0 and RHEL8 v2.0 controls --- .../tasks/type-files/redhat-8-type.yml | 1695 ++++++++++------- .../tasks/type-files/redhat-9-type.yml | 431 +++-- 2 files changed, 1276 insertions(+), 850 deletions(-) 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 c486804..a7bcadb 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -7,7 +7,6 @@ # 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. @@ -16,7 +15,6 @@ - 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: @@ -102,7 +100,7 @@ # 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 + - 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 @@ -136,7 +134,7 @@ block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.6 - Set/reset mount counter + - name: 1.1.3 - Set/reset mount counter ansible.builtin.set_fact: mount_count: 0 @@ -170,7 +168,7 @@ block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.3.1 - Set/reset mount counter + - name: 1.1.4.1 - Set/reset mount counter ansible.builtin.set_fact: mount_count: 0 @@ -328,7 +326,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: Report to user if /home does not have nodev set + - name: 1.1.7.1 - 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 @@ -369,7 +367,7 @@ check_mode: false # Let the user know if we did not find the option set. - - name: 1.1.8.3 - Report to user + - name: 1.1.8.3 - 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 @@ -396,13 +394,6 @@ # 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 -# - 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 @@ -411,6 +402,7 @@ name: autofs enabled: false state: stopped + masked: true when: "'autofs' in ansible_facts.packages" tags: - 1.1.22 @@ -470,7 +462,7 @@ with_items: "{{ yumrepos.files }}" when: gpgcheck is defined and gpgcheck -# use the system package module to ensure sudo is installed +# 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 @@ -482,6 +474,8 @@ # 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 @@ -537,7 +531,7 @@ tags: - 1.3.1 - - name: Wait for AIDE initialization to complete + - name: 1.3.1 - Wait for AIDE initialization to complete ansible.builtin.async_status: jid: "{{ aide.ansible_job_id }}" register: aide_status @@ -598,9 +592,8 @@ - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: - name: aidecheck.service + name: aidecheck.timer enabled: true - state: started # 1.5 Secure Boot settings @@ -620,6 +613,7 @@ 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" @@ -647,54 +641,10 @@ tags: - 1.4.2 -# 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 +# 1.5 Additional Process Hardening -- name: 1.5.1 Ensure core dump storage is disabled +- name: 1.5.1 - Ensure core dump storage is disabled ansible.builtin.blockinfile: path: /etc/systemd/coredump.conf create: true @@ -702,12 +652,13 @@ group: root mode: 0644 block: | - Storeage=none + Storage=none state: present + marker: "# {mark} Ansible managed Storage setting" tags: - 1.5.1 -- name: 1.5.2 Ensure core dump backtraces are disabled +- name: 1.5.2 - Ensure core dump backtraces are disabled ansible.builtin.blockinfile: path: /etc/systemd/coredump.conf create: true @@ -717,6 +668,7 @@ block: | ProcessSizeMax=0 state: present + marker: "# {mark} Ansible managed ProcessSize setting" tags: - 1.5.2 @@ -739,6 +691,7 @@ - libselinux - python3-libselinux state: present + register: selinux_installed when: selinux is defined and selinux != "Disabled" tags: - 1.6.1.1 @@ -746,8 +699,9 @@ # 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 +- name: 1.6.1.1 - Regather facts if installed selinux package ansible.builtin.setup: + when: selinux_installed.changed tags: - 1.6.1.1 @@ -785,7 +739,7 @@ group: root mode: 0644 state: touch - when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" + when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed notify: Reboot tags: - 1.6.1.3 @@ -877,7 +831,7 @@ owner: root group: root mode: 0644 - when: issue_net_use is defined and issue_net_use + when: issue_net_use is defined and issue_net_use tags: - 1.7.3 - 1.7.6 @@ -946,7 +900,7 @@ - 1.8.2 - 1.8.3 block: - - name: Set up dconf profile for gdm + - name: 1.8.[2-3] - Set up dconf profile for gdm ansible.builtin.blockinfile: path: /etc/dconf/profile/gdm owner: root @@ -961,7 +915,8 @@ tags: - 1.8.2 - 1.8.3 - - name: Create the defaults file and populate group + + - 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 @@ -975,7 +930,7 @@ - 1.8.2 - 1.8.3 - - name: Enable login screen for gdm + - 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 @@ -1003,8 +958,21 @@ tags: - 1.8.3 -# 1.8.4 - XDMCP is not enabled, skipped +# 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/local.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 @@ -1052,6 +1020,7 @@ # 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" @@ -1075,6 +1044,8 @@ - 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 @@ -1131,13 +1102,11 @@ 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 +- name: 2.2.1 - Create empty list for unneeded packages ansible.builtin.set_fact: unneeded_packages: [] @@ -1229,7 +1198,6 @@ tags: - 2.2.14 - - name: 2.2.15 - Remove ypserv; add to removal list ansible.builtin.set_fact: unneeded_packages: "{{ unneeded_packages + [ 'ypserv' ] }}" @@ -1303,14 +1271,17 @@ - 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: Process removal list +- name: 2.3 - Process removal list ansible.builtin.dnf: name: "{{ unneeded_packages }}" state: absent - + tags: + - 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 @@ -1347,6 +1318,8 @@ 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 @@ -1550,135 +1523,174 @@ # Section 3 - Firewall -- name: 3.4.1 - Install firewall package +- name: 3.4.1 - Configure firewalld when: enable_firewall is defined and enable_firewall == "firewalld" tags: - - 3.4.1 - - 3.4.2 + - 3.4.1.1 block: - - name: 3.4.1.1 - Install firewalld - ansible.builtin.dnf: - name: "firewalld" - state: present - notify: Start firewalld # 3.4.2.1 - - - name: 3.4.2.2 - Disable iptables service - ansible.builtin.service: - name: iptables - state: stopped - enabled: false - masked: true - ignore_errors: true - failed_when: false - - - name: 3.4.1.2 - Remove iptables packages - ansible.builtin.package: + - name: 3.4.1.1 - Install firewalld + ansible.builtin.dnf: + name: "firewalld" + state: present + notify: Start firewalld # 3.4.2.1 + + - name: 3.4.1.2 - Disable iptables service + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: false + masked: true + 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.1.3 - Disable netfilters service + ansible.builtin.systemd: + name: nftables + state: stopped + enabled: false + masked: true + when: "'nftables' in ansible_facts.packages" - - name: 3.4.1.2 - Disable netfilters service - ansible.builtin.systemd: - name: nftables - state: stopped - enabled: false - masked: true - when: "'nftables' in ansible_facts.packages" + - name: 3.4.1.4 - Enable firewalld service + ansible.builtin.systemd: + name: firewalld + enabled: true + state: started + masked: false - - name: 3.4.1.2 - Set default zone - ansible.builtin.lineinfile: + - 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 }}" - when: firewalld_default_zone is defined - 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 + when: firewalld_default_zone is defined + notify: Restart firewalld - - name: Notify users to configure the firewall - ansible.builtin.debug: - msg: - - "3.4.2.7 - Ensure default firewalld policy must be handled locally" - tags: - - 3.4.2 + # 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 -# Control 3.4.3 Configure nftables, skipping + - name: Notify users to configure the firewall + ansible.builtin.debug: + msg: + - "3.4.1.7 - Ensure default firewalld policy must be handled locally" -- name: 3.4.4.1 - Install iptables +- name: 3.4.2 - Configure nftables + when: enable_firewall is defined and enable_firewall == "firewalld" + tags: + - 3.4.2.1 block: - - name: 3.4.4.1 - Install iptables - ansible.builtin.dnf: - name: - - "iptables" - - "iptables-services" - state: present - notify: Start iptables - tags: - - 3.4.4.1 - - - name: 3.4.4.1 - Disable firewalld - ansible.builtin.service: - name: firewalld - state: stopped - enabled: false - ignore_errors: true - tags: - - 3.4.4.1 - - - name: Notify user to configure firewall - ansible.builtin.debug: - msg: - - "Ensure default firewall policy (3.4.4.1.[1-4]) must be handled locally" - tags: - - 3.4.4.1 + - 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 - Create a basic table if none exist + ansible.builtin.command: nft create table inet firewalld NFTables + when: not tables_list + tags: + - 3.4.2.5 + +# Control 3.4.2.[6-9,11] is not set as it is very machine dependant + - 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.4 + - 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 + tags: + - 3.4.4.1 + - 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.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 + - name: 3.4.3.1.3 - Disable firewalld + ansible.builtin.systemd: + name: firewalld + state: stopped + enabled: false + masked: true + when: "'firewalld' in ansible_facts.packages" + tags: + - 3.4.3.1.3 -# Control 3.5 Ensure wireless interfaces are disabled is interface dependent -# skipping + - name: Notify user to configure firewall + ansible.builtin.debug: + msg: + - "Ensure default firewall policy (3.4.4.2.[1-4]) must be handled locally" + tags: + - 3.4.3.2.1 -- 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 +# 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.6.0 + - 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 block: - - name: 4.1.1 - Install Audit + - name: 4.1.1.1 - Install Auditd ansible.builtin.dnf: name: - audit @@ -1687,18 +1699,10 @@ 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 +# # 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' @@ -1766,9 +1770,18 @@ 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-2] - Configure audit log storage size + - name: 4.1.2.[1-3] - Configure audit log storage size ansible.builtin.replace: path: /etc/audit/auditd.conf regexp: "{{ item.find }}" @@ -1788,369 +1801,566 @@ # 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 + - 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/login.rules - src: audit_rules/login.rules + 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.4 + - 4.1.3.1 + - 4.1.3.3 - - name: 4.1.5 - Ensure session initiation information is collected + - name: 4.1.3.2 - Ensure actions as another user are always logged ansible.builtin.template: - dest: /etc/audit/rules.d/sessions.rules - src: audit_rules/sessions.rules + 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.5 + - 4.1.3.2 - - name: 4.1.6 Ensure to collect events that modify date/time + - name: 4.1.3.4 - Ensure to collect events that modify date/time ansible.builtin.template: - dest: /etc/audit/rules.d/datetime.rules + 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.6 + - 4.1.3.4 - - name: 4.1.7 - Ensure modifications to Mandatory Access Controls are collected + # 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/MAC-policy.rules - src: audit_rules/MAC-policy.rules + 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.7 + 4.1.3.5 + + # Control 4.1.3.6 requires a system scan, skipping - - name: 4.1.8 - Ensure modifications to network environment are collected + - name: 4.1.3.[7,10] - Ensure [un]successful file system mounts are collected ansible.builtin.template: - dest: /etc/audit/rules.d/network.rules - src: audit_rules/network.rules + 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.8 + 4.1.3.7 + 4.1.3.10 - # 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 + - name: 4.1.3.7 - Ensure unsuccessful file access attempts are collected ansible.builtin.template: - dest: /etc/audit/rules.d/dac.rules - src: audit_rules/dac.rules + 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.9 + 4.1.3.7 - - name: 4.1.10 - Ensure unsuccessful unauthorized file access attempts are collected + - name: 4.1.3.8 - Ensure events that modify user/group information are collected ansible.builtin.template: - dest: /etc/audit/rules.d/bad-file-access.rules - src: audit_rules/bad-file-access.rules + 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.10 + 4.1.3.8 - - name: 4.1.11 - Ensure events that modify user/group information are collected + - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected ansible.builtin.template: - dest: /etc/audit/rules.d/user-group-info.rules - src: audit_rules/user-group-info.rules + 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.11 + 4.1.3.9 - - name: 4.1.12 - Ensure successful file system mounts are collected + - name: 4.1.3.11 - Ensure session initiation information is collected ansible.builtin.template: - dest: /etc/audit/rules.d/file-system-mounts.rules - src: audit_rules/file-system-mounts.rules + 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.12 + 4.1.3.11 - # Control 4.1.13 - Ensure use of privileged commands is collected, is machine dependent - # skipping + - 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.14 - Ensure file deletion events by users are collected + - name: 4.1.3.13 - Ensure file deletion events by users are collected ansible.builtin.template: - dest: /etc/audit/rules.d/delete.rules + 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.14 + 4.1.3.13 - - name: 4.1.15 - Ensure kernel module loading and unloading is collected + - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected ansible.builtin.template: - dest: /etc/audit/rules.d/modules.rules - src: audit_rules/modules.rules + 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.15 + 4.1.3.14 - - name: 4.1.16 - Ensure sysadmin actions (sudolog) are collected + - 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/sudolog.rules - src: audit_rules/sudolog.rules + 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.16 - - 4.1.3 + 4.1.3.15 - - name: 4.1.17 - Ensure audit configuration is immutable - ansible.builtin.copy: - dest: /etc/audit/rules.d/99-finalize.rules - content: | - -e 2 + - 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.17 - when: enable_audit is defined and enable_audit + 4.1.3.16 -# 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.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.2.1.2 - Enable Rsyslog - ansible.builtin.service: - name: rsyslog - enabled: true - tags: - - 4.2.1.2 + - 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.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.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.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 + - 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 -# Control 4.2.1.5 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent -# skipping + # 4.1.3.21 requires manual verification and ansible won't be able to check until after handlers are run; skipping -- name: 4.2.1.6 - Ensure remote rsyslog messages are only acepted on designated log hosts +# Section 4, Logging +- name: 4.2.1 - Configuring Rsyslog + when: log_service and log_service == "rsyslog" 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.1 - Ensure rsyslog is installed + ansible.builtin.dnf: + name: rsyslog + state: present + tags: + - 4.2.1.1 - - 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.2 - Ensure Rsyslog service is running + ansible.builtin.service: + name: rsyslog + enabled: true + state: started + tags: + - 4.2.1.2 - - name: 4.2.1.6 - Enable loading of imtcp module on log hosts + - name: 4.2.1.3 - Configure journald to forward logs to rsyslog + tags: + - 4.2.1.3 + 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.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: - dest: /etc/rsyslog.d/CIS.conf - regexp: '^\$ModLoad\s+imtcp' - line: "$ModLoad imtcp" + path: /etc/rsyslog.conf + regexp: '^\$FileCreateMode\s+0640' + line: "$FileCreateMode 0640" create: true owner: root group: root mode: 0644 - when: log_host is defined and log_host + state: present + notify: Restart rsyslog + tags: + - 4.2.1.4 - - 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 + - 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: 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 + mode: 0640 + when: rsyslog_file is defined + tags: + - 4.2.1.5 - - 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 + # Control 4.2.1.6 - Ensure rsyslog is configured to send logs to a remote log host is machine dependent + # skipping -- 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.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.2.3 - Ensure journald writes to peristent disk - ansible.builtin.lineinfile: - dest: /etc/systemd/journald.conf - regexp: "^Storage=((?!persistent).)*$" - line: "Storage=persistent" - insertafter: "^#Storage=" + - 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 tags: - - 4.2.2.3 + - 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 + when: log_service and log_service == "journald" + tags: + - 4.2.2.1.1 -# Control 4.2.3, Ensure permissions on log files are configured, is machine dependant -# skipping + # 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-jornal-remote.socket is masked + ansible.builtin.systemd: + name: systemd-journal-remote.socket + enabled: false + masked: true + when: log_service and log_service == "journald" + 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 + when: log_service and log_service == "journald" + 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 + when: log_service and log_service == "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 + when: log_service and log_service == "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).)*$" + line: "ForwardToSyslog=yes" + state: absent + when: log_service and log_service == "journald" + tags: + - 4.2.2.5 + + # Control 4.2.2.6, configure log rotation is machine specific, skipping + # TODO + # Control 4.2.2.7, Ensure permissions on log files are 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 - ansible.builtin.dnf: - name: logrotate - state: present tags: - 4.3.0 + block: + - name: 4.3.0 - Ensure logrotate is installed + ansible.builtin.package: + name: logrotate + state: present -# 4.3 - Ensure logrotate is configured skipped as machine and environment dependent + - 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: 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 +- name: 5.1.0 - Configure cron/at when: "'cronie' in ansible_facts.packages" tags: - - 5.1.1 + - 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.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.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.[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.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.8 -# Restrict at/cron skipped (5.1.8) as is rarely used and environment dependent + - 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 -# 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: @@ -2161,12 +2371,11 @@ 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 + - name: 5.2.2 - Set Permissions on ssh private host keys + tags: + - 5.2.2 block: - - name: 5.2.3 - Find all ssh private host keys + - name: 5.2.2 - Find all ssh private host keys ansible.builtin.find: paths: /etc/ssh file_type: file @@ -2174,19 +2383,19 @@ 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) + - 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: ssh_keys mode: 0640 loop: "{{ ssh_host_out.files }}" + + - name: 5.2.3 - Set Permissions on ssh public host keys 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 + - name: 5.2.3 - Find all ssh public host keys ansible.builtin.find: paths: /etc/ssh file_type: file @@ -2194,118 +2403,154 @@ register: ssh_hostpub_out changed_when: false - - name: 5.2.4 - Set permissions on all ssh public host keys + - 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) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\ *{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - { key: 'AllowUsers', value: "{{ ssh_allowed_groups }}" } + when: ssh_allowed_users is defined and ssh_allowed_users + tags: + - 5.2.4 + + - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.key }}\s+{{ 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 + tags: + - 5.2.4 + + - 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 tags: - 5.2.4 - - name: 5.2.5 - Set LogLevel to {{ ssh_log_level }} or more verbose, but not debug + - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) + ansible.builtin.lineinfile: + path: "/etc/ssh/sshd_config" + regexp: ^{{ item.0 }}\ *{{ item.1 }} + line: "{{ item.0 }} {{ item.1 }}" + notify: Restart sshd + loop: + - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } + when: ssh_denied_groups is defined and ssh_denied_groups + tags: + - 5.2.4 + + - 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 + 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 + - name: 5.2.6 - Ensure SSH is configured to use PAM ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - state: absent - regexp: '^X11Forwarding\s*yes' + 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 SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} or less + - name: 5.2.7 - Ensure PermitRootLogin is disbled 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" + line: "PermitRootLogin no" + regexp: '^PermitRootLogin\s*[^n]' + insertafter: '^#PermitRootLogin\s*[^n]' notify: Restart sshd tags: - 5.2.7 - - name: 5.2.8 - Ensure IgnoreRhosts is set + - name: 5.2.8 - Ensure HostbasedAuthentication is disabled ansible.builtin.lineinfile: path: /etc/ssh/sshd_config - line: "IgnoreRhosts yes" - regexp: '^IgnoreRhosts\s*[^y]' + line: "HostbasedAuthentication no" + regexp: '^HostbasedAuthentication\s*[^n]' + insertafter: '^#HostbasedAuthentication\s*[^n]' notify: Restart sshd tags: - 5.2.8 - - name: 5.2.9 - Ensure HostbasedAuthentication is disabled + - name: 5.2.9 - Ensure SSH PermitEmptyPasswords is disabled ansible.builtin.lineinfile: path: /etc/ssh/sshd_config - line: "HostbasedAuthentication no" - regexp: '^HostbasedAuthentication\s*[^n]' + state: absent + regexp: '^PermitEmptyPasswords\s*[^n]' notify: Restart sshd tags: - 5.2.9 - - name: 5.2.10 Ensure PermitRootLogin is disbled + - name: 5.2.10 - Ensure PermitUserEnvironment is disabled ansible.builtin.lineinfile: path: /etc/ssh/sshd_config - line: "PermitRootLogin no" - regexp: '^PermitRootLogin\s*[^n]' + state: absent + regexp: '^PermitUserEnvironment\s*[^n]' notify: Restart sshd tags: - 5.2.10 - - name: 5.2.11 - Ensure SSH PermitEmptyPasswords is disabled + - name: 5.2.11 - Ensure IgnoreRhosts is set ansible.builtin.lineinfile: path: /etc/ssh/sshd_config - state: absent - regexp: '^PermitEmptyPasswords\s*[^n]' + line: "IgnoreRhosts yes" + regexp: '^IgnoreRhosts\s*[^y]' + insertafter: '^#IgnoreRhosts\s*[^y]' notify: Restart sshd tags: - 5.2.11 - - name: 5.2.12 - Ensure PermitUserEnvironment is disabled + - name: 5.2.12 - Disable X11 forwarding ansible.builtin.lineinfile: path: /etc/ssh/sshd_config + regexp: '^X11Forwarding\s*yes' state: absent - regexp: '^PermitUserEnvironment\s*[^n]' notify: Restart sshd tags: - 5.2.12 - - name: 5.2.13 - Ensure SSH Idle Timeout is configured ClientAliveInterval + - name: 5.2.13 - Disable TCP Forwarding 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" + 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 SSH LoginGraceTime is set to {{ ssh_grace_time }} or less + - name: 5.2.14 - Ensure system crypto policy isn't overriden in SSH ansible.builtin.lineinfile: - path: /etc/ssh/sshd_config - line: "LoginGraceTime {{ ssh_grace_time }}" - regexp: "^LoginGraceTime {{ ssh_grace_time }}" - insertafter: "^#LoginGraceTime" + path: "/etc/ssh/sshd_config" + state: absent + regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' notify: Restart sshd tags: - 5.2.14 @@ -2315,56 +2560,72 @@ 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 is configured to use PAM + - name: 5.2.16 - Ensure SSH MaxAuthTires is set to {{ ssh_max_auth_tries }} ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "UsePAM yes" - regexp: '^UsePAM\s+[yes|no]' + 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 - Disable TCP Forwarding + - name: 5.2.17 - Limit max unauthenticated startups ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - line: "AllowTcpForwarding no" - regexp: '^AllowTcpForwarding\s+(yes|no)' - insertafter: "^#AllowTcpForwarding" + 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 unauthenticated startups + - name: 5.2.18 - Limit max sessions to {{ ssh_max_sessions }} ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - line: "maxstartups 10:30:60" - regexp: '^maxstartups\s+10:30:60' + 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 - Limit max sessions + - name: 5.2.19 - Ensure SSH LoginGraceTime is set to {{ ssh_grace_time }} ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - line: "maxsessions {{ ssh_max_sessions }}" - regexp: '^maxsessions\s+[{{ ssh_max_sessions }}]' + 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 system crypto policy isn't overriden in SSH + - name: 5.2.20 - Ensure SSH Idle Timeout is configured ClientAliveInterval ansible.builtin.lineinfile: - path: "/etc/ssh/sshd_config" - state: absent - regexp: '^\s*(CRYPTO_POLICY\s*=.*)$' + 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 @@ -2386,13 +2647,74 @@ 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" -# Control section 5.3, authselect, cannot be used with Red Hat IPA or Microsoft AD + - 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. -- name: 5.4.1 - Configure PAM files and password requirements +# Control 5.4.3, Set password retention, requries file replacement +# skipping + +- name: 5.5.1 - Configure PAM files and password requirements + tags: + - 5.5.1 block: - - name: 5.4.1 - require at least one digit in passwords + - name: 5.5.1 - require at least one digit in passwords ansible.builtin.lineinfile: path: /etc/security/pwquality.conf line: dcredit = -1 @@ -2400,7 +2722,7 @@ insertafter: "# dcredit = 0" when: password_req_digit - - name: 5.4.1 - require at least one uppercase letter in passwords + - name: 5.5.1 - require at least one uppercase letter in passwords ansible.builtin.lineinfile: path: /etc/security/pwquality.conf line: ucredit = -1 @@ -2408,7 +2730,7 @@ insertafter: "# ucredit = 0" when: password_req_upper - - name: 5.4.1 - require at least one lowercase letter in passwords + - name: 5.5.1 - require at least one lowercase letter in passwords ansible.builtin.lineinfile: path: /etc/security/pwquality.conf line: lcredit = -1 @@ -2416,7 +2738,7 @@ insertafter: "^# lcredit = 0" when: password_req_lower - - name: 5.4.1 - Require at least one special character in passwords + - name: 5.5.1 - Require at least one special character in passwords ansible.builtin.lineinfile: path: /etc/security/pwquality.conf line: ocredit = -1 @@ -2424,55 +2746,90 @@ insertafter: "^# ocredit = 0" when: password_req_digit - - name: 5.4.1 - Require at least {{ password_min_length }} characters in passwords + - 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.4.1 + - 5.5.2 -# Control 5.4.2, Ensure lockout for failed password attempts, requires file replacement -# skipping +- name: 5.5.2 - Ensure lockout time for failed password attempts is configured + ansible.builtin.replace: + path: /etc/security/faillock.conf + regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" + replace: "unlock_time={{ password_failed_time }}" + after: "#\ *deny" + owner: root + group: root + mode: 0600 + tags: + - 5.5.2 -# Control 5.4.3, Set password retention, requries file replacement -# skipping +# 5.5.3 - Password retention involves configuring pam.conf, skipping -# Control 5.4.4, Ensure password hashing algorithm is SHA-512, requires file replacement -# 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.5.1.1 - Ensure password expiration is {{ password_expire_days }} days or less +- 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.5.1.1 + - 5.6.1.1 -- name: 5.5.1.2 - Ensure password change days is set to {{ password_min_days }} +- 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.5.1.2 + - 5.6.1.2 -- name: 5.5.1.3 - Ensure password warning days is set to {{ password_warning_days }} +- 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.5.1.3 + - 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.5.1.4 - Disable accounts that are inactive for {{ password_inactive_lock_days }} days after password expiration +- 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 }}).)*$" @@ -2481,33 +2838,34 @@ group: root mode: 0600 tags: - - 5.5.1.4 + - 5.6.1.4 -# 5.5.1.5, Ensure all users last password change date is in the past, +# 5.6.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 +# 5.6.2, Ensure system accounts are secured, requires manual intervention, skipping -- name: 5.5.3 - Ensure default shell timeout is {{ shell_timeout }} seconds or less +- name: 5.6.3 - Ensure default shell timeout is {{ shell_timeout }} seconds or less ansible.builtin.blockinfile: path: "{{ item }}" - block: "TMOUT={{ shell_timeout }}" + block: | + TMOUT={{ shell_timeout }} + export TMOUT marker: "# {mark} Ansible Managed CIS Timeout" loop: - /etc/bashrc - /etc/profile tags: - - 5.5.3 + - 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.5.4 - Ensure default group for root is GID 0 +- 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.5.4 + - 5.6.4 -- name: 5.5.5 - Ensure umask is set +- name: 5.6.5 - Ensure default user umask is set ansible.builtin.replace: path: "{{ item }}" replace: " umask {{ default_umask }}" @@ -2516,141 +2874,143 @@ - /etc/bashrc - /etc/profile tags: - - 5.5.5 + - 5.6.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 +# 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.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.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.7 - Add root to the wheel group + - name: 5.6.6 - Set root password ansible.builtin.user: name: root - groups: wheel - append: true - tags: - - 5.7.0 + 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 -- 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 + # 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 - - 6.1.4 -- name: 6.1.[3,5] - Ensure permissions on /etc/shadow /etc/gshadow +- 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: 0000 + mode: 0644 loop: - - shadow - - gshadow + - passwd + - passwd- + - group + - group- tags: - 6.1.3 - 6.1.5 + - 6.1.7 + - 6.1.9 -- name: 6.1.[6-9] - Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- +- 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 - with_items: - - passwd- + loop: + - shadow - shadow- - - group- + - gshadow - gshadow- tags: + - 6.1.4 - 6.1.6 - - 6.1.7 - 6.1.8 - - 6.1.9 + - 6.1.10 -# Control 6.1.10, Ensure no world writable files exist, is system dependent so we are only +# 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.10 - Ensure no world writable files exist +- name: 6.1.11 - Ensure no world writable files exist + tags: + - 6.1.11 block: - - name: 6.1.10 - Find any world writiable files + - 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.10 - Print any world writable files found + - 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 - tags: - - 6.1.10 -# Control 6.1.11, Ensure no unowned files exist, is system dependent so we are only +# 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.11 - Ensure no unowned files exist +- name: 6.1.12 - Ensure no unowned files exist + tags: + - 6.1.12 block: - - name: 6.1.11 - Find any unowned files + - 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.11 - Print any unowned files found + - 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 - 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 + tags: + - 6.1.12 block: - - name: 6.1.12 - Find any ungrouped files + - 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.12 - Print any ungrouped files found + - 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 - tags: - - 6.1.12 -# Control 6.1.13, Audit SUID executables, is a verification and is system dependent. +# 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.14, Audit SGID executables, is a verification and is system dependent. +# 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 password fields are not empty +- 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 }'" @@ -2658,183 +3018,154 @@ 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 +- name: 6.2.2 - Ensure all groups in /etc/passwd exist in /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 + - 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.14 - Report to user any unreferenced groups + - 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 - tags: - - 6.2.14 -- name: 6.2.15 - Report on duplicate UIDs in /etc/passwd +- name: 6.2.3 - Report on duplicate UIDs in /etc/passwd + tags: + - 6.2.3 block: - - name: 6.2.15 - Use script to pull the list of duplicate UIDs + - 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.15 - Print report of duplicated UIDs to user + - 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 - tags: - - 6.2.15 -- name: 6.2.16 - Report on duplicate GIDs in /etc/group +- name: 6.2.4 - Report on duplicate GIDs in /etc/group + tags: + - 6.2.4 block: - - name: 6.2.16 - Use script to pull the list of duplicate GIDs + - 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.16 - Print report of duplcate GIDs to user + - 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 - tags: - - 6.2.16 -- name: 6.2.17 - Report on duplicate users in /etc/passwd +- name: 6.2.5 - Report on duplicate users in /etc/passwd + tags: + - 6.2.5 block: - - name: 6.2.17 - Use script to pull the list of users + - 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.17 - Print report of duplicate users to user + - 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 - tags: - - 6.2.17 -- name: 6.2.18 - Report on duplicate groups in /etc/group +- name: 6.2.6 - Report on duplicate groups in /etc/group + tags: + - 6.2.6 block: - - name: 6.2.18 - Use script to pull the list of groups + - 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.18 - Print report of duplicate groups to user + - 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 - tags: - - 6.2.18 -- name: 6.2.19 - Report on shadow group in /etc/group +- name: 6.2.7 - Ensure root PATH integrity + tags: + - 6.2.7 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 + - name: 6.2.7 - Run script on path variable + ansible.builtin.script: files/path_check.sh changed_when: false - failed_when: shadow_out.rc == "2" + 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.19 - Print report of shadow group to user + - name: 6.2.8 - Report on mulitple accounts with UID of 0 ansible.builtin.debug: - msg: "Shadow group exists in /etc/group. Remove" + msg: + - "Accounts with UID zero in addition to root" + - " {{ rootuid.stdout_lines }}" changed_when: true - when: shadow_out.stdout + when: rootuid.stdout != 'root' -- name: 6.2.20 - Report on users that do not have a home directory +- name: 6.2.9 - Report on users that do not have a home directory + tags: + - 6.2.9 block: - - name: 6.2.20 - Use script to find the users + - 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.20 - Print report of users that do not have a home directory + - 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 - tags: - - 6.2.20 + +# 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 index d87ee74..dbe5762 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -93,7 +93,7 @@ # 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 + - 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 @@ -321,7 +321,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: Report to user if /home does not have nodev set + - name: 1.1.13 - 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 @@ -343,7 +343,8 @@ check_mode: false # Let the user know if we did not find the option set. - - name: 1.1.15 - Report to user + - name: 1.1.15 - 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 @@ -362,7 +363,7 @@ check_mode: false # Let the user know if we did not find the option set. - - name: 1.1.16 - Report to user + - name: 1.1.16 - 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 @@ -397,6 +398,7 @@ name: autofs enabled: false state: stopped + masked: true when: "'autofs' in ansible_facts.packages" tags: - 1.1.22 @@ -579,9 +581,8 @@ - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: - name: aidecheck.service + name: aidecheck.timer enabled: true - state: started - name: 1.3.3 Create aidecheck config.d dir ansible.builtin.file: @@ -657,7 +658,7 @@ # 1.5 Additional Process Hardening -- name: 1.5.1 Ensure core dump storage is disabled +- name: 1.5.1 - Ensure core dump storage is disabled ansible.builtin.blockinfile: path: /etc/systemd/coredump.conf create: true @@ -665,12 +666,13 @@ group: root mode: 0644 block: | - Storeage=none + Storage=none state: present + marker: "# {mark} Ansible managed Storage setting" tags: - 1.5.1 -- name: 1.5.2 Ensure core dump backtraces are disabled +- name: 1.5.2 - Ensure core dump backtraces are disabled ansible.builtin.blockinfile: path: /etc/systemd/coredump.conf create: true @@ -680,6 +682,7 @@ block: | ProcessSizeMax=0 state: present + marker: "# {mark} Ansible managed ProcessSize setting" tags: - 1.5.2 @@ -702,6 +705,7 @@ - libselinux - python3-libselinux state: present + register: selinux_installed when: selinux is defined and selinux != "Disabled" tags: - 1.6.1.1 @@ -709,8 +713,9 @@ # 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 +- name: 1.6.1.1. - Regather facts if installed selinux package ansible.builtin.setup: + when: selinux_installed.changed tags: - 1.6.1.1 @@ -748,7 +753,7 @@ group: root mode: 0644 state: touch - when: ansible_selinux.status == "disabled" and selinux | lower != "disabled" + when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed notify: Reboot tags: - 1.6.1.3 @@ -910,7 +915,7 @@ - 1.8.3 - 1.8.4 block: - - name: Set up dconf profile for gdm + - name: 1.8.[2-4] - Set up dconf profile for gdm ansible.builtin.blockinfile: path: /etc/dconf/profile/gdm owner: root @@ -927,7 +932,7 @@ - 1.8.3 - 1.8.4 - - name: Create the defaults file and populate group + - 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 @@ -941,7 +946,7 @@ - 1.8.2 - 1.8.3 - - name: Enable login screen for gdm + - 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 @@ -995,7 +1000,7 @@ # 1.8.7 TODO # 1.8.8 TODO # 1.8.9 TODO -# 1.8.10 TODO +# 1.8.10 - Ensure XDCMP is not enabled, skipping # 1.10 Configure crypto policy - name: 1.10.0 - Configure crypto-policy @@ -1057,8 +1062,7 @@ # 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 +- name: 2.2.1 - Create empty list for unneeded packages ansible.builtin.set_fact: unneeded_packages: [] @@ -1098,6 +1102,7 @@ - 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 @@ -1192,14 +1197,17 @@ - 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: Process removal list +- name: 2.3 - Process removal list ansible.builtin.dnf: name: "{{ unneeded_packages }}" state: absent - + tags: + - 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 @@ -1446,6 +1454,16 @@ 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: @@ -1453,54 +1471,50 @@ state: stopped enabled: false masked: true - when: "'nftables' in ansible_facts.packages" + when: enable_firewall is defined and enable_firewall == "firewalld" -- name: 3.4.1.1 - Install firewall package - tags: - - 3.4.1 - - 3.4.2 - block: - - 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 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.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 + - 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 - # Benchmark 3.4.2.[3-7] is not set as it is very machine dependant +# 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 +- 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 - tags: - - 4.1.3.0 when: enable_audit is defined and enable_audit block: - name: 4.1.1.1 - Install Auditd @@ -1527,8 +1541,8 @@ tags: - 4.1.1.2 - # use the replace module to add it to grub bootloader and then notify - # grub to rebuild + # 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 @@ -1583,6 +1597,7 @@ 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 @@ -1613,9 +1628,34 @@ # 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 - 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/sudolog.rules + dest: /etc/audit/rules.d/00-sudolog.rules src: audit_rules/sudolog.rules owner: root group: root @@ -1627,7 +1667,7 @@ - name: 4.1.3.2 - Ensure actions as another user are always logged ansible.builtin.template: - dest: /etc/audit/rules.d/user_emulation.rules + dest: /etc/audit/rules.d/00-user_emulation.rules src: audit_rules/user_emulation.rules owner: root group: root @@ -1636,9 +1676,9 @@ tags: - 4.1.3.2 - - name: 4.1.3.4 Ensure to collect events that modify date/time + - name: 4.1.3.4 - Ensure to collect events that modify date/time ansible.builtin.template: - dest: /etc/audit/rules.d/datetime.rules + dest: /etc/audit/rules.d/00-datetime.rules src: audit_rules/datetime.rules owner: root group: root @@ -1650,7 +1690,7 @@ # 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/network.rules + dest: /etc/audit/rules.d/00-network.rules src: audit_rules/network.rules owner: root group: root @@ -1659,20 +1699,23 @@ tags: 4.1.3.5 - - name: 4.1.3.6 - Ensure successful file system mounts are collected + # 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/file-system-mounts.rules + 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.6 + 4.1.3.7 + 4.1.3.10 - - name: 4.1.3.7 - Ensure unsuccessful unauthorized file access attempts are collected + - name: 4.1.3.7 - Ensure unsuccessful file access attempts are collected ansible.builtin.template: - dest: /etc/audit/rules.d/bad-file-access.rules + dest: /etc/audit/rules.d/00-bad-file-access.rules src: audit_rules/bad-file-access.rules owner: root group: root @@ -1683,7 +1726,7 @@ - name: 4.1.3.8 - Ensure events that modify user/group information are collected ansible.builtin.template: - dest: /etc/audit/rules.d/user-group-info.rules + dest: /etc/audit/rules.d/00-user-group-info.rules src: audit_rules/user-group-info.rules owner: root group: root @@ -1694,7 +1737,7 @@ - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected ansible.builtin.template: - dest: /etc/audit/rules.d/dac.rules + dest: /etc/audit/rules.d/00-dac.rules src: audit_rules/dac.rules owner: root group: root @@ -1703,20 +1746,9 @@ 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/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/sessions.rules + dest: /etc/audit/rules.d/00-sessions.rules src: audit_rules/sessions.rules owner: root group: root @@ -1727,7 +1759,7 @@ - name: 4.1.3.12 - Ensure system logins are collected ansible.builtin.template: - dest: /etc/audit/rules.d/login.rules + dest: /etc/audit/rules.d/00-login.rules src: audit_rules/login.rules owner: root group: root @@ -1738,7 +1770,7 @@ - name: 4.1.3.13 - Ensure file deletion events by users are collected ansible.builtin.template: - dest: /etc/audit/rules.d/delete.rules + dest: /etc/audit/rules.d/00-delete.rules src: audit_rules/delete.rules owner: root group: root @@ -1749,7 +1781,7 @@ - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected ansible.builtin.template: - dest: /etc/audit/rules.d/MAC-policy.rules + dest: /etc/audit/rules.d/00-MAC-policy.rules src: audit_rules/MAC-policy.rules owner: root group: root @@ -1760,7 +1792,7 @@ - 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/chcon.rules + dest: /etc/audit/rules.d/00-chcon.rules src: audit_rules/chcon.rules owner: root group: root @@ -1771,7 +1803,7 @@ - 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/setfacl.rules + dest: /etc/audit/rules.d/00-setfacl.rules src: audit_rules/setfacl.rules owner: root group: root @@ -1782,8 +1814,8 @@ - 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/setfacl.rules - src: audit_rules/setfacl.rules + dest: /etc/audit/rules.d/00-chacl.rules + src: audit_rules/chacl.rules owner: root group: root mode: 0600 @@ -1793,7 +1825,7 @@ - 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/usermod.rules + dest: /etc/audit/rules.d/00-usermod.rules src: audit_rules/usermod.rules owner: root group: root @@ -1804,7 +1836,7 @@ - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected ansible.builtin.template: - dest: /etc/audit/rules.d/modules.rules + dest: /etc/audit/rules.d/00-modules.rules src: audit_rules/modules.rules owner: root group: root @@ -1827,7 +1859,6 @@ # 4.1.3.21 requires manual verification and ansible won't be able to check until after handlers are run; skipping -## Control is 640 or less restrictive, so chose 600 #- name: 4.1.4.[1-3,5-7] - Set autid files to mode 600, user root, group root # ansible.builtin.file: # path: "{{ item }}" @@ -2019,13 +2050,14 @@ tags: - 4.2.2.1.4 - - name: 4.2.2.2 Ensure jorunald service is enabled + - name: 4.2.2.1.4 Ensure jorunald service is masked ansible.builtin.systemd: name: systemd-journal-remote.service - enabled: true + enabled: false + masked: true when: log_service and log_service == "journald" tags: - - 4.2.2.2 + - 4.2.2.1.4 - name: 4.2.2.3 - Ensure journald compresses large files ansible.builtin.lineinfile: @@ -2049,7 +2081,7 @@ tags: - 4.2.2.4 - - name: 4.2.2.5 - Forward journald logs to rsyslog IF rsyslog is sending logs to a log host + - 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).)*$" @@ -2065,14 +2097,33 @@ # 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 - ansible.builtin.dnf: - name: logrotate - state: present tags: - 4.3.0 + block: + - name: 4.3.0 - Ensure logrotate is installed + ansible.builtin.package: + name: logrotate + state: present -# 4.3 - Ensure logrotate is configured skipped as machine and environment dependent + - 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 # @@ -2084,17 +2135,29 @@ tags: - 5.1.0 block: - - name: Create the cron/at allow files (5.1.8) - ansible.builtin.copy: - dest: "{{ item }}" - content: "" - force: false + - 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: 0644 - with_items: - - /etc/cron.allow - - /etc/at.allow + 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 @@ -2134,35 +2197,33 @@ - 5.1.6 - 5.1.7 - - name: 5.1.8 - Ensure cron is restricted to authorized users - ansible.builtin.lineinfile: - path: /etc/cron.allow - regexp: "^{{ item }}" - line: "{{ item }}" + - 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 - loop: - - cron_allow + state: file + when: at_allow and at_allow | length > 0 tags: - 5.1.8 - name: 5.1.9 - Ensure at is restricted to authorized users ansible.builtin.lineinfile: path: /etc/at.allow - regexp: "^{{ item }}" - line: "{{ item }}" + regexp: ^{{ item.0 }} + line: "{{ item.0 }}" owner: root group: root mode: 0600 + create: true loop: - - at_allow + - "{{ at_allow }}" + when: at_allow and at_allow | length > 0 tags: - 5.1.9 -# If you want to deploy your own SSH config file, exclude the entire 5.2.0 tag - name: 5.2 - SSH File configurations - when: "'openssh-server' in ansible_facts.packages" tags: - 5.2.0 block: @@ -2215,50 +2276,50 @@ mode: 0644 loop: "{{ ssh_hostpub_out.files }}" - - name: 5.2.4 - Ensure SSH access is limited + - name: 5.2.4 - Ensure SSH access is limited (AllowUsers) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\s+{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: - - [ "AllowUsers", ssh_allowed_users ] + - { key: 'AllowUsers', value: "{{ ssh_allowed_users }}" } when: ssh_allowed_users is defined and ssh_allowed_users tags: - 5.2.4 - - name: 5.2.4 - Ensure SSH access is limited + - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\s+{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: - - [ AllowGroups, "{{ ssh_allowed_groups }}" ] + - { key: 'AllowGroups', value: "{{ ssh_allowed_groups }}" } when: ssh_allowed_groups is defined and ssh_allowed_groups tags: - 5.2.4 - - name: 5.2.4 - Ensure SSH access is limited + - name: 5.2.4 - Ensure SSH access is limited (DenyUsers) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\s+{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" - notify: Restart sshd + regexp: "^{{ item.key }}\ *{{ item.value }}" + line: "{{ item.key }} {{ item.value }}" loop: - - [ DenyUsers, "{{ ssh_denied_users }}" ] + - { key: 'DenyUsers', value: "{{ ssh_denied_users }}" } + notify: Restart sshd when: ssh_denied_users is defined and ssh_denied_users tags: - 5.2.4 - - name: 5.2.4 - Ensure SSH access is limited + - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\s+{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: - - [ DenyGroups, "{{ ssh_denied_groups }}" ] + - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } when: ssh_denied_groups is defined and ssh_denied_groups tags: - 5.2.4 @@ -2278,11 +2339,12 @@ 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 + - name: 5.2.7 - Ensure PermitRootLogin is disbled ansible.builtin.lineinfile: path: /etc/ssh/sshd_config line: "PermitRootLogin no" @@ -2428,8 +2490,7 @@ tags: - 5.2.20 - - # Make sure the sudoers file includes the requirement to use pty +# 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 @@ -2559,11 +2620,11 @@ when: password_req_digit - name: 5.5.2 - Ensure lockout attempts for failed password attempts is configured - ansible.builtin.replace: + ansible.builtin.lineinfile: path: /etc/security/faillock.conf regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" - replace: "deny={{ password_failed_attempts }}" - after: "#\ *deny" + line: "deny = {{ password_failed_attempts }}" + insertafter: "#\ *deny" owner: root group: root mode: 0600 @@ -2571,17 +2632,38 @@ - 5.5.2 - name: 5.5.2 - Ensure lockout time for failed password attempts is configured - ansible.builtin.replace: + ansible.builtin.lineinfile: path: /etc/security/faillock.conf regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" - replace: "unlock_time={{ password_failed_time }}" - after: "#\ *deny" + 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: @@ -2626,10 +2708,7 @@ # 5.6.1.5, Ensure all users last password change date is in the past, # is not easily automated. Will revisit later -# Control 5.6.2, Ensure system accounts are secured, requires manual intervention, skipping - -# Control 5.5.4, Ensure password hashing algorithm is SHA-512, requires file replacement -# skipping +# 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: @@ -2738,7 +2817,7 @@ changed_when: true when: ww_files.stdout -# Control 6.1.11, Ensure no unowned files exist, is system dependent so we are only +# 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: @@ -2756,8 +2835,6 @@ 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.11 - Ensure no ungrouped files exist tags: - 6.1.11 @@ -2813,7 +2890,7 @@ changed_when: true loop: "{{ empty_passwords.stdout_lines }}" -- name: 6.2.2 - Ensure no legacy "+" entries exist in password files +- name: 6.2.2 - /etc/shadow password fields are not empty ansible.builtin.command: "awk -F: '($2 == \"\")' {{ item }}" loop: - /etc/shadow @@ -2823,10 +2900,16 @@ # Contorl 6.2.3, requires manual intervention, skipping -- name: 6.2.4 - Report on duplicate UIDs in /etc/passwd +- 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 @@ -2891,7 +2974,6 @@ changed_when: true when: duplicate_groups.stdout - - name: 6.2.8 - Ensure root PATH integrity tags: - 6.2.8 @@ -2910,7 +2992,7 @@ - " {{ path_check.stdout }}" when: path_check.stdout and not ansible_check_mode -- name: 6.2.9 - Report on multiple accounts with UID of 0 +- name: 6.2.9 - Ensure root is the only UID 0 account tags: - 6.2.9 block: @@ -2928,12 +3010,25 @@ changed_when: true when: rootuid.stdout != 'root' -# Control 6.2.10 is environment dependent, skipping -# Control 6.2.11 is environment dependent, skipping -# Control 6.2.12 is environment dependent, skipping -# Control 6.2.13 is environment dependent, skipping -# Control 6.2.14 is environment dependent, skipping -# Control 6.2.15 is environment dependent, skipping -# Control 6.2.16 is environment dependent, skipping +- 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 -# Controls 6.2.[9-13,20] are recommended to be handled by monitoring software + - 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 From cea599c72e3b7dfd15d3e5c84ed0d6e5461b41cd Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 17 Apr 2023 15:12:39 -0400 Subject: [PATCH 22/68] Updates for RHEL9 rules --- roles/cis_security/templates/audit_rules/datetime.rules | 8 +++----- roles/cis_security/templates/audit_rules/network.rules | 5 +++-- roles/cis_security/templates/audit_rules/sudolog.rules | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/roles/cis_security/templates/audit_rules/datetime.rules b/roles/cis_security/templates/audit_rules/datetime.rules index 7f79962..7a02993 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 --w /etc/localtime -p wa -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 \ No newline at end of file diff --git a/roles/cis_security/templates/audit_rules/network.rules b/roles/cis_security/templates/audit_rules/network.rules index 63d590e..19cf3a7 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 -S setdomainname -k -F system-locale +-a always,exit -F arch=b32 -S sethostname -S 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/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 From 8e4a98af0e0579dea2d8480f42c31cdcc2813640 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 17 Apr 2023 15:12:49 -0400 Subject: [PATCH 23/68] Added RHEL9 rules --- docs/controls_list.md | 589 +++++++++++++++++++++++------------------- 1 file changed, 327 insertions(+), 262 deletions(-) diff --git a/docs/controls_list.md b/docs/controls_list.md index d865696..a5d7275 100644 --- a/docs/controls_list.md +++ b/docs/controls_list.md @@ -1,265 +1,330 @@ Below are the tags used in the CIS roles on Linux Machines. -| RHEL 8 / Fedora 31 / CentOS 8 / Oracle 8 | RHEL 7 / Centos 7 / Oracle 7 / SLES 15 | Ubuntu 18.04 / 20.04 | Control Description | Notes | +| RHEL 9 | 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 +| -----------------| -----------------|--------------------- | -----------------|--------------------- | --------------------- | +| | 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.6 | Remove squashfs +| 1.1.1.2 | 1.1.1.4 | 1.1.1.7 | 1.1.1.7 | Remove udf +| 1.1.2 | 1.1.2 | | 1.1.2 | Ensure tmpfs is configured +| 1.1.2.1 | | 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.2 | 1.1.3 | 1.1.4 | 1.1.3 | Ensure nodev option on /tmp partition +| 1.1.2.3 | 1.1.4 | 1.1.5 | 1.1.4 | Ensure nosuid option set on /tmp partition +| 1.1.2.4 | 1.1.5 | 1.1.3 | 1.1.5 | Ensure noexec option set on /tmp partition +| 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.6 | Report if /var is not on a separate partition +| 1.1.3.2 | 1.1.3.2 | | | Report if /var is missing nodev option +| 1.1.3.3 | 1.1.3.3 | | | Report if /var is missing noexec option +| 1.1.4 | 1.1.4 | | | Configure /var/tmp +| 1.1.4.1 | 1.1.4.1 | 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.4.2 | 1.1.4.2 | 1.1.13 | 1.1.8 | Ensure nodev option on /var/tmp partition +| 1.1.4.3 | 1.1.4.3 | 1.1.14 | 1.1.9 | Ensure nosuid option set on /var/tmp partition +| 1.1.4.4 | 1.1.4.4 | 1.1.12 | 1.1.10 | Ensure noexec option set on /var/tmp partition +| 1.1.5 | 1.1.5 | | | Configure /var/log +| 1.1.5.1 | 1.1.5.1 | 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.5.2 | 1.1.5.2 | | | Ensure nodev option on /var/log partition +| 1.1.5.3 | 1.1.5.3 | | | Ensure nosuid option set on /var/log partition +| 1.1.5.4 | 1.1.5.4 | | | Ensure noexec option set on /var/log partition +| 1.1.6 | 1.1.6 | | | Configure /var/log/audit +| 1.1.6.1 | 1.1.6.1 | 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.6.2 | 1.1.6.2 | | | Ensure nodev option on /var/log/audit partition +| 1.1.6.3 | 1.1.6.3 | | | Ensure nosuid option set on /var/log/audit partition +| 1.1.6.4 | 1.1.6.4 | | | Ensure noexec option set on /var/log/audit partition +| 1.1.7 | 1.1.7 | | | Configure home +| 1.1.7.1 | 1.1.7.1 | 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.7.2 | 1.1.7.2 | | | Ensure nodev option on /home partition +| 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.15 | Report if /dev/shm does not have nodev set +| 1.1.8.2 | 1.1.8.3 | 1.1.9 | 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.22 | Disable automounting +|| 1.1.23 | 1.1.24 | 1.1.23 | Disable USB storage module +| 1.2.0 | 1.2.0 | | | Update System to latest +|| 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.1 | 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.2 | 1.2.4 | 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 | | 1.3.1 | Ensure sudo is installed +| 5.3.2 | 5.3.2 | | 1.3.2 | Ensure sudo commands use pty +| 5.3.3 | 5.3.3 | | 1.3.3 | Ensure sudo log file exists +| 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.4.1 | Ensure aide is installed +| 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.5.2 | Ensure bootloader password is set |skipped: environment dependent| +| 1.4.2 | 1.4.2 | 1.4.2 | 1.5.1 | Set permissions on grub bootloader files +| | | 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.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.6.2 | Ensure address space layout reandomization (ASLR) is enabled +| | | 1.5.4 | | Ensure prelink is not installed +| 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.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.7.1 | 1.7.1 | 1.7.1.1 | 1.8.1.1 | Install motd banners +| 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.8.1.3 | Install issue.net banners +| 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.8.1.5 | Ensure permissions on /etc/issue are configured +| 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 | | Ensure GNOME Display Manager is removed +| 1.8.2 | 1.8.2 | 1.8.2 | 1.8.2 | Ensure GDM banner set up +| 1.8.3 | | 1.8.3 | | Ensure last logged in user display is disabled +| 1.8.4 | | | | Ensure GDM screen locks when user is idle +| 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 \(SLES 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.2.1.1 | Verify Time synchronization is in use +| 2.1.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 | Disable display manager +| 2.2.18 | 2.2.20 | 2.2.21 \(SLES 2.2.18\) | 2.2.16 | Remove rsync +| 2.2.2 | 2.2.3 | 2.2.3 | 2.2.3 | Remove avahi +| 2.2.12 | 2.2.14 | 2.2.14 | 2.2.14 | Remove snmp +| 2.2.11 | 2.2.13 | 2.2.13 | 2.2.13 | Remove Web proxy +| 2.2.10 | 2.2.12 | 2.2.12 | 2.2.12 | Remove Samba server +| 2.2.9 | 2.2.11 | 2.2.11 | 2.2.11 | Remove Mail Relay Server +| 2.2.8 | 2.2.10 | 2.2.10 | 2.2.10 | Remove http Server +| 2.2.6 | 2.2.\[7,8\] | 2.2.9 | 2.2.9 | Remove FTP Server +| 2.2.5 | 2.2.6 | 2.2.8 | 2.2.8 | Remove DNS server +| 2.2.\[12,16\] | 2.2.18 | 2.2.7 | 2.2.7 | Remove nfs server +| 2.2.16 | 2.2.19 | 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.4 | 2.2.5 | 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.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.4 | Disable cups as we my not be able to uninstall it +| | 2.2.15 | 2.2.16 | 2.2.17 | Remove NIS Server +| | 2.2.16 | | | Remove telnet-server +| 2.2.15 | 2.2.17 | 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.3 | | | | Remove NFS Client +| 2.3.1 | 2.3.2 | 2.2.19 \(SLES 2.2.8\) | 2.3.4 | Remove telnet client +| 2.3.2 | 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.2.1 | 3.1.1 | 3.1.1 | 3.1.2 | Ensure IP forwarding is disabled | included in 3.1.0 +| 3.2.2 | 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.3.1 | 3.2.1 | 3.2.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.2.2 | Ensure ICMP redirects are not accepted | included in 3.2.0 +| 3.3.3 | 3.2.3 | 3.2.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.2.4 | Ensure suspicious packets are logged | included in 3.2.0 +| 3.3.5 | 3.2.5 | 3.2.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.2.6 | Ensure bogus ICMP responses are ignored | included in 3.2.0 +| 3.3.7 | 3.2.7 | 3.2.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.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.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 | Ensure SCTP is disabled +| 3.1.3 | 3.1.3 | 3.5.1 | 3.4.1 | Ensure DCCP is disabled +| 3.1.4 | 3.1.4 | 3.5.4 | 3.4.4 | Ensure TIPC is disabled +| | | 3.5.3 | 3.4.3 | Ensure RDS is disabled +| | | 3.6.1 | 3.5.1.1 | Ensure a firewall package is installed (firewalld or iptables) +| | | 3.6.2-5 | | Ensure firewall is configured +| | | | | Ensure firewalld is enabled and running +| | | | | Disable iptables service +| | | | | Disable netfilters service +| | | | | Ensure default zone is set for firewalld +| | | | | Set default zone in firewalld +| | | | | Ensure network interfaces are assigned to appropriate zone |skipped: machine dependent| +| | 3.4.1 | | | Configure firewalld (block tag) +| | 3.4.1.1 | | | Ensure firewalld is installed +| | 3.4.1.2 | | | Ensure iptables-services is not installed +| | 3.4.1.3 | | | Ensure nftables either not installed or masked with firewalld +| | 3.4.1.4 | | | Ensure firewalld is enabled and running +| 3.4.2.1 | 3.4.1.5 | | | Ensure firewalld default zone is set +| 3.4.1 | 3.4.2 | | | Configure nftables (block tag) +| 3.4.1.1 | 3.4.2.1 | | | Ensure nftables is installed +| 3.4.1.2 | | | | Ensure a single firewall package package is installed +| | 3.4.2.2 | | | Ensure firewalld is either uninstalled or masked with nftables +| | 3.4.2.3 | | | Ensure iptables-services not installed with nftables +| 3.4.2.2 | 3.4.2.5 | | | Ensure an nftables table is installed +| 3.4.1.1 | 3.4.2.10 | | | Ensure nftables is started +| | 3.4.3 | | | Configure iptables (block tag) +| | 3.4.3.1.1| | | Ensure iptables is installed +| | 3.4.3.1.2| | | Ensure nftables is not installed with iptables +| | 3.4.3.1.3| | | Ensure firewald is not installed with iptables +| | | | 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.5.3 | Configure nftables (skipped: seldom used) +| | | 3.6.1 | | Ensure iptables are flushed +| | | 3.6.2-5 | 3.5.4 | Configure iptables (skipped: machine dependent) +| | | 3.7 | 3.6 | Ensure wireless interfaces are disabled |skipped: machine dependent| +| | 3.6.0 | 3.3.3 | 3.7 | Disable IPv6 +| 4.1.0 | 4.1.0 | | | Configure Auditing (block tag) +| 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 | Enable auditd service +| 4.1.1.2 | 4.1.1.3 | 4.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 | Ensure audit_backlog_limit is sufficient +| 4.1.2.1 | 4.1.2.1 | 4.1.1.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 | Ensure audit logs are not automatically deleted +| 4.1.2.3 | 4.1.2.3 | 4.1.1.2 | 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.14 | Ensure changes to system administration scope \(sudoers\) is collected +| 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.15 | Ensure sysadmin actions (sudolog) are collected +| 4.1.3.4 | 4.1.3.4 | 4.1.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.5 | Ensure modifications to network environment are collected +| 4.1.3.6 | 4.1.3.6 | 4.1.12 | 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.10 | Ensure unsuccessful unauthorized file access attempts are collected +| 4.1.3.8 | 4.1.3.8 | 4.1.5 | 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.9 | Ensure modifications to discretionary access controls are collected +| 4.1.3.10 | 4.1.3.10 | 4.1.13 | 4.1.12 | Ensure successful file system mounts are collected +| 4.1.3.11 | 4.1.3.11 | 4.1.9 | 4.1.8 | Ensure session initiation information is collected +| 4.1.3.12 | 4.1.3.12 | 4.1.8 | 4.1.7 | Ensure system logins and logouts are collected +| 4.1.3.13 | 4.1.3.13 | 4.1.14 | 4.1.13 | Ensure file deletion events by users are collected +| 4.1.3.14 | 4.1.3.14 | 4.1.7 | 4.1.6 | Ensure modifications to Mandatory Access Controls are collected +| 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 | | | Ensure all attempts to use the setfacl command are collected +| 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 | | | Ensure all attempts to use the usermod command are collected +| 4.1.3.19 | 4.1.3.19 | 4.1.17 | 4.1.16 | Ensure kernel module loading and unloading is collected +| 4.1.3.20 | 4.1.3.20 | 4.1.18 | 4.1.17 | Ensure audit configuration is immutable +| 4.1.4.1 | | | | Ensure audit log files are mode 0640 more more restrictive +| 4.1.4.2 | | | | Ensure only authorized users own audit log files +| 4.1.4.3 | | | | Ensure only authorized groups own audit log flies +| 4.1.4.5 | | | | Ensure audit config files are mode 640 or more restrictive +| 4.1.4.6 | | | | Ensure audit config files are owned by user root +| 4.1.4.7 | | | | Ensure audit tools are mode 0755 or more restrictive +| 4.1.4.9 | | | | Ensure audit tools are owned by user root +| 4.1.4.10 | | | | Ensure audit tools belong to group root +| 4.2.1.1 | 4.2.1.1 | 4.2.3 | 4.2.1.1 | Ensure rsyslog is installed +| 4.2.1.2 | 4.2.1.2 | | 4.2.1.2 | Enable rsyslog +| 4.2.1.3 | 4.2.1.3 | | | Ensure journald is configured to send messages to rsyslog +| 4.2.1.4 | 4.2.1.4 | 4.2.1.3 | 4.2.1.4 | Ensure rsyslog default file permissions are configured +| 4.2.1.5 | 4.2.1.5 | 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.6 | 4.2.1.6 | 4.2.1.4 | 4.2.1.5 | 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.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 | | | Configure journald to send logs to a remote log host +| 4.2.2.1.1| 4.2.2.1.1| | | Ensure systemd-journal-remote is installed +| 4.2.2.1.2| 4.2.2.1.2| | | Ensure systemd-journal-remote is configured +| 4.2.2.1.3| 4.2.2.1.3| | | Ensure systmd-journal-remote is enabled +| 4.2.2.1.4| 4.2.2.1.4| | | Ensure journald is not configured to recieve logs from a remote client +| 4.2.2.2 | 4.2.2.2 | | | Ensure journald service is enabled +| 4.2.2.3 | 4.2.2.3 | | 4.2.2.2 | Ensure journald compresses large files +| 4.2.2.4 | 4.2.2.4 | | 4.2.2.3 | Ensure journald writes to peristent disk +| 4.2.2.5 | 4.2.2.5 | | 4.2.2.1 | Forward journald logs to rsyslog IF rsyslog is sending logs to a log host +| 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 | | | Configure Cron/At +| 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 | Ensure permissions on /etc/crontab +| 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 | Ensure permissions on /etc/cron.daily +| 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 | Ensure permissions on /etc/cron.monthly +| 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 | 5.1.8 | Ensure cron restricted to authorized users |skipped: except in RHEL 8/9| +| 5.1.9 | 5.1.9 | 5.1.8 | 5.1.8 | Ensure at restricted to authorized users |skipped: except in RHEL 8/9| +| 5.2.1.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 | Set permissions on SSH file +| 5.2.1.2 | 5.2.2 | | 5.2.2 | Set Permissions on ssh private host keys +| 5.2.1.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.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.1.5 | 5.2.5 | 5.2.3 | 5.2.5 | Set LogLevel to INFO +| 5.2.1.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.10 | Ensure PermitRootLogin is disbled +| 5.2.1.8 | 5.2.8 | 5.2.7 | 5.2.9 | Ensure HostbasedAuthentication is disabled +| 5.2.1.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.12 | Ensure PermitUserEnvironment is disabled +| 5.2.1.11 | 5.2.11 | | | Ensure IgnoreRhosts is enabled +| 5.2.1.12 | 5.2.12 | 5.2.4 | 5.2.6 | Disable X11 forwarding +| 5.2.1.13 | 5.2.13 | | 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.19 | Ensure SSH Banner is configured +| 5.2.1.16 | 5.2.16 | 5.2.5 | 5.2.7 | Ensure SSH MaxAuthTires is set +| 5.2.1.17 | 5.2.17 | | 5.2.22 | Ensure MaxStartups is configured +| 5.2.1.18 | 5.2.18 | | 5.2.23 | Ensure MaxSessions is set to 10 or less +| 5.2.1.19 | 5.2.19 | 5.2.13 | 5.2.17 | Ensure SSH LoginGraceTime is set +| 5.2.1.20 | 5.2.20 | 5.2.12 | 5.2.16 | Ensure SSH Idle Timeout is configured +| | | | 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.3.0 | 5.3.0 | | | Configure privilege escalation +| 5.3.1 | 5.3.1 | | | Ensure sudo is installed +| 5.3.2 | 5.3.2 | | | Ensure sudo commands use pty +| 5.3.3 | 5.3.3 | | | Ensure sudo log file exists +| 5.3.4 | 5.3.4 | | | Ensure users must provide password for escalation +| 5.3.5 | 5.3.5 | | | Ensure re-authentication for privlege ecalation is not disabled globally +| 5.3.7 | 5.3.7 | 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 | +| 5.5.1 | 5.5.1 | 5.3.1 | 5.3.1 | Configure PAM files and password requirements +| 5.5.2 | 5.5.2 | | | Ensure lockout for failed password attempts +| 5.5.4 | 5.5.4 | | | Configure password hashing algorithm is SHA-512 or YESCRIPT +| 5.6.1.1 | 5.6.1.1 | 5.4.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.4.1.3 | Ensure password warning days is set to 7 +| 5.6.1.3 | 5.6.1.3 | 5.4.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.4.1.4 | Disable accounts that are inactive for 30 days after password expiration +| 5.6.3 | 5.6.3 | 5.4.5 | 5.4.5 | Ensure default shell timeout is 900 seconds or less +| 5.6.4 | 5.6.4 | 5.4.3 | 5.4.3 | Ensure default group for root is GID 0 +| 5.6.5 | 5.6.5 | 5.4.4 | 5.4.4 | Ensure default umask is set +| 6.1.1,6.1.3| 6.1.3,6.1.5| 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| 6.1.3,6.1.5 | Ensure permissions on /etc/shadow /etc/gshadow +| 6.1.2,4,6,8 | 6.1.7,8,9,10| 6.1.6,7,8,9| 6.1.6,7,8,9 | Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- +| 6.1.1 | 6.1.3 | 6.1.2 | 6.1.2 | Ensure permissions on /etc/passwd +| 6.1.2 | 6.1.7 | 6.1.6 | 6.1.6 | Ensure permissions on /etc/passwd- +| 6.1.3 | 6.1.5 | 6.1.4 | 6.1.4 | Ensure permissions on /etc/group +| 6.1.4 | 6.1.9 | 6.1.8 | 6.1.8 | Ensure permissions on /etc/group- +| 6.1.5 | 6.1.4 | 6.1.3 | 6.1.3 | Ensure permissions on /etc/shadow +| 6.1.6 | 6.1.8 | 6.1.7 | 6.1.7 | Ensure permissions on /etc/shadow- +| 6.1.7 | 6.1.6 | 6.1.5 | 6.1.5 | Ensure permissions on /etc/gshadow +| 6.1.8 | 6.1.10 | 6.1.9 | 6.1.9 | Ensure permissions on /etc/gshadow- +| 6.1.9 | 6.1.11 | 6.1.10 | 6.1.10 | Report if any world writable files exist +| 6.1.10 | 6.1.12 | 6.1.11 | 6.1.11 | Report if any unowned files or directories exist +| 6.1.11 | 6.1.13 | 6.1.12 | 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 | | | Ensure accounts in /etc/passwd use shadowed passwords +| 6.2.2 | 6.2.1 | 6.2.1 | 6.2.1 | Report on password fields that are empty +| 6.2.4 | 6.2.3 | 6.2.16 | 6.2.16 | Report on duplicate UIDs in /etc/passwd +| 6.2.5 | 6.2.4 | 6.2.17 | 6.2.17 | Report on duplicate GIDs in /etc/group +| 6.2.6 | 6.2.5 | 6.2.18 | 6.2.18 | Report on duplicate users in /etc/passwd +| 6.2.7 | 6.2.6 | 6.2.19 | 6.2.19 | Report on duplicate groups in /etc/group +| 6.2.8 | 6.2.7 | 6.2.6 | 6.2.7 | Ensure root PATH integrity +| 6.2.9 | 6.2.8 | 6.2.5 | 6.2.6 | Report on multiple accounts with UID of 0 +| 6.2.10 | 6.2.10 | 6.2.9 | 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.15 | 6.2.15 | Report on groups in /etc/passwd with a GID not in /etc/group +| | | | 6.2.20 | Report if shadow group exists in /etc/group From 169ef258b7b5a3f6bfa3231f9b12ecc241f34a4a Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 17 Apr 2023 15:13:03 -0400 Subject: [PATCH 24/68] Added mew versions --- roles/cis_security/README.md | 8 +- roles/cis_security/defaults/main.yml | 9 +- .../tasks/type-files/redhat-7-type.yml | 12 +- .../tasks/type-files/redhat-8-type.yml | 258 +++++++++----- .../tasks/type-files/redhat-9-type.yml | 318 +++++++++++------- 5 files changed, 391 insertions(+), 214 deletions(-) diff --git a/roles/cis_security/README.md b/roles/cis_security/README.md index 43e79c0..bb5315f 100644 --- a/roles/cis_security/README.md +++ b/roles/cis_security/README.md @@ -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 | @@ -38,7 +39,7 @@ been made to update the controls to work with the newer operating systems. Older 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: @@ -160,4 +161,5 @@ ways files can be manipuldated with modules. - 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.0 \ No newline at end of file diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index 7228519..f6d882f 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -52,13 +52,15 @@ 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' -log_service: "journald" # journald or rsyslog for logging. Choose one. Currently only implemented in RHEL 9! +# 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 -# Rsyslog 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 @@ -97,6 +99,7 @@ ssh_allowed_users: "" # Linux: RHEL9, space separated list of users to 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: "adm" # 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 password_req_digit: true # Linux 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 79c29a7..ef9c7c2 100644 --- a/roles/cis_security/tasks/type-files/redhat-7-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-7-type.yml @@ -1523,7 +1523,9 @@ # Section 3 - Firewall - - name: 3.6 - Install firewalld + - name: 3.6 - Configure firewalld + tags: + - 3.6.0 block: - name: 3.6.1 - Install firewalld ansible.builtin.package: @@ -1554,10 +1556,10 @@ notify: Restart firewalld when: enable_firewall is defined and enable_firewall == "firewalld" - tags: - - 3.6.1 - - name: 3.6.1 - Install iptables + - name: 3.6.1 - Configure iptables + tags: + - 3.6.0 block: - name: 3.6.1 Install iptables ansible.builtin.package: @@ -1576,8 +1578,6 @@ 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" 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 a7bcadb..ce3a741 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -7,6 +7,7 @@ # 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. @@ -15,6 +16,7 @@ - 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: @@ -128,13 +130,13 @@ # 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 - Report if /var is not on a separate partition +- 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 mount counter + - name: 1.1.3 - Set/reset /var mount counter ansible.builtin.set_fact: mount_count: 0 @@ -148,27 +150,44 @@ 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 + - 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.4 - /var/tmp partition and mount options +- name: 1.1.4.1 - Configure /var/tmp tags: - - 1.1.4.1 - - 1.1.4.2 - - 1.1.4.3 - - 1.1.4.4 + - 1.1.4 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.4.1 - Set/reset mount counter + - name: 1.1.4 - Set/reset /var/tmp mount counter ansible.builtin.set_fact: mount_count: 0 @@ -199,13 +218,13 @@ # 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 + - 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 nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + 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.4 + - 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) @@ -221,23 +240,23 @@ # 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 + - 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 noexec set" - when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 + 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.2 + - 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.1 - Report if /var/log is not on a separate partition +- name: 1.1.5 - Configure /var tags: - - 1.1.5.1 + - 1.1.5 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.5.1 - Set/reset mount counter + - name: 1.1.5 - Set/reset /var/log mount counter ansible.builtin.set_fact: mount_count: 0 @@ -251,24 +270,47 @@ 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 + - 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 - 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.1 - Report if /var/log/audit is not on a separate partition +- name: 1.1.6 - Configure /var/log/audit tags: - - 1.1.12 + - 1.1.6 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.6.1 - Set/reset mount counter + - name: 1.1.6 - Set/reset /var/log/audit mount counter ansible.builtin.set_fact: mount_count: 0 @@ -282,24 +324,28 @@ 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 + - 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.1 - Report if /home is not on a separate partition +- name: 1.1.7 - Configure /home tags: - - 1.1.7.1 + - 1.1.7 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.7.1 - Set/reset mount counter + - name: 1.1.7 - Set/reset mount counter ansible.builtin.set_fact: mount_count: 0 @@ -314,6 +360,8 @@ 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. @@ -322,23 +370,35 @@ 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.1 - Report to user if /home does not have nodev set + - 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.1 - Report if /dev/shm does not have nodev set +- name: 1.1.8 - Configure /dev/shm tags: - - 1.1.8.1 + - 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 @@ -348,16 +408,19 @@ check_mode: false # Let the user know if we did not find the option set. - - name: 1.1.8.1 - Report to user + - 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.3 - Report if /dev/shm does not have nosuid set +- name: 1.1.8.4 - Report if /dev/shm does not have nosuid set tags: - - 1.1.8.3 + - 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 @@ -366,39 +429,41 @@ changed_when: false check_mode: false -# Let the user know if we did not find the option set. - - name: 1.1.8.3 - 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 - -# Grep out /dev/shm and see if the given option is set. -- name: 1.1.8.2 - Report if /dev/shm does not have noexec set +- name: 1.1.8.3 - Report if /dev/shm does not have noexec set tags: - - 1.1.8.2 + - 1.1.8.3 block: - - name: 1.1.8.2 - Determine if /dev/shm has noexec set + - 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.2 - Report to user + - 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.18, 1.1.19, 1.1.20 are for removable media +# 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.service: + ansible.builtin.systemd: name: autofs enabled: false state: stopped @@ -426,7 +491,6 @@ - name: 1.2.[3,4] - Ensure GPG keys are configured tags: - 1.2.3 - - 1.2.4 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' @@ -443,7 +507,7 @@ replace: "gpgcheck=1" when: gpgcheck and ansible_distribution == "Fedora" - # 1.2.3 is just to ensure repositores are configured correctly. Environment dependant + # 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/ @@ -641,6 +705,28 @@ 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 @@ -739,7 +825,7 @@ group: root mode: 0644 state: touch - when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed + when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed notify: Reboot tags: - 1.6.1.3 @@ -1056,18 +1142,6 @@ 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) @@ -1526,8 +1600,12 @@ - name: 3.4.1 - Configure firewalld when: enable_firewall is defined and enable_firewall == "firewalld" tags: - - 3.4.1.1 + - 3.4.1 block: + - name: 3.4.1 - Configure firewalld + debug: + msg: "3.4.1 - Configure firewalld" + - name: 3.4.1.1 - Install firewalld ansible.builtin.dnf: name: "firewalld" @@ -1586,10 +1664,14 @@ - "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 == "firewalld" + when: enable_firewall is defined and enable_firewall == "nftables" tags: - - 3.4.2.1 + - 3.4.2 block: + - name: 3.4.2 - Configure nftables + debug: + msg: "3.4.2 - Configure nftables" + - name: 3.4.2.1 - ensure nftables is installed ansible.builtin.dnf: name: nftables @@ -1609,11 +1691,19 @@ # Control 3.4.2.4 requires manual review, skipping (can be a TODO) - - 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 + - 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.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.2.[6-9,11] is not set as it is very machine dependant @@ -1689,6 +1779,8 @@ # 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: @@ -2055,7 +2147,7 @@ tags: - 4.2.1.3 block: - - name: Find any rsyslog files where all logs are being forwarded to a loghost + - 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 @@ -2091,7 +2183,7 @@ owner: root group: root mode: 0640 - when: rsyslog_file is defined + when: rsyslog_file is defined and rsyslog_file | length > 0 tags: - 4.2.1.5 @@ -2228,7 +2320,7 @@ # Control 4.2.2.6, configure log rotation is machine specific, skipping # TODO - # Control 4.2.2.7, Ensure permissions on log files are configured, is machine dependant, skipping + # Control 4.2.2.7, Ensure journald default file permissions configured, is machine dependant, skipping # Control 4.2.3 is machine specific, skipping @@ -2414,11 +2506,11 @@ - name: 5.2.4 - Ensure SSH access is limited (AllowUsers) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\ *{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: - - { key: 'AllowUsers', value: "{{ ssh_allowed_groups }}" } + - { key: 'AllowUsers', value: "{{ ssh_allowed_users }}" } when: ssh_allowed_users is defined and ssh_allowed_users tags: - 5.2.4 @@ -2426,7 +2518,7 @@ - name: 5.2.4 - Ensure SSH access is limited (AllowGroups) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.key }}\s+{{ item.value }} + regexp: ^{{ item.key }}\ *{{ item.value }} line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: @@ -2450,8 +2542,8 @@ - name: 5.2.4 - Ensure SSH access is limited (DenyGroups) ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config" - regexp: ^{{ item.0 }}\ *{{ item.1 }} - line: "{{ item.0 }} {{ item.1 }}" + regexp: ^{{ item.key }}\ *{{ item.value }} + line: "{{ item.key }} {{ item.value }}" notify: Restart sshd loop: - { key: 'DenyGroups', value: "{{ ssh_denied_groups }}" } @@ -2758,7 +2850,7 @@ ansible.builtin.lineinfile: path: /etc/security/faillock.conf regexp: "^\ *deny\ *=\ *{{ password_failed_attempts }}*$" - line: "deny={{ password_failed_attempts }}" + line: "deny = {{ password_failed_attempts }}" insertafter: "#\ *deny" owner: root group: root @@ -2767,11 +2859,11 @@ - 5.5.2 - name: 5.5.2 - Ensure lockout time for failed password attempts is configured - ansible.builtin.replace: + ansible.builtin.lineinfile: path: /etc/security/faillock.conf regexp: "^\ *unlock_time\ *=\ *{{ password_failed_time }}*$" - replace: "unlock_time={{ password_failed_time }}" - after: "#\ *deny" + line: "unlock_time={{ password_failed_time }}" + insertafter: "#\ *deny" owner: root group: root mode: 0600 diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index dbe5762..36bb100 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -83,17 +83,14 @@ # Create and configure the local-fs systemd service file -- name: 1.1.[2-5] - Ensure /tmp is configured +- name: 1.1.2 - 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: 1.1.1.2 - Ensure the local-fs directory is created + - 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 @@ -101,6 +98,11 @@ 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 @@ -118,16 +120,21 @@ 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.6 - Report if /var is not on a separate partition +- name: 1.1.3 - Configure /var tags: - - 1.1.6 + - 1.1.3 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.6 - Set/reset mount counter + - name: 1.1.3 - Set/reset /var mount counter ansible.builtin.set_fact: mount_count: 0 @@ -135,43 +142,58 @@ # 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 + - 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.6 - Report to user + - 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 +- name: 1.1.4.1 - Configure /var/tmp tags: - - 1.1.7 + - 1.1.4 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.7 - Set/reset mount counter + - name: 1.1.4 - Set/reset /var/tmp 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 + - 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 }}" @@ -179,60 +201,60 @@ with_items: - "{{ ansible_mounts }}" tags: - - 1.1.7 + - 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.7 - Report to user if /var/tmp not on 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.7 + - 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.8 - Report to user if /var/tmp does not have nodev set + - 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 nodev set" - when: mount_options is defined and "nodev" not in mount_options and mount_count == 0 + 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.8 + - 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.9 - Report to user if /var/tmp does not have nosuid set + - 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.9 + - 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.10 - Report to user if /var/tmp does not have noexec set + - 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 noexec set" - when: mount_options is defined and "noexec" not in mount_options and mount_count == 0 + 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.10 + - 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.11 - Report if /var/log is not on a separate partition +- name: 1.1.5 - Configure /var tags: - - 1.1.11 + - 1.1.5 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.11 - Set/reset mount counter + - name: 1.1.5 - Set/reset /var/log mount counter ansible.builtin.set_fact: mount_count: 0 @@ -240,30 +262,53 @@ # 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 + - name: 1.1.5.1 - 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.11 - Report to user + - 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 - 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.12 - Report if /var/log/audit is not on a separate partition +- name: 1.1.6 - Configure /var/log/audit tags: - - 1.1.12 + - 1.1.6 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.12 - Set/reset mount counter + - name: 1.1.6 - Set/reset /var/log/audit mount counter ansible.builtin.set_fact: mount_count: 0 @@ -271,30 +316,34 @@ # 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 + - 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 }}" + 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.12 - Report to user + - 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.13 - Report if /home is not on a separate partition +- name: 1.1.7 - Configure /home tags: - - 1.1.13 + - 1.1.7 block: # Create a empty integer variable and set it as a fact on the managed # machine. - - name: 1.1.13 - Set/reset mount counter + - name: 1.1.7 - Set/reset mount counter ansible.builtin.set_fact: mount_count: 0 @@ -302,38 +351,52 @@ # 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 + - 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.13 - Report to user if /home is 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.13 - Report to user if /home does not have nodev set + - 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.15 - Report if /dev/shm does not have nodev set +- name: 1.1.8 - Configure /dev/shm tags: - - 1.1.15 + - 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 @@ -343,17 +406,19 @@ check_mode: false # Let the user know if we did not find the option set. - - name: 1.1.15 - Report to user if /dev/shm does not have nodev 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.16 - Report if /dev/shm does not have nosuid set +- name: 1.1.8.4 Report if /dev/shm does not have nosuid set tags: - - 1.1.16 + - 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 @@ -362,39 +427,41 @@ 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 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 - -# 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 +- name: 1.1.8.3 - Report if /dev/shm does not have noexec set tags: - - 1.1.17 + - 1.1.8.3 block: - - name: 1.1.17 - Determine if /dev/shm has noexec set + - 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.17 - Report to user + - 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.18, 1.1.19, 1.1.20 are for removable media +# 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.service: + ansible.builtin.systemd: name: autofs enabled: false state: stopped @@ -422,7 +489,6 @@ - name: 1.2.[3,4] - Ensure GPG keys are configured tags: - 1.2.3 - - 1.2.4 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' @@ -432,7 +498,7 @@ replace: "gpgcheck=1" when: gpgcheck and ansible_distribution == "Fedora" - # 1.2.3 is just to ensure repositores are configured correctly. Environment dependant + # 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/ @@ -520,7 +586,7 @@ tags: - 1.3.1 - - name: Wait for AIDE initialization to complete + - name: 1.3.1 - Wait for AIDE initialization to complete ansible.builtin.async_status: jid: "{{ aide.ansible_job_id }}" register: aide_status @@ -644,17 +710,28 @@ tags: - 1.4.2 -- name: 1.4.2 - Set permissions on user.cfg (if exists) - ansible.builtin.file: - path: "{{ item }}" - owner: root - group: root - mode: 0600 - loop: - - "{{ usercfgdir.stat.path}}" - when: usercfgdir.stat.exists +- name: 1.4.3 - Require authorization to enter Rescue mode tags: - - 1.4.2 + - 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 @@ -713,7 +790,7 @@ # 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 +- name: 1.6.1.1 - Regather facts if installed selinux package ansible.builtin.setup: when: selinux_installed.changed tags: @@ -1516,6 +1593,8 @@ - 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: @@ -1619,6 +1698,7 @@ - {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 @@ -1859,43 +1939,43 @@ # 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-3,5-7] - Set autid 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/*' -## TODO, find the log_file in the audit.conf file and set it too -# 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 +- name: 4.1.4.[1-2,5-7] - Set autid 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 +- 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 @@ -1920,7 +2000,7 @@ tags: - 4.2.1.3 block: - - name: Find any rsyslog files where all logs are being forwarded to a loghost + - 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 @@ -1956,7 +2036,7 @@ owner: root group: root mode: 0640 - when: rsyslog_file is defined + when: rsyslog_file is defined and rsyslog_file | length > 0 tags: - 4.2.1.5 @@ -2093,7 +2173,7 @@ # Control 4.2.2.6, configure log rotation is machine specific, skipping # TODO - # Control 4.2.2.7, Ensure permissions on log files are configured, is machine dependant, skipping + # Control 4.2.2.7, Ensure journald default file permissions configured, is machine dependant, skipping # Control 4.2.3 is machine specific, skipping From f51be61f8f25f322a1cfda47d8b407b4ff9a9382 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 17 Apr 2023 15:26:04 -0400 Subject: [PATCH 25/68] Ran README.md through a linter --- roles/cis_security/README.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/roles/cis_security/README.md b/roles/cis_security/README.md index bb5315f..e19eb74 100644 --- a/roles/cis_security/README.md +++ b/roles/cis_security/README.md @@ -2,7 +2,7 @@ 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. -### 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. @@ -36,19 +36,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-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 @@ -59,12 +63,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] ``` @@ -72,16 +77,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}} @@ -95,12 +103,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. @@ -114,17 +123,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 }}" @@ -149,12 +160,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 + - 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 @@ -162,4 +174,4 @@ ways files can be manipuldated with modules. - 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) -- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0 \ No newline at end of file +- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0 From 36b5f6d15f439bac78c5333100c179f90367340a Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 17 Apr 2023 15:28:09 -0400 Subject: [PATCH 26/68] Ran file through linter --- docs/controls_list_win.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/controls_list_win.md b/docs/controls_list_win.md index 40d4685..446c07f 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 | | -----------------|--------------------- | -----------------|--------------------- | --------------------- |-----------------| From a260594dcf9fbb5c39c9fe6bc5155ebde7e12f21 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:54:48 +0200 Subject: [PATCH 27/68] Update duplicate_groups.sh --- roles/cis_security/files/duplicate_groups.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 1876f861066a7429e2688ded6692b09226ad423d Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:55:49 +0200 Subject: [PATCH 28/68] Update duplicate_guids.sh --- roles/cis_security/files/duplicate_guids.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6465e24462e31bed5a8af84c0e033c62986cbe90 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:56:31 +0200 Subject: [PATCH 29/68] Update duplicate_uids.sh --- roles/cis_security/files/duplicate_uids.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e537c5a25eb3d28539b64dd984c11261a7ea9645 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:57:29 +0200 Subject: [PATCH 30/68] Update duplicate_users.sh --- roles/cis_security/files/duplicate_users.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 74542e0149bf7ecb1fae02cea41fff02cb0441b6 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:58:20 +0200 Subject: [PATCH 31/68] Update non_existant_homedirs.sh --- roles/cis_security/files/non_existant_homedirs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 461add5fd883e858be30bde9124a3165920f18e6 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:58:59 +0200 Subject: [PATCH 32/68] Update path_check.sh --- roles/cis_security/files/path_check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 64e978e50061325bd18c584eab7ca20b7b09ee69 Mon Sep 17 00:00:00 2001 From: Pierre-Gronau-ndaal <72132223+Pierre-Gronau-ndaal@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:59:35 +0200 Subject: [PATCH 33/68] Update undefined_groups.sh --- roles/cis_security/files/undefined_groups.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e30ead30bfc73ebe7f221f84c0bba5e0e222b623 Mon Sep 17 00:00:00 2001 From: dsglaser Date: Thu, 27 Apr 2023 19:41:41 -0400 Subject: [PATCH 34/68] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4de70bb..34c6ff0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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.04 / 20.04 LTS and certain Windows servers. ### Introduction @@ -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 | @@ -41,7 +42,7 @@ been made to update the controls to work with the newer operating systems. Older 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: From 51173a73aa5caa26b98b04d64377ea80941de872 Mon Sep 17 00:00:00 2001 From: dsglaser Date: Thu, 27 Apr 2023 19:49:21 -0400 Subject: [PATCH 35/68] Update README.md --- roles/cis_security/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/cis_security/README.md b/roles/cis_security/README.md index bb5315f..d13be33 100644 --- a/roles/cis_security/README.md +++ b/roles/cis_security/README.md @@ -1,6 +1,6 @@ # 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.04 LTS and certain Windows servers. ### Introduction @@ -162,4 +162,4 @@ ways files can be manipuldated with modules. - 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) -- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0 \ No newline at end of file +- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0 From afde2f1f104264efe3ac5705b77e44400e98b661 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:25:54 -0400 Subject: [PATCH 36/68] Updated with ubuntu 22, added a RHEL9 control. --- docs/controls_list.md | 664 +++++++++++++++++++++--------------------- 1 file changed, 336 insertions(+), 328 deletions(-) diff --git a/docs/controls_list.md b/docs/controls_list.md index a5d7275..7d53d98 100644 --- a/docs/controls_list.md +++ b/docs/controls_list.md @@ -1,330 +1,338 @@ -Below are the tags used in the CIS roles on Linux Machines. +# CIS Security -| RHEL 9 | 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.1 | 1.1.1.3 | 1.1.1.6 | 1.1.1.6 | Remove squashfs -| 1.1.1.2 | 1.1.1.4 | 1.1.1.7 | 1.1.1.7 | Remove udf -| 1.1.2 | 1.1.2 | | 1.1.2 | Ensure tmpfs is configured -| 1.1.2.1 | | 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.2 | 1.1.3 | 1.1.4 | 1.1.3 | Ensure nodev option on /tmp partition -| 1.1.2.3 | 1.1.4 | 1.1.5 | 1.1.4 | Ensure nosuid option set on /tmp partition -| 1.1.2.4 | 1.1.5 | 1.1.3 | 1.1.5 | Ensure noexec option set on /tmp partition -| 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.6 | Report if /var is not on a separate partition -| 1.1.3.2 | 1.1.3.2 | | | Report if /var is missing nodev option -| 1.1.3.3 | 1.1.3.3 | | | Report if /var is missing noexec option -| 1.1.4 | 1.1.4 | | | Configure /var/tmp -| 1.1.4.1 | 1.1.4.1 | 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.4.2 | 1.1.4.2 | 1.1.13 | 1.1.8 | Ensure nodev option on /var/tmp partition -| 1.1.4.3 | 1.1.4.3 | 1.1.14 | 1.1.9 | Ensure nosuid option set on /var/tmp partition -| 1.1.4.4 | 1.1.4.4 | 1.1.12 | 1.1.10 | Ensure noexec option set on /var/tmp partition -| 1.1.5 | 1.1.5 | | | Configure /var/log -| 1.1.5.1 | 1.1.5.1 | 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.5.2 | 1.1.5.2 | | | Ensure nodev option on /var/log partition -| 1.1.5.3 | 1.1.5.3 | | | Ensure nosuid option set on /var/log partition -| 1.1.5.4 | 1.1.5.4 | | | Ensure noexec option set on /var/log partition -| 1.1.6 | 1.1.6 | | | Configure /var/log/audit -| 1.1.6.1 | 1.1.6.1 | 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.6.2 | 1.1.6.2 | | | Ensure nodev option on /var/log/audit partition -| 1.1.6.3 | 1.1.6.3 | | | Ensure nosuid option set on /var/log/audit partition -| 1.1.6.4 | 1.1.6.4 | | | Ensure noexec option set on /var/log/audit partition -| 1.1.7 | 1.1.7 | | | Configure home -| 1.1.7.1 | 1.1.7.1 | 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.7.2 | 1.1.7.2 | | | Ensure nodev option on /home partition -| 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.15 | Report if /dev/shm does not have nodev set -| 1.1.8.2 | 1.1.8.3 | 1.1.9 | 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.22 | Disable automounting -|| 1.1.23 | 1.1.24 | 1.1.23 | Disable USB storage module -| 1.2.0 | 1.2.0 | | | Update System to latest -|| 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.1 | 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.2 | 1.2.4 | 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 | | 1.3.1 | Ensure sudo is installed -| 5.3.2 | 5.3.2 | | 1.3.2 | Ensure sudo commands use pty -| 5.3.3 | 5.3.3 | | 1.3.3 | Ensure sudo log file exists -| 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.4.1 | Ensure aide is installed -| 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.5.2 | Ensure bootloader password is set |skipped: environment dependent| -| 1.4.2 | 1.4.2 | 1.4.2 | 1.5.1 | Set permissions on grub bootloader files -| | | 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.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.6.2 | Ensure address space layout reandomization (ASLR) is enabled -| | | 1.5.4 | | Ensure prelink is not installed +## 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.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.7.1 | 1.7.1 | 1.7.1.1 | 1.8.1.1 | Install motd banners -| 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.8.1.3 | Install issue.net banners -| 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.8.1.5 | Ensure permissions on /etc/issue are configured -| 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 | | Ensure GNOME Display Manager is removed -| 1.8.2 | 1.8.2 | 1.8.2 | 1.8.2 | Ensure GDM banner set up -| 1.8.3 | | 1.8.3 | | Ensure last logged in user display is disabled -| 1.8.4 | | | | Ensure GDM screen locks when user is idle -| 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 \(SLES 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.2.1.1 | Verify Time synchronization is in use -| 2.1.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 | Disable display manager -| 2.2.18 | 2.2.20 | 2.2.21 \(SLES 2.2.18\) | 2.2.16 | Remove rsync -| 2.2.2 | 2.2.3 | 2.2.3 | 2.2.3 | Remove avahi -| 2.2.12 | 2.2.14 | 2.2.14 | 2.2.14 | Remove snmp -| 2.2.11 | 2.2.13 | 2.2.13 | 2.2.13 | Remove Web proxy -| 2.2.10 | 2.2.12 | 2.2.12 | 2.2.12 | Remove Samba server -| 2.2.9 | 2.2.11 | 2.2.11 | 2.2.11 | Remove Mail Relay Server -| 2.2.8 | 2.2.10 | 2.2.10 | 2.2.10 | Remove http Server -| 2.2.6 | 2.2.\[7,8\] | 2.2.9 | 2.2.9 | Remove FTP Server -| 2.2.5 | 2.2.6 | 2.2.8 | 2.2.8 | Remove DNS server -| 2.2.\[12,16\] | 2.2.18 | 2.2.7 | 2.2.7 | Remove nfs server -| 2.2.16 | 2.2.19 | 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.4 | 2.2.5 | 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.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.4 | Disable cups as we my not be able to uninstall it -| | 2.2.15 | 2.2.16 | 2.2.17 | Remove NIS Server -| | 2.2.16 | | | Remove telnet-server -| 2.2.15 | 2.2.17 | 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.3 | | | | Remove NFS Client -| 2.3.1 | 2.3.2 | 2.2.19 \(SLES 2.2.8\) | 2.3.4 | Remove telnet client -| 2.3.2 | 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.2.1 | 3.1.1 | 3.1.1 | 3.1.2 | Ensure IP forwarding is disabled | included in 3.1.0 -| 3.2.2 | 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.3.1 | 3.2.1 | 3.2.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.2.2 | Ensure ICMP redirects are not accepted | included in 3.2.0 -| 3.3.3 | 3.2.3 | 3.2.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.2.4 | Ensure suspicious packets are logged | included in 3.2.0 -| 3.3.5 | 3.2.5 | 3.2.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.2.6 | Ensure bogus ICMP responses are ignored | included in 3.2.0 -| 3.3.7 | 3.2.7 | 3.2.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.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.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 | Ensure SCTP is disabled -| 3.1.3 | 3.1.3 | 3.5.1 | 3.4.1 | Ensure DCCP is disabled -| 3.1.4 | 3.1.4 | 3.5.4 | 3.4.4 | Ensure TIPC is disabled -| | | 3.5.3 | 3.4.3 | Ensure RDS is disabled -| | | 3.6.1 | 3.5.1.1 | Ensure a firewall package is installed (firewalld or iptables) -| | | 3.6.2-5 | | Ensure firewall is configured -| | | | | Ensure firewalld is enabled and running -| | | | | Disable iptables service -| | | | | Disable netfilters service -| | | | | Ensure default zone is set for firewalld -| | | | | Set default zone in firewalld -| | | | | Ensure network interfaces are assigned to appropriate zone |skipped: machine dependent| -| | 3.4.1 | | | Configure firewalld (block tag) -| | 3.4.1.1 | | | Ensure firewalld is installed -| | 3.4.1.2 | | | Ensure iptables-services is not installed -| | 3.4.1.3 | | | Ensure nftables either not installed or masked with firewalld -| | 3.4.1.4 | | | Ensure firewalld is enabled and running -| 3.4.2.1 | 3.4.1.5 | | | Ensure firewalld default zone is set -| 3.4.1 | 3.4.2 | | | Configure nftables (block tag) -| 3.4.1.1 | 3.4.2.1 | | | Ensure nftables is installed -| 3.4.1.2 | | | | Ensure a single firewall package package is installed -| | 3.4.2.2 | | | Ensure firewalld is either uninstalled or masked with nftables -| | 3.4.2.3 | | | Ensure iptables-services not installed with nftables -| 3.4.2.2 | 3.4.2.5 | | | Ensure an nftables table is installed -| 3.4.1.1 | 3.4.2.10 | | | Ensure nftables is started -| | 3.4.3 | | | Configure iptables (block tag) -| | 3.4.3.1.1| | | Ensure iptables is installed -| | 3.4.3.1.2| | | Ensure nftables is not installed with iptables -| | 3.4.3.1.3| | | Ensure firewald is not installed with iptables -| | | | 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.5.3 | Configure nftables (skipped: seldom used) -| | | 3.6.1 | | Ensure iptables are flushed -| | | 3.6.2-5 | 3.5.4 | Configure iptables (skipped: machine dependent) -| | | 3.7 | 3.6 | Ensure wireless interfaces are disabled |skipped: machine dependent| -| | 3.6.0 | 3.3.3 | 3.7 | Disable IPv6 -| 4.1.0 | 4.1.0 | | | Configure Auditing (block tag) -| 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 | Enable auditd service -| 4.1.1.2 | 4.1.1.3 | 4.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 | Ensure audit_backlog_limit is sufficient -| 4.1.2.1 | 4.1.2.1 | 4.1.1.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 | Ensure audit logs are not automatically deleted -| 4.1.2.3 | 4.1.2.3 | 4.1.1.2 | 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.14 | Ensure changes to system administration scope \(sudoers\) is collected -| 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.15 | Ensure sysadmin actions (sudolog) are collected -| 4.1.3.4 | 4.1.3.4 | 4.1.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.5 | Ensure modifications to network environment are collected -| 4.1.3.6 | 4.1.3.6 | 4.1.12 | 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.10 | Ensure unsuccessful unauthorized file access attempts are collected -| 4.1.3.8 | 4.1.3.8 | 4.1.5 | 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.9 | Ensure modifications to discretionary access controls are collected -| 4.1.3.10 | 4.1.3.10 | 4.1.13 | 4.1.12 | Ensure successful file system mounts are collected -| 4.1.3.11 | 4.1.3.11 | 4.1.9 | 4.1.8 | Ensure session initiation information is collected -| 4.1.3.12 | 4.1.3.12 | 4.1.8 | 4.1.7 | Ensure system logins and logouts are collected -| 4.1.3.13 | 4.1.3.13 | 4.1.14 | 4.1.13 | Ensure file deletion events by users are collected -| 4.1.3.14 | 4.1.3.14 | 4.1.7 | 4.1.6 | Ensure modifications to Mandatory Access Controls are collected -| 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 | | | Ensure all attempts to use the setfacl command are collected -| 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 | | | Ensure all attempts to use the usermod command are collected -| 4.1.3.19 | 4.1.3.19 | 4.1.17 | 4.1.16 | Ensure kernel module loading and unloading is collected -| 4.1.3.20 | 4.1.3.20 | 4.1.18 | 4.1.17 | Ensure audit configuration is immutable -| 4.1.4.1 | | | | Ensure audit log files are mode 0640 more more restrictive -| 4.1.4.2 | | | | Ensure only authorized users own audit log files -| 4.1.4.3 | | | | Ensure only authorized groups own audit log flies -| 4.1.4.5 | | | | Ensure audit config files are mode 640 or more restrictive -| 4.1.4.6 | | | | Ensure audit config files are owned by user root -| 4.1.4.7 | | | | Ensure audit tools are mode 0755 or more restrictive -| 4.1.4.9 | | | | Ensure audit tools are owned by user root -| 4.1.4.10 | | | | Ensure audit tools belong to group root -| 4.2.1.1 | 4.2.1.1 | 4.2.3 | 4.2.1.1 | Ensure rsyslog is installed -| 4.2.1.2 | 4.2.1.2 | | 4.2.1.2 | Enable rsyslog -| 4.2.1.3 | 4.2.1.3 | | | Ensure journald is configured to send messages to rsyslog -| 4.2.1.4 | 4.2.1.4 | 4.2.1.3 | 4.2.1.4 | Ensure rsyslog default file permissions are configured -| 4.2.1.5 | 4.2.1.5 | 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.6 | 4.2.1.6 | 4.2.1.4 | 4.2.1.5 | 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.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 | | | Configure journald to send logs to a remote log host -| 4.2.2.1.1| 4.2.2.1.1| | | Ensure systemd-journal-remote is installed -| 4.2.2.1.2| 4.2.2.1.2| | | Ensure systemd-journal-remote is configured -| 4.2.2.1.3| 4.2.2.1.3| | | Ensure systmd-journal-remote is enabled -| 4.2.2.1.4| 4.2.2.1.4| | | Ensure journald is not configured to recieve logs from a remote client -| 4.2.2.2 | 4.2.2.2 | | | Ensure journald service is enabled -| 4.2.2.3 | 4.2.2.3 | | 4.2.2.2 | Ensure journald compresses large files -| 4.2.2.4 | 4.2.2.4 | | 4.2.2.3 | Ensure journald writes to peristent disk -| 4.2.2.5 | 4.2.2.5 | | 4.2.2.1 | Forward journald logs to rsyslog IF rsyslog is sending logs to a log host -| 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 | | | Configure Cron/At -| 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 | Ensure permissions on /etc/crontab -| 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 | Ensure permissions on /etc/cron.daily -| 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 | Ensure permissions on /etc/cron.monthly -| 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 | 5.1.8 | Ensure cron restricted to authorized users |skipped: except in RHEL 8/9| -| 5.1.9 | 5.1.9 | 5.1.8 | 5.1.8 | Ensure at restricted to authorized users |skipped: except in RHEL 8/9| -| 5.2.1.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 | Set permissions on SSH file -| 5.2.1.2 | 5.2.2 | | 5.2.2 | Set Permissions on ssh private host keys -| 5.2.1.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.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.1.5 | 5.2.5 | 5.2.3 | 5.2.5 | Set LogLevel to INFO -| 5.2.1.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.10 | Ensure PermitRootLogin is disbled -| 5.2.1.8 | 5.2.8 | 5.2.7 | 5.2.9 | Ensure HostbasedAuthentication is disabled -| 5.2.1.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.12 | Ensure PermitUserEnvironment is disabled -| 5.2.1.11 | 5.2.11 | | | Ensure IgnoreRhosts is enabled -| 5.2.1.12 | 5.2.12 | 5.2.4 | 5.2.6 | Disable X11 forwarding -| 5.2.1.13 | 5.2.13 | | 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.19 | Ensure SSH Banner is configured -| 5.2.1.16 | 5.2.16 | 5.2.5 | 5.2.7 | Ensure SSH MaxAuthTires is set -| 5.2.1.17 | 5.2.17 | | 5.2.22 | Ensure MaxStartups is configured -| 5.2.1.18 | 5.2.18 | | 5.2.23 | Ensure MaxSessions is set to 10 or less -| 5.2.1.19 | 5.2.19 | 5.2.13 | 5.2.17 | Ensure SSH LoginGraceTime is set -| 5.2.1.20 | 5.2.20 | 5.2.12 | 5.2.16 | Ensure SSH Idle Timeout is configured -| | | | 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.3.0 | 5.3.0 | | | Configure privilege escalation -| 5.3.1 | 5.3.1 | | | Ensure sudo is installed -| 5.3.2 | 5.3.2 | | | Ensure sudo commands use pty -| 5.3.3 | 5.3.3 | | | Ensure sudo log file exists -| 5.3.4 | 5.3.4 | | | Ensure users must provide password for escalation -| 5.3.5 | 5.3.5 | | | Ensure re-authentication for privlege ecalation is not disabled globally -| 5.3.7 | 5.3.7 | 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 | -| 5.5.1 | 5.5.1 | 5.3.1 | 5.3.1 | Configure PAM files and password requirements -| 5.5.2 | 5.5.2 | | | Ensure lockout for failed password attempts -| 5.5.4 | 5.5.4 | | | Configure password hashing algorithm is SHA-512 or YESCRIPT -| 5.6.1.1 | 5.6.1.1 | 5.4.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.4.1.3 | Ensure password warning days is set to 7 -| 5.6.1.3 | 5.6.1.3 | 5.4.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.4.1.4 | Disable accounts that are inactive for 30 days after password expiration -| 5.6.3 | 5.6.3 | 5.4.5 | 5.4.5 | Ensure default shell timeout is 900 seconds or less -| 5.6.4 | 5.6.4 | 5.4.3 | 5.4.3 | Ensure default group for root is GID 0 -| 5.6.5 | 5.6.5 | 5.4.4 | 5.4.4 | Ensure default umask is set -| 6.1.1,6.1.3| 6.1.3,6.1.5| 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| 6.1.3,6.1.5 | Ensure permissions on /etc/shadow /etc/gshadow -| 6.1.2,4,6,8 | 6.1.7,8,9,10| 6.1.6,7,8,9| 6.1.6,7,8,9 | Ensure permissions on /etc/passwd- /etc/[g]shadow- /etc/group- -| 6.1.1 | 6.1.3 | 6.1.2 | 6.1.2 | Ensure permissions on /etc/passwd -| 6.1.2 | 6.1.7 | 6.1.6 | 6.1.6 | Ensure permissions on /etc/passwd- -| 6.1.3 | 6.1.5 | 6.1.4 | 6.1.4 | Ensure permissions on /etc/group -| 6.1.4 | 6.1.9 | 6.1.8 | 6.1.8 | Ensure permissions on /etc/group- -| 6.1.5 | 6.1.4 | 6.1.3 | 6.1.3 | Ensure permissions on /etc/shadow -| 6.1.6 | 6.1.8 | 6.1.7 | 6.1.7 | Ensure permissions on /etc/shadow- -| 6.1.7 | 6.1.6 | 6.1.5 | 6.1.5 | Ensure permissions on /etc/gshadow -| 6.1.8 | 6.1.10 | 6.1.9 | 6.1.9 | Ensure permissions on /etc/gshadow- -| 6.1.9 | 6.1.11 | 6.1.10 | 6.1.10 | Report if any world writable files exist -| 6.1.10 | 6.1.12 | 6.1.11 | 6.1.11 | Report if any unowned files or directories exist -| 6.1.11 | 6.1.13 | 6.1.12 | 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 | | | Ensure accounts in /etc/passwd use shadowed passwords -| 6.2.2 | 6.2.1 | 6.2.1 | 6.2.1 | Report on password fields that are empty -| 6.2.4 | 6.2.3 | 6.2.16 | 6.2.16 | Report on duplicate UIDs in /etc/passwd -| 6.2.5 | 6.2.4 | 6.2.17 | 6.2.17 | Report on duplicate GIDs in /etc/group -| 6.2.6 | 6.2.5 | 6.2.18 | 6.2.18 | Report on duplicate users in /etc/passwd -| 6.2.7 | 6.2.6 | 6.2.19 | 6.2.19 | Report on duplicate groups in /etc/group -| 6.2.8 | 6.2.7 | 6.2.6 | 6.2.7 | Ensure root PATH integrity -| 6.2.9 | 6.2.8 | 6.2.5 | 6.2.6 | Report on multiple accounts with UID of 0 -| 6.2.10 | 6.2.10 | 6.2.9 | 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.15 | 6.2.15 | Report on groups in /etc/passwd with a GID not in /etc/group -| | | | 6.2.20 | Report if shadow group exists in /etc/group +| 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 From a74ac4fcad48c81ef64e3f97a44b65d78892cb42 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:26:25 -0400 Subject: [PATCH 37/68] do a daemon-reload on restarting aide --- roles/cis_security/handlers/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/cis_security/handlers/main.yml b/roles/cis_security/handlers/main.yml index a0b44cb..7c2b4a2 100644 --- a/roles/cis_security/handlers/main.yml +++ b/roles/cis_security/handlers/main.yml @@ -9,7 +9,6 @@ ansible.builtin.service: name: auditd state: restarted - use: service when: ansible_os_family != "windows" listen: "Restart auditd" @@ -89,6 +88,7 @@ ansible.builtin.systemd: name: aidecheck enabled: true + daemon_reload: true state: restarted - name: Flush network routes From ff837670eaf1f06bde0322d121ad85242ca2a618 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:28:20 -0400 Subject: [PATCH 38/68] added 6.2.3 control, formatting and cleanup --- .../tasks/type-files/redhat-9-type.yml | 184 ++++++++++-------- 1 file changed, 104 insertions(+), 80 deletions(-) diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 36bb100..fa432bc 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -248,7 +248,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.5 - Configure /var +- name: 1.1.5 - Configure /var/log tags: - 1.1.5 block: @@ -343,7 +343,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 + - name: 1.1.7 - Set/reset /home mount counter ansible.builtin.set_fact: mount_count: 0 @@ -407,7 +407,6 @@ # 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 @@ -416,7 +415,7 @@ - 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 +- name: 1.1.8.4 - Report if /dev/shm does not have nosuid set tags: - 1.1.8.4 block: @@ -1113,7 +1112,7 @@ # 2 Services -- name: 2.1.1 - Verify chrony is installed +- name: 2.1.1 - Verify chrony is installed ansible.builtin.dnf: name: "chrony" state: present @@ -1284,7 +1283,9 @@ 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 @@ -1409,7 +1410,7 @@ tags: - 3.3.4 -- name: 3.3.5 - Ensure broadcast iCMP requests are ignored +- 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: @@ -1708,6 +1709,9 @@ # 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 }}" @@ -1777,7 +1781,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.5 + - 4.1.3.5 # Control 4.1.3.6 requires a system scan, skipping @@ -1813,7 +1817,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.8 + - 4.1.3.8 - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected ansible.builtin.template: @@ -1824,7 +1828,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.9 + - 4.1.3.9 - name: 4.1.3.11 - Ensure session initiation information is collected ansible.builtin.template: @@ -1835,7 +1839,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.11 + - 4.1.3.11 - name: 4.1.3.12 - Ensure system logins are collected ansible.builtin.template: @@ -1846,7 +1850,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.12 + - 4.1.3.12 - name: 4.1.3.13 - Ensure file deletion events by users are collected ansible.builtin.template: @@ -1857,7 +1861,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.13 + - 4.1.3.13 - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected ansible.builtin.template: @@ -1868,7 +1872,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.14 + - 4.1.3.14 - name: 4.1.3.15 - Ensure successful and unsuccessful attempts to use the chcon command are recorded ansible.builtin.template: @@ -1879,7 +1883,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.15 + - 4.1.3.15 - name: 4.1.3.16 - Ensure successful and unsuccessful attempts to use the setfacl command are recorded ansible.builtin.template: @@ -1890,7 +1894,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.16 + - 4.1.3.16 - name: 4.1.3.17 - Ensure successful and unsuccessful attempts to use the chacl command are recorded ansible.builtin.template: @@ -1901,7 +1905,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.17 + - 4.1.3.17 - name: 4.1.3.18 - Ensure successful and unsuccessful attempts to use the usermod command are recorded ansible.builtin.template: @@ -1912,7 +1916,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.18 + - 4.1.3.18 - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected ansible.builtin.template: @@ -1923,7 +1927,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.19 + - 4.1.3.19 - name: 4.1.3.20 - Ensure audit configuration is immutable ansible.builtin.copy: @@ -1935,11 +1939,11 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.20 + - 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 autid files to mode 600, user root, group root +- 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 @@ -2107,6 +2111,7 @@ # 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: @@ -2114,28 +2119,25 @@ ansible.builtin.dnf: name: systemd-journal-remote state: present - when: log_service and log_service == "journald" 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-jornal-remote.socket is masked + - 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 - when: log_service and log_service == "journald" tags: - 4.2.2.1.4 - - name: 4.2.2.1.4 Ensure jorunald service is masked + - 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 - when: log_service and log_service == "journald" tags: - 4.2.2.1.4 @@ -2146,7 +2148,6 @@ line: "Compress=yes" insertafter: "^#Compress=" notify: Restart journald - when: log_service and log_service == "journald" tags: - 4.2.2.3 @@ -2157,7 +2158,6 @@ line: "Storage=persistent" insertafter: "^#Storage=" notify: Restart journald - when: log_service and log_service == "journald" tags: - 4.2.2.4 @@ -2165,9 +2165,7 @@ ansible.builtin.lineinfile: dest: /etc/systemd/journald.conf regexp: "^ForwardToSyslog=((?!yes).)*$" - line: "ForwardToSyslog=yes" state: absent - when: log_service and log_service == "journald" tags: - 4.2.2.5 @@ -2286,7 +2284,7 @@ state: file when: at_allow and at_allow | length > 0 tags: - - 5.1.8 + - 5.1.9 - name: 5.1.9 - Ensure at is restricted to authorized users ansible.builtin.lineinfile: @@ -2332,8 +2330,8 @@ ansible.builtin.file: dest: "{{ item.path }}" owner: root - group: ssh_keys - mode: 0640 + group: root + mode: 0600 loop: "{{ ssh_host_out.files }}" - name: 5.2.3 - Set Permissions on ssh public host keys @@ -2356,53 +2354,49 @@ mode: 0644 loop: "{{ ssh_hostpub_out.files }}" - - name: 5.2.4 - Ensure SSH access is limited (AllowUsers) - 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 - tags: - - 5.2.4 - - - 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 - tags: - - 5.2.4 - - - 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 - tags: - - 5.2.4 - - - 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.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: @@ -2655,9 +2649,9 @@ # Control 5.4.3, Set password retention, requries file replacement # skipping -- name: 5.5.1 - Configure PAM files and password requirements +- name: 5.5.0 - Configure PAM files and password requirements tags: - - 5.5.1 + - 5.5.0 block: - name: 5.5.1 - require at least one digit in passwords ansible.builtin.lineinfile: @@ -2978,7 +2972,37 @@ tags: - 6.2.2 -# Contorl 6.2.3, requires manual intervention, skipping +- 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: From 3ae1fd72ca165c4bf2952bae93e7c2f481ded19b Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:28:56 -0400 Subject: [PATCH 39/68] Updated to handle ubuntu 22.04+ --- roles/cis_security/templates/aidecheck.service | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 %} From f778a83484937b478e0699ba5626f84d14097ad5 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:29:41 -0400 Subject: [PATCH 40/68] updated with Ubuntu 22.04+ variables --- roles/cis_security/defaults/main.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index f6d882f..97e6224 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -11,12 +11,13 @@ 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/9 only ships with chrony and this variable is not used +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 @@ -52,6 +53,9 @@ 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' +# 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 @@ -66,7 +70,7 @@ logrotate_file: # Linux: RHEL 8/9, Copy file listed for logrotat # 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 @@ -88,7 +92,7 @@ ssh_mac_list: "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac- 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" + - '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 @@ -98,7 +102,7 @@ ssh_login_banner: issue.net # Linux: a file with no path will exist in the r 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: "adm" # Linux: RHEL9, space separated list of users to add to DneyGroups 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 @@ -114,6 +118,7 @@ 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: From d375a30497e36b1dc92bcb8c9a4146bcbe5a342f Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:30:22 -0400 Subject: [PATCH 41/68] added a standard rsyslog.conf config file --- roles/cis_security/files/rsyslog.conf | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 roles/cis_security/files/rsyslog.conf 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 From 86b1743cde9bca672519f6ed33a88dbd34f07acc Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:32:14 -0400 Subject: [PATCH 42/68] Initial controls for Ubuntu 22.04! --- roles/cis_security/tasks/CIS-Ubuntu-22.yml | 4 + .../tasks/type-files/ubuntu-22-type.yml | 3323 +++++++++++++++++ 2 files changed, 3327 insertions(+) create mode 100644 roles/cis_security/tasks/CIS-Ubuntu-22.yml create mode 100644 roles/cis_security/tasks/type-files/ubuntu-22-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/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml new file mode 100644 index 0000000..53b08dd --- /dev/null +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -0,0 +1,3323 @@ +--- +# 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: "{{ item }}" + state: absent + loop: + - unused_filesystems + 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/true" + 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/true" + 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 + notify: Restart aidecheck + + - 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 + +# 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 From ab7f2c97891b1004f2031211a249d3595be42480 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:32:53 -0400 Subject: [PATCH 43/68] Initial 22.04 controls --- roles/cis_security/tasks/type-files/ubuntu-22-type.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml index 53b08dd..bd66a20 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -606,7 +606,6 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: Restart aidecheck - name: 1.3.2 - Enable aidecheck.service ansible.builtin.systemd: @@ -624,7 +623,6 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: Restart aidecheck - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: From 24d6a72c7670c1bb5b1c9e4405b32449511e8814 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 09:33:18 -0400 Subject: [PATCH 44/68] minor formatting changes --- roles/cis_security/tasks/type-files/redhat-9-type.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index fa432bc..29f0319 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -642,7 +642,6 @@ group: root mode: 0644 setype: systemd_unit_file_t - notify: Restart aidecheck - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: From 3a80c579d8c7afd89ab680c50e5f9cae8d2c8cc9 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 10:05:46 -0400 Subject: [PATCH 45/68] comment fix for Ubuntu 22.04 in Readme --- README.md | 41 ++++++++++++++++++++++++------------ galaxy.yml | 2 +- roles/cis_security/README.md | 6 ++++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 34c6ff0..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-9) 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. @@ -31,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 | @@ -38,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.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. @@ -56,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] ``` @@ -70,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}} @@ -93,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. @@ -111,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 }}" @@ -147,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/galaxy.yml b/galaxy.yml index 6e838d6..7a254df 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -9,7 +9,7 @@ namespace: dsglaser name: cis_security # The version of the collection. Must be compatible with semantic versioning -version: 1.5.0 +version: 1.5.1 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/roles/cis_security/README.md b/roles/cis_security/README.md index c4dcfb1..8bfc9b6 100644 --- a/roles/cis_security/README.md +++ b/roles/cis_security/README.md @@ -1,6 +1,6 @@ # cis_security -A collection to implement Center for Internet Security (CIS) controls for RHEL (7-9) 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 @@ -29,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 @@ -174,4 +175,5 @@ ways files can be manipuldated with modules. - 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) -- 4/14/2023 - dsglaser@gmail.com - Added support for RHEL 9, updated RHEL 8 to CIS v2.0.0 +- 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 From a46832aa102064946f58823141345a0cd9004028 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 10:09:57 -0400 Subject: [PATCH 46/68] fixed metadata for Ubuntu 22.04 --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 7a254df..9ce479d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -25,7 +25,7 @@ authors: # A short summary description of the collection description: | A collection to implement Center for Internet Security (CIS) controls for RHEL (7-9) and RHEL clones - (Oracle, CentOS), SLES 15, Ubuntu 18.04 LTS, Ubuntu 20.04 LTS, Microsoft Windows Server 2019, and Microsoft Windows 10. + (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' From 83bd47f62bea23e7fc95ae67b6e221b7999a0a61 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 2 May 2023 10:10:46 -0400 Subject: [PATCH 47/68] upped galaxy version number --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 9ce479d..2d87208 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -9,7 +9,7 @@ namespace: dsglaser name: cis_security # The version of the collection. Must be compatible with semantic versioning -version: 1.5.1 +version: 1.5.2 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md From 749d3c1d29c23cc4c6582922ab54cd4883cab927 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 10 May 2023 08:50:41 -0400 Subject: [PATCH 48/68] fixed unused filesystems issue. --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 2 +- roles/cis_security/tasks/type-files/redhat-9-type.yml | 7 ------- roles/cis_security/tasks/type-files/ubuntu-22-type.yml | 4 +--- 3 files changed, 2 insertions(+), 11 deletions(-) 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 ce3a741..07ba5e6 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -75,7 +75,7 @@ # to remove packages from the system that are not needed. - name: Remove unused_filesystem list ansible.builtin.dnf: - name: unused_filesystems + name: "{{ unused_filesystems }}" state: absent - name: Add unused_filesystems to /etc/modprobe.d/CIS.conf diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 29f0319..8d0f7be 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -46,13 +46,6 @@ 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 - disable squashfs ansible.builtin.blockinfile: path: /etc/modprobe.d/squashfs.conf diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml index bd66a20..d28f64b 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -74,10 +74,8 @@ # to remove packages from the system that are not needed. - name: 1.1 - Process unused_filesystem list ansible.builtin.package: - name: "{{ item }}" + name: "{{ unused_filesystems }}" state: absent - loop: - - unused_filesystems tags: - 1.1.0 From e5e2de207c158f7020a0634cfd80b19fb0c665c1 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 10 May 2023 08:51:46 -0400 Subject: [PATCH 49/68] updated z stream number --- galaxy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galaxy.yml b/galaxy.yml index 2d87208..50c0232 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.5.2 +version: 1.5.3 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md From ace826fdf7c4364208b6af446c76e0d3e7659e04 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 11 May 2023 22:52:27 -0400 Subject: [PATCH 50/68] updated /bin/true tests to /bin/false per controls --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 2 +- roles/cis_security/tasks/type-files/redhat-9-type.yml | 2 +- roles/cis_security/tasks/type-files/ubuntu-22-type.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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 07ba5e6..cda9010 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -81,7 +81,7 @@ - name: Add unused_filesystems to /etc/modprobe.d/CIS.conf ansible.builtin.lineinfile: dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" + line: "install {{ item }} /bin/false" state: present create: true owner: root diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 8d0f7be..30a0cf1 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -1496,7 +1496,7 @@ - name: 3.1.3 - Process uncommon network list ansible.builtin.lineinfile: dest: /etc/modprobe.d/CIS.conf - line: "install {{ item }} /bin/true" + line: "install {{ item }} /bin/false" state: present create: true owner: root diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml index d28f64b..fc54736 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -82,7 +82,7 @@ - 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/true" + line: "install {{ item }} /bin/false" state: present create: true owner: root @@ -496,7 +496,7 @@ - name: 1.1.10 - Disable USB storage module ansible.builtin.lineinfile: dest: /etc/modprobe.d/CIS.conf - line: "install usb-storage /bin/true" + line: "install usb-storage /bin/false" state: present create: true owner: root From 334943ece89a88658ebb4119c69551934118b186 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Thu, 11 May 2023 23:31:56 -0400 Subject: [PATCH 51/68] change dconf dir from local to distro --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 2 +- roles/cis_security/tasks/type-files/redhat-9-type.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 cda9010..7034249 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -1049,7 +1049,7 @@ # 1.8.5 - Diable media automount - name: 1.8.5 - disable removable media automount in GDM ansible.builtin.copy: - dest: "/etc/dconf/db/local.d/00-media-automount" + dest: "/etc/dconf/db/distro.d/00-media-automount" owner: root group: root mode: 0644 diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 30a0cf1..5e3311e 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -1044,7 +1044,7 @@ - name: 1.8.4 Set gdm timeouts ansible.builtin.blockinfile: - path: /etc/dconf/db/local.d/00-screensaver + path: /etc/dconf/db/distro.d/00-screensaver owner: root group: root mode: 0644 From 4cbf19af664f9fa77014c7af61fb67611626a4d8 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Fri, 12 May 2023 07:24:47 -0400 Subject: [PATCH 52/68] handling machines with disabled Linux --- roles/cis_security/defaults/main.yml | 5 +++-- .../tasks/type-files/redhat-8-type.yml | 14 +++++++------- .../tasks/type-files/redhat-9-type.yml | 8 ++++---- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/roles/cis_security/defaults/main.yml b/roles/cis_security/defaults/main.yml index 97e6224..f3de6d6 100644 --- a/roles/cis_security/defaults/main.yml +++ b/roles/cis_security/defaults/main.yml @@ -11,8 +11,9 @@ 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; 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_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" 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 7034249..c822b52 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -73,12 +73,12 @@ # 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 +- name: 1.1 - Remove unused_filesystem list ansible.builtin.dnf: name: "{{ unused_filesystems }}" state: absent -- name: Add unused_filesystems to /etc/modprobe.d/CIS.conf +- 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" @@ -778,7 +778,7 @@ - python3-libselinux state: present register: selinux_installed - when: selinux is defined and selinux != "Disabled" + when: selinux is defined and selinux | lower != "disabled" tags: - 1.6.1.1 @@ -801,7 +801,7 @@ with_items: - selinux=0 - enforcing=0 - when: selinux is defined and selinux != "Disabled" + when: selinux is defined and selinux | lower != "disabled" notify: Rebuild grub tags: - 1.6.1.2 @@ -812,7 +812,7 @@ dest: /etc/selinux/config regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" replace: "SELINUXTYPE={{ selinux_policy }}" - when: ( selinux is defined and selinux_policy is defined ) and selinux != "Disabled" + when: ( selinux is defined and selinux_policy is defined ) and selinux | lower != "disabled" tags: - 1.6.1.3 @@ -825,7 +825,7 @@ group: root mode: 0644 state: touch - when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed + when: ( ansible_selinux.status == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed notify: Reboot tags: - 1.6.1.3 @@ -847,12 +847,12 @@ - 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 - when: ansible_selinux.status != "disabled" failed_when: unconfined_services_out.rc == "2" changed_when: false check_mode: false diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 5e3311e..39bd622 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -797,7 +797,7 @@ with_items: - selinux=0 - enforcing=0 - when: selinux is defined and selinux != "Disabled" + when: selinux is defined and selinux | lower != "disabled" notify: Rebuild grub tags: - 1.6.1.2 @@ -808,7 +808,7 @@ dest: /etc/selinux/config regexp: "^SELINUXTYPE=((?!{{ selinux_policy }}).)*$" replace: "SELINUXTYPE={{ selinux_policy }}" - when: ( selinux is defined and selinux_policy is defined ) and selinux != "Disabled" + when: ( selinux is defined and selinux_policy is defined ) and selinux | lower != "disabled" tags: - 1.6.1.3 @@ -821,7 +821,7 @@ group: root mode: 0644 state: touch - when: ( ansible_selinux.mode == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed + when: ( ansible_selinux.status == "disabled" and selinux | lower != "disabled" ) or selinux_installed.changed notify: Reboot tags: - 1.6.1.3 @@ -843,12 +843,12 @@ - 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 - when: ansible_selinux.status != "disabled" failed_when: unconfined_services_out.rc == "2" changed_when: false check_mode: false From 798150991964397462c2693f30cff5410e2a5242 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Fri, 12 May 2023 07:25:00 -0400 Subject: [PATCH 53/68] bump release number --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 50c0232..ff0f399 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -9,7 +9,7 @@ namespace: dsglaser name: cis_security # The version of the collection. Must be compatible with semantic versioning -version: 1.5.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 From d90b751e33dab3f38646bf5171352925d4cc38bc Mon Sep 17 00:00:00 2001 From: David Glaser Date: Fri, 12 May 2023 09:51:12 -0400 Subject: [PATCH 54/68] updates to handle mount_options correctly --- .../tasks/type-files/redhat-8-type.yml | 218 ++++++++++++------ .../tasks/type-files/redhat-9-type.yml | 211 +++++++++++------ 2 files changed, 281 insertions(+), 148 deletions(-) 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 c822b52..ae653bd 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -147,6 +147,7 @@ - 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 }}" @@ -160,24 +161,30 @@ 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 + - name: 1.1.3.[2-3] - /var mount option controls 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 + 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 @@ -215,38 +222,45 @@ 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 + - name: 1.1.4.[2-4] - /var/tmp mount option controls 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 + 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 @@ -267,6 +281,7 @@ - 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 }}" @@ -283,24 +298,30 @@ 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 + - name: 1.1.5.[2-3] - /var/log mount option controls 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 + 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 @@ -321,6 +342,7 @@ - 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 }}" @@ -337,6 +359,46 @@ 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 @@ -373,24 +435,30 @@ 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 + - name: 1.1.7.[2-3] - /home mount option controls 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 + 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 @@ -1603,7 +1671,7 @@ - 3.4.1 block: - name: 3.4.1 - Configure firewalld - debug: + ansible.builtin.debug: msg: "3.4.1 - Configure firewalld" - name: 3.4.1.1 - Install firewalld @@ -1669,7 +1737,7 @@ - 3.4.2 block: - name: 3.4.2 - Configure nftables - debug: + ansible.builtin.debug: msg: "3.4.2 - Configure nftables" - name: 3.4.2.1 - ensure nftables is installed diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 39bd622..735821d 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -151,24 +151,30 @@ 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 + - name: 1.1.3.[2-3] - /var mount option controls 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 + 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 @@ -206,38 +212,45 @@ 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 + - name: 1.1.4.[2-4] - /var/tmp mount option controls 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 + 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 @@ -274,24 +287,30 @@ 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 + - name: 1.1.5.[2-3] - /var/log mount option controls 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 + 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 @@ -328,6 +347,46 @@ 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 @@ -364,24 +423,30 @@ 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 + - name: 1.1.7.[2-3] - /home mount option controls 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 + 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 From 0a8bdd5202e2aaaefbfb2f9feb5b5f74992df652 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Fri, 12 May 2023 10:39:41 -0400 Subject: [PATCH 55/68] fixes for rhel8 passalgo and 5.6.5 --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 9 +++++---- roles/cis_security/tasks/type-files/redhat-9-type.yml | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) 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 ae653bd..519bb42 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -2940,11 +2940,11 @@ # 5.5.3 - Password retention involves configuring pam.conf, skipping -- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 or yescrypt +- 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 = {{ password_hash_alg | lower }}" + replace: "crypt_style = sha512" after: "[defaults]" owner: root group: root @@ -2952,11 +2952,11 @@ tags: - 5.5.4 -- name: 5.5.4 - Ensure password hashing algorithm is SHA-512 or yescrypt +- 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 {{ password_hash_alg | upper }}" + replace: "ENCRYPT_METHOD SHA512" tags: - 5.5.4 @@ -3033,6 +3033,7 @@ loop: - /etc/bashrc - /etc/profile + - /etc/login.defs tags: - 5.6.5 diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 735821d..5abd976 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -2869,6 +2869,7 @@ loop: - /etc/bashrc - /etc/profile + - /etc/login.defs tags: - 5.6.5 From 46fa906366b1a979361427e7b7df3363e276de70 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Fri, 12 May 2023 13:11:14 -0400 Subject: [PATCH 56/68] fixed restart auditd service handler --- roles/cis_security/handlers/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/roles/cis_security/handlers/main.yml b/roles/cis_security/handlers/main.yml index 7c2b4a2..0060769 100644 --- a/roles/cis_security/handlers/main.yml +++ b/roles/cis_security/handlers/main.yml @@ -6,9 +6,7 @@ # https://access.redhat.com/solutions/2664811 - name: Restart auditd - ansible.builtin.service: - name: auditd - state: restarted + ansible.builtin.command: service auditd restart when: ansible_os_family != "windows" listen: "Restart auditd" From 7012b4906d420c0e95e8aa9566ad0895c1400153 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Mon, 15 May 2023 09:42:44 -0400 Subject: [PATCH 57/68] Fix control 3.3.2 --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 10 ++++++++-- roles/cis_security/tasks/type-files/redhat-9-type.yml | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) 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 519bb42..41490d7 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -1519,13 +1519,19 @@ - name: 3.3.2 - Ensure ICMP redirects are not accepted ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.conf.all.accept_redirects' : '0' }) }}" + 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({ 'net.ipv6.conf.all.accept_redirects' : '0' }) }}" + 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 diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index 5abd976..b3f99e3 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -1438,13 +1438,19 @@ - name: 3.3.2 - Ensure ICMP redirects are not accepted ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ 'net.ipv4.conf.all.accept_redirects' : '0' }) }}" + 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({ 'net.ipv6.conf.all.accept_redirects' : '0' }) }}" + 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 From 9505b4ee8d15abc29ceb98b2f302b833aa40c9d9 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 16 May 2023 14:28:43 -0400 Subject: [PATCH 58/68] added blacklist to unused filesystems per v2.0.0 --- .../tasks/type-files/redhat-8-type.yml | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) 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 41490d7..61eb07a 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -49,47 +49,57 @@ # 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' ] }}" +# 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.1 + - 1.1.1.0 -- name: 1.1.1.2 - Add squashfs to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'squashfs' ] }}" +- 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.2 + - 1.1.1.1 -- name: 1.1.1.3 - Add udf to list of unused filesystems - ansible.builtin.set_fact: - unused_filesystems: "{{ unused_filesystems + [ 'udf' ] }}" +- 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.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 - Remove unused_filesystem list - ansible.builtin.dnf: - name: "{{ unused_filesystems }}" - state: absent + - 1.1.1.2 -- 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 +- 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 - with_items: - - "{{ unused_filesystems }}" + 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 From 51aa977281a5e0032e45675e2add5c7fce1a7ffa Mon Sep 17 00:00:00 2001 From: David Glaser Date: Tue, 16 May 2023 15:47:14 -0400 Subject: [PATCH 59/68] minor fixes and typo fixes --- .../tasks/type-files/redhat-8-type.yml | 163 +++++++++--------- .../tasks/type-files/redhat-9-type.yml | 50 ++---- 2 files changed, 96 insertions(+), 117 deletions(-) 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 61eb07a..d41d504 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -274,7 +274,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.5 - Configure /var +- name: 1.1.5 - Configure /var/log tags: - 1.1.5 block: @@ -417,7 +417,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 + - name: 1.1.7 - Set/reset /home mount counter ansible.builtin.set_fact: mount_count: 0 @@ -486,8 +486,7 @@ 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 - + - 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 @@ -761,17 +760,19 @@ path: "/boot/grub2/grub.cfg" register: grubdir tags: - 1.4.1 + 1.4.0 -- name: 1.4.1 - set variable for grub.cfg in LILO location +- 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.1 + 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 +- name: 1.4.2 - Set permissions on grub.cfg, grubenv ansible.builtin.file: path: "{{ item }}" owner: root @@ -1174,7 +1175,7 @@ # 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 +- name: 2.1.1 - Verify chrony is installed ansible.builtin.dnf: name: "chrony" state: present @@ -1433,7 +1434,9 @@ 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 @@ -1564,7 +1567,7 @@ tags: - 3.3.4 -- name: 3.3.5 - Ensure broadcast iCMP requests are ignored +- 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: @@ -1861,6 +1864,7 @@ - 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: @@ -1876,9 +1880,9 @@ - 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 + # 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' @@ -1977,6 +1981,9 @@ # 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 }}" @@ -2046,7 +2053,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.5 + - 4.1.3.5 # Control 4.1.3.6 requires a system scan, skipping @@ -2082,7 +2089,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.8 + - 4.1.3.8 - name: 4.1.3.9 - Ensure modifications to discretionary access controls are collected ansible.builtin.template: @@ -2093,7 +2100,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.9 + - 4.1.3.9 - name: 4.1.3.11 - Ensure session initiation information is collected ansible.builtin.template: @@ -2104,7 +2111,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.11 + - 4.1.3.11 - name: 4.1.3.12 - Ensure system logins are collected ansible.builtin.template: @@ -2115,7 +2122,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.12 + - 4.1.3.12 - name: 4.1.3.13 - Ensure file deletion events by users are collected ansible.builtin.template: @@ -2126,7 +2133,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.13 + - 4.1.3.13 - name: 4.1.3.14 - Ensure modifications to Mandatory Access Controls are collected ansible.builtin.template: @@ -2137,7 +2144,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.14 + - 4.1.3.14 - name: 4.1.3.15 - Ensure successful and unsuccessful attempts to use the chcon command are recorded ansible.builtin.template: @@ -2148,7 +2155,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.15 + - 4.1.3.15 - name: 4.1.3.16 - Ensure successful and unsuccessful attempts to use the setfacl command are recorded ansible.builtin.template: @@ -2159,7 +2166,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.16 + - 4.1.3.16 - name: 4.1.3.17 - Ensure successful and unsuccessful attempts to use the chacl command are recorded ansible.builtin.template: @@ -2170,7 +2177,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.17 + - 4.1.3.17 - name: 4.1.3.18 - Ensure successful and unsuccessful attempts to use the usermod command are recorded ansible.builtin.template: @@ -2181,7 +2188,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.18 + - 4.1.3.18 - name: 4.1.3.19 - Ensure kernel module loading and unloading is collected ansible.builtin.template: @@ -2192,7 +2199,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.19 + - 4.1.3.19 - name: 4.1.3.20 - Ensure audit configuration is immutable ansible.builtin.copy: @@ -2204,7 +2211,7 @@ mode: 0600 notify: Restart auditd tags: - 4.1.3.20 + - 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 @@ -2338,6 +2345,7 @@ # 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: @@ -2345,19 +2353,17 @@ ansible.builtin.dnf: name: systemd-journal-remote state: present - when: log_service and log_service == "journald" 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-jornal-remote.socket is masked + - 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 - when: log_service and log_service == "journald" tags: - 4.2.2.1.4 @@ -2366,7 +2372,6 @@ name: systemd-journal-remote.service enabled: false masked: true - when: log_service and log_service == "journald" tags: - 4.2.2.1.4 @@ -2377,7 +2382,6 @@ line: "Compress=yes" insertafter: "^#Compress=" notify: Restart journald - when: log_service and log_service == "journald" tags: - 4.2.2.3 @@ -2388,7 +2392,6 @@ line: "Storage=persistent" insertafter: "^#Storage=" notify: Restart journald - when: log_service and log_service == "journald" tags: - 4.2.2.4 @@ -2396,9 +2399,7 @@ ansible.builtin.lineinfile: dest: /etc/systemd/journald.conf regexp: "^ForwardToSyslog=((?!yes).)*$" - line: "ForwardToSyslog=yes" state: absent - when: log_service and log_service == "journald" tags: - 4.2.2.5 @@ -2517,7 +2518,7 @@ state: file when: at_allow and at_allow | length > 0 tags: - - 5.1.8 + - 5.1.9 - name: 5.1.9 - Ensure at is restricted to authorized users ansible.builtin.lineinfile: @@ -2563,8 +2564,8 @@ ansible.builtin.file: dest: "{{ item.path }}" owner: root - group: ssh_keys - mode: 0640 + group: root + mode: 0600 loop: "{{ ssh_host_out.files }}" - name: 5.2.3 - Set Permissions on ssh public host keys @@ -2588,52 +2589,48 @@ loop: "{{ ssh_hostpub_out.files }}" - name: 5.2.4 - Ensure SSH access is limited (AllowUsers) - 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 - tags: - - 5.2.4 - - - 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 - tags: - - 5.2.4 - - - 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 - tags: - - 5.2.4 - - - 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 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: @@ -2886,9 +2883,9 @@ # Control 5.4.3, Set password retention, requries file replacement # skipping -- name: 5.5.1 - Configure PAM files and password requirements +- name: 5.5.0 - Configure PAM files and password requirements tags: - - 5.5.1 + - 5.5.0 block: - name: 5.5.1 - require at least one digit in passwords ansible.builtin.lineinfile: diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index b3f99e3..ccb49bf 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -138,6 +138,7 @@ - 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 }}" @@ -271,6 +272,7 @@ - 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 }}" @@ -304,7 +306,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.5.3 Report to user if /var/log does not have nosuid set + - 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 @@ -331,6 +333,7 @@ - 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 }}" @@ -700,6 +703,7 @@ group: root mode: 0644 setype: systemd_unit_file_t + notify: Restart aidecheck - name: 1.3.2 - Enable aidecheck.timer ansible.builtin.systemd: @@ -742,14 +746,14 @@ path: "/boot/grub2/grub.cfg" register: grubdir tags: - 1.4.1 + 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.1 + 1.4.0 # Control 1.4.1, Grub bootloader password - skipped @@ -839,7 +843,7 @@ - python3-libselinux state: present register: selinux_installed - when: selinux is defined and selinux != "Disabled" + when: selinux is defined and selinux | lower != "disabled" tags: - 1.6.1.1 @@ -1556,38 +1560,16 @@ # 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.1 - Disable TIPC - ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'tipc' ] }}" - - name: 3.1.3 - 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 - with_items: - - "{{ uncommon_network }}" - - - name: 3.1.3 - Add to blacklist - ansible.builtin.lineinfile: - dest: /etc/modprobe.d/CIS.conf - line: "blacklist {{ item }}" - state: present + - name: 3.1.3 - Disable TIPC + ansible.builtin.blockinfile: + path: /etc/modprobe.d/tipc.conf create: true - owner: root - group: root - mode: 0644 - with_items: - - "{{ uncommon_network }}" - + 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 From 9d8329ccef137e83c2f08263e967e3f6d707c4a9 Mon Sep 17 00:00:00 2001 From: Mohamed Gamal Date: Thu, 18 May 2023 16:24:29 +0300 Subject: [PATCH 60/68] Remove spaces from issue file to meet CIS remedetion --- roles/cis_security/files/issue | 1 - 1 file changed, 1 deletion(-) 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. - From 84b8d3ed89ffd6ba84134f26e25eb7fa992fab25 Mon Sep 17 00:00:00 2001 From: Mohamed Gamal Date: Thu, 18 May 2023 16:25:51 +0300 Subject: [PATCH 61/68] Update banner --- roles/cis_security/files/banner | 1 - 1 file changed, 1 deletion(-) 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. - From 176ff156aad8414668efd6d842a7704b54348b3b Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 28 Jun 2023 16:09:29 -0400 Subject: [PATCH 62/68] updated with lint fixes --- .../tasks/type-files/redhat-7-type.yml | 4693 ++++++++--------- 1 file changed, 2342 insertions(+), 2351 deletions(-) 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 ef9c7c2..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_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 - 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 - Configure 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 +# 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" - - - name: 3.6.1 - Configure 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 - when: enable_firewall is defined and enable_firewall == "iptables" - - - 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,8 +1987,8 @@ 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: @@ -2134,12 +2130,10 @@ 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 From 763e009dae11c765549a46bf402123d1f17c9c64 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 28 Jun 2023 16:22:41 -0400 Subject: [PATCH 63/68] updates for jinja rules --- .../tasks/type-files/redhat-8-type.yml | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) 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 d41d504..fa86754 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -1265,7 +1265,7 @@ - name: 2.2.1 - Remove xinetd; add to removal list ansible.builtin.set_fact: - unneeded_packages: "{{ unneeded_packages + [ 'xinetd' ] }}" + unneeded_packages: "{{ unneeded_packages + ['xinetd'] }}" when: tftp_server is defined and not tftp_server tags: @@ -1273,151 +1273,151 @@ - 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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'] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + 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' ] }}" + unneeded_packages: "{{ unneeded_packages + ['tftp'] }}" tags: - 2.3.6 @@ -1491,20 +1491,20 @@ - 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'}) }}" + 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'}) }}" + 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' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" loop: - net.ipv4.conf.all.send_redirects - net.ipv4.conf.default.send_redirects @@ -1513,7 +1513,7 @@ - name: 3.3.1 - Ensure source routed packets are not accepted ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" loop: - net.ipv4.conf.all.accept_source_route - net.ipv4.conf.default.accept_source_route @@ -1522,7 +1522,7 @@ - name: 3.3.1 - Ensure source routed packets are not accepted ansible.builtin.set_fact: - unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" loop: - net.ipv6.conf.all.accept_source_route - net.ipv6.conf.default.accept_source_route @@ -1532,7 +1532,7 @@ - name: 3.3.2 - Ensure ICMP redirects are not accepted ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" loop: - net.ipv4.conf.all.accept_redirects - net.ipv4.conf.default.accept_redirects @@ -1541,7 +1541,7 @@ - name: 3.3.2 - Ensure ICMP redirects are not accepted ansible.builtin.set_fact: - unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({ item : '0' }) }}" + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" loop: - net.ipv6.conf.all.accept_redirects - net.ipv6.conf.default.accept_redirects @@ -1551,7 +1551,7 @@ - name: 3.3.3 - Ensure secure ICMP redirects are not accepted ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '0' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '0'}) }}" loop: - net.ipv4.conf.all.secure_redirects - net.ipv4.conf.default.secure_redirects @@ -1560,7 +1560,7 @@ - name: 3.3.4 - Ensure suspicious packets are logged ansible.builtin.set_fact: - unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({ item : '1' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '1'}) }}" loop: - net.ipv4.conf.all.log_martians - net.ipv4.conf.default.log_martians @@ -1569,19 +1569,19 @@ - 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' }) }}" + 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' }) }}" + 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' }) }}" + unneeded_ipv4_network: "{{ unneeded_ipv4_network | combine({item: '1'}) }}" loop: - net.ipv4.conf.all.rp_filter - net.ipv4.conf.default.rp_filter @@ -1590,13 +1590,13 @@ - 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' }) }}" + 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' }) }}" + unneeded_ipv6_network: "{{ unneeded_ipv6_network | combine({item: '0'}) }}" when: not ipv6_disable loop: - net.ipv6.conf.all.accept_ra @@ -1626,7 +1626,7 @@ reload: true state: present sysctl_set: true - loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv4_network) }}" + loop: "{{ lookup('ansible.builtin.dict', unneeded_ipv4_network) }}" notify: Flush network routes - name: 3.3 - Process unneeded network settings for IPv6 @@ -1640,7 +1640,7 @@ reload: true state: present sysctl_set: true - loop: "{{ lookup('ansible.builtin.dict' , unneeded_ipv6_network) }}" + loop: "{{ lookup('ansible.builtin.dict', unneeded_ipv6_network) }}" notify: Flush network routes - name: 3.1 - Disable uncommon network protocols @@ -1656,13 +1656,13 @@ - name: 3.1.2 - Add sctp to list of uncommon network protocols to disable ansible.builtin.set_fact: - uncommon_network: "{{ uncommon_network + [ 'sctp' ] }}" + 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' ] }}" + uncommon_network: "{{ uncommon_network + ['dccp'] }}" tags: - 3.1.3 From ced796b2d4f88a0bde35e46061a9f51007e480c5 Mon Sep 17 00:00:00 2001 From: Nicholas <140363962+devops-nick@users.noreply.github.com> Date: Wed, 23 Aug 2023 20:48:16 +0800 Subject: [PATCH 64/68] Update controls_list_win.md --- docs/controls_list_win.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/controls_list_win.md b/docs/controls_list_win.md index 446c07f..d484e42 100644 --- a/docs/controls_list_win.md +++ b/docs/controls_list_win.md @@ -33,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| From 13c3cabec3ae6ac342fff7cdf01fe00be66e39f5 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 27 Sep 2023 21:33:15 -0400 Subject: [PATCH 65/68] Finished the error in Issue #80 --- roles/cis_security/tasks/type-files/ubuntu-18-type.yml | 2 +- roles/cis_security/tasks/type-files/ubuntu-22-type.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 3ce6a40..9fe54b2 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml @@ -1153,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 diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml index fc54736..45fd1b9 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -1228,7 +1228,7 @@ # to remove packages from the system that are not needed. - name: Process removal list ansible.builtin.apt: - name: unneeded_packages + name: "{{ unneeded_packages }}" state: absent purge: true tags: From 7a780241986ff5adf7b5ea5d33ab5af75e7441bb Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 27 Sep 2023 21:44:52 -0400 Subject: [PATCH 66/68] formatting fixes per Issue #72 --- roles/cis_security/templates/audit_rules/datetime.rules | 2 +- roles/cis_security/templates/audit_rules/network.rules | 4 ++-- roles/cis_security/templates/audit_rules/user_emulation.rules | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/cis_security/templates/audit_rules/datetime.rules b/roles/cis_security/templates/audit_rules/datetime.rules index 7a02993..a86a024 100644 --- a/roles/cis_security/templates/audit_rules/datetime.rules +++ b/roles/cis_security/templates/audit_rules/datetime.rules @@ -1,3 +1,3 @@ -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 \ No newline at end of file +-w /etc/localtime -p wa -k time-change diff --git a/roles/cis_security/templates/audit_rules/network.rules b/roles/cis_security/templates/audit_rules/network.rules index 19cf3a7..3dd3d84 100644 --- a/roles/cis_security/templates/audit_rules/network.rules +++ b/roles/cis_security/templates/audit_rules/network.rules @@ -1,5 +1,5 @@ --a always,exit -F arch=b64 -S sethostname -S setdomainname -k -F system-locale --a always,exit -F arch=b32 -S sethostname -S setdomainname -k -F 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 diff --git a/roles/cis_security/templates/audit_rules/user_emulation.rules b/roles/cis_security/templates/audit_rules/user_emulation.rules index 5152868..268004a 100644 --- a/roles/cis_security/templates/audit_rules/user_emulation.rules +++ b/roles/cis_security/templates/audit_rules/user_emulation.rules @@ -1,2 +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 \ No newline at end of file +-a always,exit -F arch=b32 -C euid!=uid -F auid!=unset -S execve -k user_emulation From bda805490a8517dfe6a81a868c918ea434b3cd52 Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 27 Sep 2023 22:07:15 -0400 Subject: [PATCH 67/68] fixed symllnk location per Issue #78 --- roles/cis_security/tasks/type-files/ubuntu-18-type.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9fe54b2..c3b1985 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml @@ -159,8 +159,8 @@ # 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 From a1ebd900fa9754f8b61d8923ac40ab9cf4d2ebad Mon Sep 17 00:00:00 2001 From: David Glaser Date: Wed, 27 Sep 2023 22:12:08 -0400 Subject: [PATCH 68/68] for some reason I need to push them again? --- roles/cis_security/tasks/type-files/redhat-8-type.yml | 2 +- roles/cis_security/tasks/type-files/redhat-9-type.yml | 2 +- roles/cis_security/tasks/type-files/ubuntu-18-type.yml | 2 +- roles/cis_security/tasks/type-files/ubuntu-22-type.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 fa86754..a1f90e3 100644 --- a/roles/cis_security/tasks/type-files/redhat-8-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-8-type.yml @@ -3069,7 +3069,7 @@ - name: 5.6.6 - 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 # Section 6 - System Maintenance diff --git a/roles/cis_security/tasks/type-files/redhat-9-type.yml b/roles/cis_security/tasks/type-files/redhat-9-type.yml index ccb49bf..56c961d 100644 --- a/roles/cis_security/tasks/type-files/redhat-9-type.yml +++ b/roles/cis_security/tasks/type-files/redhat-9-type.yml @@ -2880,7 +2880,7 @@ - name: 5.6.6 - 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 # Section 6 - System Maintenance 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 c3b1985..116eebd 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-18-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-18-type.yml @@ -698,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 diff --git a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml index 45fd1b9..cf41bd1 100644 --- a/roles/cis_security/tasks/type-files/ubuntu-22-type.yml +++ b/roles/cis_security/tasks/type-files/ubuntu-22-type.yml @@ -693,7 +693,7 @@ - name: 1.4.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.4.3 - Set root password