├── templates ├── etc │ ├── issue.j2 │ ├── motd.j2 │ ├── issue.net.j2 │ ├── systemd │ │ ├── journald.conf.d │ │ │ ├── forwardtosyslog.conf.j2 │ │ │ ├── storage.conf.j2 │ │ │ └── rotation.conf.j2 │ │ └── system │ │ │ └── tmp.mount.j2 │ ├── crypto-policies │ │ └── policies │ │ │ └── modules │ │ │ ├── NO-WEAKMAC.pmod.j2 │ │ │ ├── NO-SSHWEAKMACS.pmod.j2 │ │ │ ├── NO-SHA1.pmod.j2 │ │ │ ├── NO-SSHCBC.pmod.j2 │ │ │ ├── NO-SSHETM.pmod.j2 │ │ │ └── NO-SSHWEAKCIPHERS.pmod.j2 │ ├── security │ │ └── pwquality.conf.d │ │ │ ├── 50-pwlength.conf.j2 │ │ │ ├── 50-pwdictcheck.conf.j2 │ │ │ ├── 50-pwdifok.conf.j2 │ │ │ ├── 50-pwroot.conf.j2 │ │ │ ├── 50-pwrepeat.conf.j2 │ │ │ ├── 50-pwquality_enforce.conf.j2 │ │ │ ├── 50-pwmaxsequence.conf.j2 │ │ │ └── 50-pwcomplexity.conf.j2 │ ├── dconf │ │ └── db │ │ │ ├── 00-media-autorun.j2 │ │ │ ├── 00-media-automount.j2 │ │ │ ├── 00-autorun_lock.j2 │ │ │ ├── gdm.d │ │ │ └── 01-banner-message.j2 │ │ │ ├── 00-screensaver_lock.j2 │ │ │ ├── 00-automount_lock.j2 │ │ │ └── 00-screensaver.j2 │ ├── modprobe.d │ │ └── modprobe.conf.j2 │ ├── aide.conf.d │ │ └── crypt_audit_procs.conf.j2 │ ├── sysctl.d │ │ ├── 60-kernel_sysctl.conf.j2 │ │ ├── 60-disable_ipv6.conf.j2 │ │ ├── 60-netipv6_sysctl.conf.j2 │ │ └── 60-netipv4_sysctl.conf.j2 │ ├── cron.d │ │ └── aide.cron.j2 │ ├── logrotate.d │ │ └── rsyslog_log.j2 │ ├── chrony.conf.j2 │ └── ansible │ │ └── compliance_facts.j2 └── audit │ └── 98_auditd_exception.rules.j2 ├── vars ├── CentOS.yml ├── Rocky.yml ├── AlmaLinux.yml ├── OracleLinux.yml ├── RedHat.yml ├── audit.yml ├── is_container.yml └── main.yml ├── site.yml ├── .ansible-lint ├── .gitattributes ├── tasks ├── check_prereqs.yml ├── section_7 │ └── main.yml ├── section_3 │ ├── main.yml │ ├── cis_3.2.x.yml │ └── cis_3.1.x.yml ├── section_1 │ ├── cis_1.2.2.x.yml │ ├── main.yml │ ├── cis_1.5.x.yml │ ├── cis_1.1.2.4.x.yml │ ├── cis_1.1.2.3.x.yml │ ├── cis_1.7.x.yml │ ├── cis_1.4.x.yml │ ├── cis_1.1.2.2.x.yml │ ├── cis_1.1.2.5.x.yml │ ├── cis_1.1.2.6.x.yml │ ├── cis_1.1.2.7.x.yml │ ├── cis_1.2.1.x.yml │ ├── cis_1.1.2.1.x.yml │ ├── cis_1.3.1.x.yml │ └── cis_1.6.x.yml ├── section_4 │ ├── main.yml │ ├── cis_4.2.x.yml │ └── cis_4.1.x.yml ├── audit_only.yml ├── section_2 │ ├── main.yml │ ├── cis_2.3.x.yml │ ├── cis_2.2.x.yml │ └── cis_2.4.x.yml ├── warning_facts.yml ├── LE_audit_setup.yml ├── parse_etc_password.yml ├── section_6 │ ├── main.yml │ ├── cis_6.2.4.1.yml │ ├── cis_6.2.2.1.x.yml │ ├── cis_6.2.2.x.yml │ ├── cis_6.3.2.x.yml │ ├── cis_6.3.4.x.yml │ ├── cis_6.3.1.x.yml │ ├── cis_6.2.1.x.yml │ └── cis_6.1.x.yml ├── post_remediation_audit.yml ├── post.yml ├── section_5 │ ├── cis_5.4.3.x.yml │ ├── cis_5.3.1.x.yml │ ├── main.yml │ ├── cis_5.3.3.3.x.yml │ ├── cis_5.3.3.1.x.yml │ ├── cis_5.2.x.yml │ └── cis_5.3.3.4.x.yml ├── fetch_audit_output.yml ├── auditd.yml └── pre_remediation_audit.yml ├── molecule ├── default │ ├── verify.yml │ ├── molecule.yml │ └── converge.yml ├── wsl │ ├── verify.yml │ ├── molecule.yml │ └── converge.yml └── localhost │ ├── verify.yml │ ├── converge.yml │ └── molecule.yml ├── collections └── requirements.yml ├── .github └── workflows │ ├── add_repo_issue_to_gh_project.yml │ ├── export_badges_public.yml │ ├── export_badges_private.yml │ ├── benchmark_tracking_controller.yml │ ├── main_pipeline_validation.yml │ └── devel_pipeline_validation.yml ├── meta └── main.yml ├── .gitignore ├── files ├── etc │ └── systemd │ │ └── system │ │ └── tmp.mount └── fs_with_cves.sh ├── Makefile ├── .yamllint ├── LICENSE ├── .pre-commit-config.yaml └── CONTRIBUTING.rst /templates/etc/issue.j2: -------------------------------------------------------------------------------- 1 | {{ rhel9cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /templates/etc/motd.j2: -------------------------------------------------------------------------------- 1 | {{ rhel9cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /templates/etc/issue.net.j2: -------------------------------------------------------------------------------- 1 | {{ rhel9cis_warning_banner }} 2 | -------------------------------------------------------------------------------- /vars/CentOS.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | os_gpg_key_pubkey_name: centos-gpg-keys 4 | os_gpg_key_pubkey_content: "builder@centos.org 9.0" 5 | -------------------------------------------------------------------------------- /site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Apply ansible-lockdown hardening 4 | hosts: all 5 | become: true 6 | roles: 7 | - role: "{{ playbook_dir }}" 8 | -------------------------------------------------------------------------------- /templates/etc/systemd/journald.conf.d/forwardtosyslog.conf.j2: -------------------------------------------------------------------------------- 1 | # File created for CIS benchmark 2 | # CIS rule 6_2_2_2 3 | [Journal] 4 | ForwardToSyslog=no 5 | -------------------------------------------------------------------------------- /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | parseable: true 4 | quiet: true 5 | skip_list: 6 | - 'package-latest' 7 | - 'risky-shell-pipe' 8 | use_default_rules: true 9 | verbosity: 0 10 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy to disable weak macs 2 | # Carried out as part of CIS Benchmark rule 1.6.4 3 | 4 | mac = -*-64 5 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwlength.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.2 Ensure minimum password length is configured 3 | minlen = {{ rhel9cis_passwd_minlen_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwdictcheck.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.6 Ensure password dictionary check is enabled 3 | dictcheck = {{ rhel9cis_passwd_dictcheck_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwdifok.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.1 Ensure password number of changed characters is configured 3 | difok = {{ rhel9cis_passwd_difok_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwroot.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.8 Ensure password quality is enforced for the root user 3 | {{ rhel9cis_passwd_quality_enforce_root_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwrepeat.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.4 Ensure password same consecutive characters is configured 3 | maxrepeat = {{ rhel9cis_passwd_maxrepeat_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwquality_enforce.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.7 Ensure password quality checking is enforced 3 | enforcing = {{ rhel9cis_passwd_quality_enforce_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwmaxsequence.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.5 Ensure password maximum sequential characters is configured 3 | maxsequence = {{ rhel9cis_passwd_maxsequence_value }} 4 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-SSHWEAKMACS.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy to disable weak macs 2 | # Carried out as part of CIS Benchmark control 5.1.6 3 | 4 | mac@SSH = -HMAC-MD5* -UMAC-64* -UMAC-128* 5 | -------------------------------------------------------------------------------- /vars/Rocky.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OS Specific Settings 3 | 4 | os_gpg_key_pubkey_name: gpg-pubkey-350d275d-6279464b 5 | os_gpg_key_pubkey_content: "Rocky Enterprise Software Foundation - Release key 2022 350d275d" 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # adding github settings to show correct language 2 | *.sh linguist-detectable=true 3 | *.yml linguist-detectable=true 4 | *.ps1 linguist-detectable=true 5 | *.j2 linguist-detectable=true 6 | *.md linguist-documentation 7 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-SHA1.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy dropping the SHA1 hash and signature support 2 | # Carried out as part of CIS Benchmark rule 1.6.3 3 | 4 | hash = -SHA1 5 | sign = -*-SHA1 6 | sha1_in_certs = 0 7 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-SSHCBC.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy to disable all CBC mode ciphers 2 | # for the SSH protocol (libssh and OpenSSH) 3 | # Carried out as part of CIS Benchmark rule 1.6.5 4 | 5 | cipher@SSH = -*-CBC 6 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-SSHETM.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy to disable Encrypt then MAC 2 | # for the SSH protocol (libssh and OpenSSH) 3 | # Carried out as part of CIS Benchmark rule 1.6.7 4 | 5 | etm@SSH = DISABLE_ETM 6 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-media-autorun.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | [org/gnome/desktop/media-handling] 6 | autorun-never=true 7 | -------------------------------------------------------------------------------- /tasks/check_prereqs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "PREREQ | If required install libselinux package to manage file changes." 4 | when: '"libselinux-python3" not in ansible_facts.packages' 5 | ansible.builtin.package: 6 | name: libselinux-python3 7 | state: present 8 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-media-automount.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | [org/gnome/desktop/media-handling] 6 | automount=false 7 | automount-open=false 8 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-autorun_lock.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | # Lock desktop media-handling settings 6 | /org/gnome/desktop/media-handling/autorun-never 7 | -------------------------------------------------------------------------------- /templates/etc/modprobe.d/modprobe.conf.j2: -------------------------------------------------------------------------------- 1 | # Disable usage of protocol {{ item }} 2 | # Set by ansible {{ benchmark }} remediation role 3 | # https://github.com/ansible-lockdown 4 | ## This file is managed by Ansible, YOUR CHANGES WILL BE LOST! 5 | 6 | install {{ item }} /bin/true 7 | -------------------------------------------------------------------------------- /vars/AlmaLinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OS Specific Settings 3 | 4 | os_gpg_key_pubkey_name: gpg-pubkey-b86b3716-61e69f29 5 | os_gpg_key_pubkey_content: "AlmaLinux OS 9 b86b3716" 6 | # disable repo_gpgcheck due to OS default repos 7 | rhel9cis_rule_enable_repogpg: false 8 | -------------------------------------------------------------------------------- /vars/OracleLinux.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OS Specific Settings 3 | os_gpg_key_pubkey_name: gpg-pubkey-8d8b756f-629e59ec 4 | os_gpg_key_pubkey_content: "Oracle Linux (release key 1) " 5 | # disable repo_gpgcheck due to OS default repos 6 | rhel9cis_rule_enable_repogpg: false 7 | -------------------------------------------------------------------------------- /tasks/section_7/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 7.1 | System File Permissions" 4 | ansible.builtin.import_tasks: 5 | file: cis_7.1.x.yml 6 | 7 | - name: "SECTION | 7.2 | Local User and Group Settings" 8 | ansible.builtin.import_tasks: 9 | file: cis_7.2.x.yml 10 | -------------------------------------------------------------------------------- /vars/RedHat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # OS Specific Settings 3 | 4 | os_gpg_key_pubkey_name: gpg-pubkey-fd431d51-4ae0493b 5 | os_gpg_key_pubkey_content: "Red Hat, Inc. (release key 2) fd431d51" 6 | 7 | # disable repo_gpgcheck due to OS default repos 8 | rhel9cis_rule_enable_repogpg: false 9 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/gdm.d/01-banner-message.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | [org/gnome/login-screen] 6 | banner-message-enable=true 7 | banner-message-text="{{ rhel9cis_warning_banner | trim | replace("\n", "\\n") }}" 8 | -------------------------------------------------------------------------------- /templates/etc/systemd/journald.conf.d/storage.conf.j2: -------------------------------------------------------------------------------- 1 | # File created for CIS benchmark 2 | [Journal] 3 | {% if rhel9cis_rule_6_2_2_3 %} 4 | # Set compress CIS rule 6_2_2_3 5 | Compress=yes 6 | {% endif %} 7 | 8 | {% if rhel9cis_rule_6_2_2_4 %} 9 | # Set persistent storage CIS rule 6_2_2_4 10 | Storage=persistent 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /templates/etc/aide.conf.d/crypt_audit_procs.conf.j2: -------------------------------------------------------------------------------- 1 | # Audit Tools 2 | /sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 3 | /sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 4 | /sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 5 | /sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 6 | /sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 7 | /sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 8 | -------------------------------------------------------------------------------- /templates/etc/sysctl.d/60-kernel_sysctl.conf.j2: -------------------------------------------------------------------------------- 1 | ## This file is managed by Ansible, YOUR CHANGES WILL BE LOST! 2 | 3 | {% if rhel9cis_rule_1_5_1 %} 4 | # Adress space randomise 5 | # CIS 1.5.1 6 | kernel.randomize_va_space = 2 7 | {% endif %} 8 | {% if rhel9cis_rule_1_5_2 %} 9 | # Ptrace scope 10 | # CIS 1.5.2 11 | kernel.yama.ptrace_scope = 1 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: false 5 | 6 | vars: 7 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 8 | 9 | tasks: 10 | - name: "Include verify tasks" 11 | ansible.builtin.include_role: 12 | name: "{{ role_name }}" 13 | tasks_from: verify 14 | -------------------------------------------------------------------------------- /molecule/wsl/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: false 5 | 6 | vars: 7 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 8 | 9 | tasks: 10 | - name: "Include verify tasks" 11 | ansible.builtin.include_role: 12 | name: "{{ role_name }}" 13 | tasks_from: verify 14 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-screensaver_lock.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | # Lock desktop screensaver idle-delay setting 6 | /org/gnome/desktop/session/idle-delay 7 | 8 | # Lock desktop screensaver lock-delay setting 9 | /org/gnome/desktop/screensaver/lock-delay 10 | -------------------------------------------------------------------------------- /templates/etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod.j2: -------------------------------------------------------------------------------- 1 | # This is a subpolicy to disable weak ciphers 2 | # for the SSH protocol (libssh and OpenSSH) 3 | # Carried out as part of CIS Benchmark rules combined 1.6.6 and 5.1.4 4 | cipher@SSH ={% if rhel9cis_rule_1_6_6 %} -CHACHA20-POLY1305{% endif %}{% if rhel9cis_rule_5_1_4 %} -3DES-CBC -AES-128-CBC -AES-192-CBC -AES-256-CBC{% endif %} 5 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-automount_lock.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | # Lock desktop media-handling automount setting 6 | /org/gnome/desktop/media-handling/automount 7 | 8 | # Lock desktop media-handling automount-open 9 | /org/gnome/desktop/media-handling/automount-open 10 | -------------------------------------------------------------------------------- /templates/etc/systemd/journald.conf.d/rotation.conf.j2: -------------------------------------------------------------------------------- 1 | # File created for CIS benchmark 2 | # CIS rule 6_2_1_3 3 | [Journal] 4 | SystemMaxUse={{ rhel9cis_journald_systemmaxuse }} 5 | SystemKeepFree={{ rhel9cis_journald_systemkeepfree }} 6 | RuntimeMaxUse={{ rhel9cis_journald_runtimemaxuse }} 7 | RuntimeKeepFree={{ rhel9cis_journald_runtimekeepfree }} 8 | MaxFileSec={{ rhel9cis_journald_maxfilesec }} 9 | -------------------------------------------------------------------------------- /molecule/localhost/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Verify 3 | hosts: all 4 | gather_facts: false 5 | become: true 6 | 7 | vars: 8 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 9 | 10 | tasks: 11 | - name: "Include verify tasks" 12 | ansible.builtin.include_role: 13 | name: "{{ role_name }}" 14 | tasks_from: verify 15 | -------------------------------------------------------------------------------- /templates/etc/sysctl.d/60-disable_ipv6.conf.j2: -------------------------------------------------------------------------------- 1 | ## This file is managed by Ansible, YOUR CHANGES WILL BE LOST! 2 | 3 | # IPv6 disable 4 | {% if rhel9cis_rule_3_1_1 and not rhel9cis_ipv6_required %} 5 | net.ipv6.conf.all.disable_ipv6 = 1 6 | net.ipv6.conf.default.disable_ipv6 = 1 7 | {% for interface in ansible_interfaces %} 8 | net.ipv6.conf.{{ interface }}.disable_ipv6 = 1 9 | {% endfor %} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /collections/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | collections: 4 | - name: community.general 5 | source: https://github.com/ansible-collections/community.general 6 | type: git 7 | 8 | - name: community.crypto 9 | source: https://github.com/ansible-collections/community.crypto 10 | type: git 11 | 12 | - name: ansible.posix 13 | source: https://github.com/ansible-collections/ansible.posix 14 | type: git 15 | -------------------------------------------------------------------------------- /tasks/section_3/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 3.1.x | Configure Network Devices" 4 | ansible.builtin.import_tasks: 5 | file: cis_3.1.x.yml 6 | 7 | - name: "SECTION | 3.2.x | Configure Network Kernel Modules" 8 | ansible.builtin.import_tasks: 9 | file: cis_3.2.x.yml 10 | 11 | - name: "SECTION | 3.3.x | Configure Network Kernel Parameters" 12 | ansible.builtin.import_tasks: 13 | file: cis_3.3.x.yml 14 | -------------------------------------------------------------------------------- /templates/etc/cron.d/aide.cron.j2: -------------------------------------------------------------------------------- 1 | # Run AIDE integrity check 2 | ## Ansible controlled file 3 | # Added as part of ansible-lockdown CIS baseline 4 | # provided by Mindpoint Group - A Tyto Athene Company 5 | ### YOUR CHANGES WILL BE LOST! 6 | # CIS 1.3.2 7 | 8 | {{ rhel9cis_aide_cron['aide_minute'] }} {{ rhel9cis_aide_cron['aide_hour'] }} {{ rhel9cis_aide_cron['aide_month'] }} {{ rhel9cis_aide_cron['aide_weekday'] }} {{ rhel9cis_aide_cron['aide_job'] }} 9 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.2.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.2.2.1 | PATCH | Ensure updates, patches, and additional security software are installed" 4 | when: 5 | - rhel9cis_rule_1_2_2_1 6 | - not system_is_ec2 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - rule_1.2.2.1 12 | - NIST800-53R5_SI-2 13 | ansible.builtin.package: 14 | name: "*" 15 | state: latest 16 | notify: Change_requires_reboot 17 | -------------------------------------------------------------------------------- /tasks/section_4/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 4.1.x | Configure a firewall" 4 | ansible.builtin.import_tasks: 5 | file: cis_4.1.x.yml 6 | 7 | - name: "SECTION | 4.2.x | Configure FirewallD" 8 | when: rhel9cis_firewall == "firewalld" 9 | ansible.builtin.import_tasks: 10 | file: cis_4.2.x.yml 11 | 12 | - name: "SECTION | 4.3.x | Configure nftables" 13 | when: rhel9cis_firewall == "nftables" 14 | ansible.builtin.import_tasks: 15 | file: cis_4.3.x.yml 16 | -------------------------------------------------------------------------------- /templates/audit/98_auditd_exception.rules.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | ### YOUR CHANGES WILL BE LOST! 5 | 6 | # This file contains users whose actions are not logged by auditd 7 | {% if rhel9cis_allow_auditd_uid_user_exclusions %} 8 | {% for user in rhel9cis_auditd_uid_exclude %} 9 | -a never,user -F uid!={{ user }} -F auid!={{ user }} 10 | {% endfor %} 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /.github/workflows/add_repo_issue_to_gh_project.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Add Repo Issue to ALD GH project 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | - reopened 9 | - transferred 10 | jobs: 11 | add-to-project: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@main 15 | with: 16 | project-url: https://github.com/orgs/ansible-lockdown/projects/1 17 | github-token: ${{ secrets.ALD_GH_PROJECT }} 18 | -------------------------------------------------------------------------------- /tasks/audit_only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Audit_only | Fetch audit files 4 | when: 5 | - fetch_audit_output 6 | - audit_only 7 | ansible.builtin.import_tasks: 8 | file: fetch_audit_output.yml 9 | 10 | - name: Audit_only | Show Audit Summary 11 | when: audit_only 12 | ansible.builtin.debug: 13 | msg: "{{ audit_results.split('\n') }}" 14 | 15 | - name: Audit_only | Stop task for host as audit_only selected 16 | when: audit_only 17 | ansible.builtin.meta: end_host 18 | -------------------------------------------------------------------------------- /tasks/section_2/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 2.1 | Special Purpose Services" 4 | ansible.builtin.import_tasks: 5 | file: cis_2.1.x.yml 6 | 7 | - name: "SECTION | 2.2 | Service Clients" 8 | ansible.builtin.import_tasks: 9 | file: cis_2.2.x.yml 10 | 11 | - name: "SECTION | 2.3 | Time Synchronization" 12 | ansible.builtin.import_tasks: 13 | file: cis_2.3.x.yml 14 | 15 | - name: "SECTION | 2.4 | Job Schedulers" 16 | ansible.builtin.import_tasks: 17 | file: cis_2.4.x.yml 18 | -------------------------------------------------------------------------------- /molecule/localhost/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is a playbook to test the tasks. 3 | - name: Converge 4 | hosts: all 5 | become: true 6 | gather_facts: true 7 | 8 | vars: 9 | ansible_user: "{{ lookup('env', 'USER') }}" 10 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 11 | rhel9cis_rule_5_2_4: false 12 | 13 | pre_tasks: 14 | tasks: 15 | - name: "Include tasks" 16 | ansible.builtin.include_role: 17 | name: "{{ role_name }}" 18 | -------------------------------------------------------------------------------- /templates/etc/security/pwquality.conf.d/50-pwcomplexity.conf.j2: -------------------------------------------------------------------------------- 1 | # CIS Configurations 2 | # 5.3.3.2.3 Ensure password complexity is configured 3 | {% if rhel9cis_passwd_complex_option == 'minclass' %} # pragma: allowlist secret 4 | minclass = {{ rhel9cis_passwd_minclass }} 5 | {% endif %} 6 | {% if rhel9cis_passwd_complex_option == 'credits' %} # pragma: allowlist secret 7 | dcredit = {{rhel9cis_passwd_dcredit }} 8 | ucredit = {{ rhel9cis_passwd_ucredit }} 9 | ocredit = {{ rhel9cis_passwd_ocredit }} 10 | lcredit = {{ rhel9cis_passwd_lcredit }} 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /molecule/wsl/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Molecule configuration 3 | # https://molecule.readthedocs.io/en/latest/ 4 | 5 | driver: 6 | name: delegated 7 | options: 8 | managed: false 9 | ansible_connection_options: 10 | ansible_connection: local 11 | platforms: 12 | - name: localhost 13 | 14 | provisioner: 15 | name: ansible 16 | config_options: 17 | defaults: 18 | interpreter_python: auto_silent 19 | callbacks_enabled: profile_tasks, timer 20 | 21 | lint: | 22 | set -e 23 | yamllint . 24 | ansible-lint 25 | flake8 26 | 27 | verifier: 28 | name: ansible 29 | -------------------------------------------------------------------------------- /templates/etc/dconf/db/00-screensaver.j2: -------------------------------------------------------------------------------- 1 | ## Ansible controlled file 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | # Specify the dconf path 6 | [org/gnome/desktop/session] 7 | 8 | # Number of seconds of inactivity before the screen goes blank 9 | # Set to 0 seconds if you want to deactivate the screensaver. 10 | idle-delay=uint32 {{ rhel9cis_screensaver_idle_delay }} 11 | 12 | # Specify the dconf path 13 | [org/gnome/desktop/screensaver] 14 | 15 | # Number of seconds after the screen is blank before locking the screen 16 | lock-delay=uint32 {{ rhel9cis_screensaver_lock_delay }} 17 | -------------------------------------------------------------------------------- /.github/workflows/export_badges_public.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Export Public Repo Badges 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | - devel 10 | workflow_dispatch: 11 | 12 | jobs: 13 | export-badges: 14 | if: github.repository_visibility == 'public' && (github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (github.ref_name == 'devel' || github.ref_name == 'main'))) 15 | uses: ansible-lockdown/github_linux_IaC/.github/workflows/export_badges_public.yml@self_hosted 16 | with: 17 | repo_name: ${{ github.repository }} 18 | secrets: 19 | BADGE_PUSH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }} 20 | -------------------------------------------------------------------------------- /molecule/localhost/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Molecule configuration 3 | # https://molecule.readthedocs.io/en/latest/ 4 | 5 | driver: 6 | name: delegated 7 | options: 8 | managed: false 9 | ansible_connection_options: 10 | ansible_connection: local 11 | platforms: 12 | - name: localhost 13 | 14 | provisioner: 15 | name: ansible 16 | config_options: 17 | defaults: 18 | interpreter_python: auto_silent 19 | stdout_callback: yaml 20 | callbacks_enabled: profile_tasks, timer 21 | 22 | lint: | 23 | set -e 24 | yamllint . 25 | ansible-lint 26 | flake8 27 | 28 | verifier: 29 | name: ansible 30 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: "MindPoint Group" 4 | description: "Apply the RHEL 9 CIS" 5 | company: "MindPoint Group" 6 | license: MIT 7 | role_name: rhel9_cis 8 | namespace: mindpointgroup 9 | min_ansible_version: 2.10.1 10 | platforms: 11 | - name: EL 12 | versions: 13 | - "9" 14 | galaxy_tags: 15 | - system 16 | - security 17 | - stig 18 | - hardening 19 | - benchmark 20 | - compliance 21 | - redhat 22 | - complianceascode 23 | - disa 24 | - rhel9 25 | - cis 26 | - rocky 27 | - alma 28 | collections: 29 | - community.general 30 | - community.crypto 31 | - ansible.posix 32 | dependencies: [] 33 | -------------------------------------------------------------------------------- /templates/etc/sysctl.d/60-netipv6_sysctl.conf.j2: -------------------------------------------------------------------------------- 1 | ## This file is managed by Ansible, YOUR CHANGES WILL BE LOST! 2 | 3 | # IPv6 Network sysctl 4 | {% if rhel9cis_ipv6_required %} 5 | {% if rhel9cis_rule_3_3_1 %} 6 | # CIS 3.3.1 7 | net.ipv6.conf.all.forwarding = 0 8 | {% endif %} 9 | {% if rhel9cis_rule_3_3_5 %} 10 | # CIS 3.3.5 11 | net.ipv6.conf.all.accept_redirects = 0 12 | net.ipv6.conf.default.accept_redirects = 0 13 | {% endif %} 14 | {% if rhel9cis_rule_3_3_8 %} 15 | # CIS 3.3.8 16 | net.ipv6.conf.all.accept_source_route = 0 17 | net.ipv6.conf.default.accept_source_route = 0 18 | {% endif %} 19 | {% if rhel9cis_rule_3_3_11 %} 20 | # CIS 3.3.11 21 | net.ipv6.conf.all.accept_ra = 0 22 | net.ipv6.conf.default.accept_ra = 0 23 | {% endif %} 24 | {% endif %} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | *.log 3 | *.retry 4 | .cache 5 | .vagrant 6 | tests/*redhat-subscription 7 | tests/Dockerfile 8 | *.iso 9 | *.box 10 | packer_cache 11 | delete* 12 | ignore* 13 | test_inv 14 | # temp remove doc while this is built up 15 | doc/ 16 | # VSCode 17 | .vscode 18 | 19 | # Byte-compiled / optimized / DLL files 20 | __pycache__/ 21 | *.py[cod] 22 | *$py.class 23 | 24 | # DS_Store 25 | .DS_Store 26 | ._* 27 | 28 | # Linux Editors 29 | *~ 30 | \#*\# 31 | /.emacs.desktop 32 | /.emacs.desktop.lock 33 | .elc 34 | auto-save-list 35 | tramp 36 | .\#* 37 | *.swp 38 | *.swo 39 | rh-creds.env 40 | travis.env 41 | 42 | # Lockdown-specific 43 | benchparse/ 44 | *xccdf.xml 45 | *.retry 46 | 47 | # GitHub Action/Workflow files 48 | .github/ 49 | 50 | # Precommit exclusions 51 | .ansible/ 52 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Molecule configuration 3 | # https://molecule.readthedocs.io/en/latest/ 4 | 5 | driver: 6 | name: docker 7 | 8 | platforms: 9 | - name: ubi9 10 | image: registry.access.redhat.com/ubi9/ubi-init 11 | pre_build_image: true 12 | volumes: 13 | - /sys/fs/cgroup:/sys/fs/cgroup:ro 14 | privileged: true 15 | command: "/usr/sbin/init" 16 | capabilities: 17 | - SYS_ADMIN 18 | 19 | provisioner: 20 | name: ansible 21 | config_options: 22 | defaults: 23 | interpreter_python: auto_silent 24 | callbacks_enabled: profile_tasks, timer 25 | 26 | lint: | 27 | set -e 28 | yamllint . 29 | ansible-lint 30 | flake8 31 | 32 | verifier: 33 | name: ansible 34 | -------------------------------------------------------------------------------- /files/etc/systemd/system/tmp.mount: -------------------------------------------------------------------------------- 1 | # This file is part of systemd. 2 | # 3 | # systemd is free software; you can redistribute it and/or modify it 4 | # under the terms of the GNU Lesser General Public License as published by 5 | # the Free Software Foundation; either version 2.1 of the License, or 6 | # (at your option) any later version. 7 | 8 | [Unit] 9 | Description=Temporary Directory 10 | Documentation=man:hier(7) 11 | Documentation=http://www.freedesktop.org/wiki/Software/systemd/APIFileSystems 12 | ConditionPathIsSymbolicLink=!/tmp 13 | DefaultDependencies=no 14 | Conflicts=umount.target 15 | Before=local-fs.target umount.target 16 | 17 | [Mount] 18 | What=tmpfs 19 | Where=/tmp 20 | Type=tmpfs 21 | Options=mode=1777,strictatime,noexec,nodev,nosuid 22 | 23 | # Make 'systemctl enable tmp.mount' work: 24 | [Install] 25 | WantedBy=local-fs.target 26 | -------------------------------------------------------------------------------- /templates/etc/logrotate.d/rsyslog_log.j2: -------------------------------------------------------------------------------- 1 | /var/log/rsyslog/*.log { 2 | {{ rhel9cis_rsyslog_logrotate_rotated_when }} 3 | rotate {{ rhel9cis_rsyslog_logrotate_rotatation_keep }} 4 | {% if rhel9cis_rsyslog_logrotate_compress %} 5 | compress 6 | {% else %} 7 | nocompress 8 | {% endif %} 9 | {% if rhel9cis_rsyslog_logrotate_missingok %} 10 | missingok 11 | {% else %} 12 | nomissingok 13 | {% endif %} 14 | {% if rhel9cis_rsyslog_logrotate_notifempty %} 15 | notifempty 16 | {% else %} 17 | ifempty 18 | {% endif %} 19 | {% if rhel9cis_rsyslog_logrotate_create %} 20 | create{% if rhel9cis_rsyslog_logrotate_create_opts is defined %} {{ rhel9cis_rsyslog_logrotate_create_opts }}{% endif %} 21 | {% endif %} 22 | 23 | postrotate 24 | /usr/bin/systemctl reload rsyslog.service >/dev/null || true 25 | endscript 26 | } 27 | -------------------------------------------------------------------------------- /molecule/wsl/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is a playbook to test the tasks. 3 | - name: Converge 4 | hosts: all 5 | become: true 6 | gather_facts: true 7 | 8 | vars: 9 | ansible_user: "{{ lookup('env', 'USER') }}" 10 | system_is_container: true 11 | rhel8cis_selinux_disable: true 12 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 13 | rhel8cis_rule_5_3_4: false 14 | rhel8cis_rule_1_1_10: false 15 | rhel8cis_rsyslog_ansiblemanaged: false 16 | rhel8cis_rule_3_4_1_3: false 17 | rhel8cis_rule_3_4_1_4: false 18 | rhel8cis_rule_4_2_1_2: false 19 | rhel8cis_rule_4_2_1_4: false 20 | rhel8cis_rule_5_1_1: false 21 | 22 | pre_tasks: 23 | tasks: 24 | - name: "Include tasks" 25 | ansible.builtin.include_role: 26 | name: "{{ role_name }}" 27 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is a playbook to test the tasks. 3 | - name: Converge 4 | hosts: all 5 | gather_facts: true 6 | 7 | vars: 8 | role_name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" 9 | ansible_user: root 10 | system_is_container: true 11 | rhel9cis_selinux_disable: true 12 | rhel9cis_rule_5_2_4: false 13 | rhel9cis_rule_1_1_10: false 14 | rhel9cis_firewall: "none" 15 | rhel9cis_rule_4_1_1_1: false 16 | rhel9cis_rule_4_1_1_2: false 17 | rhel9cis_rule_4_1_1_3: false 18 | rhel9cis_rule_4_1_1_4: false 19 | rhel9cis_rule_4_2_1_2: false 20 | rhel9cis_rule_4_2_1_4: false 21 | rhel9cis_rule_5_1_1: false 22 | 23 | pre_tasks: 24 | tasks: 25 | - name: "Include tasks" 26 | ansible.builtin.include_role: 27 | name: "{{ role_name }}" 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all help galaxy-install ansible-list yamllint pip-requirements 2 | 3 | GALAXY=ansible-galaxy 4 | ANSIBLE_LINT='/usr/local/bin/ansible-lint' 5 | ANSIBLE_FILE=site.yml 6 | 7 | all: help 8 | 9 | help: 10 | @echo "Make command examples for Ansible" 11 | @echo "Command for assisting with ansible setup" 12 | @echo " galaxy-install to install roles using ansible-galaxy" 13 | @echo " ansible-lint to lint playbook files" 14 | @echo " yamllint to lint playbook files" 15 | @echo " pip-requirements add pip required file" 16 | 17 | galaxy-install: 18 | $(GALAXY) install -r ./collections/requirements.yml 19 | 20 | ansible-lint: 21 | $(ANSIBLE_LINT) $(ANSIBLE_FILE) 22 | 23 | yamllint: 24 | git ls-files "*.yml"|xargs yamllint 25 | 26 | pip-requirements: 27 | @echo 'Python dependencies:' 28 | @cat .config/requirements.txt 29 | pip3 install -r requirements.txt 30 | -------------------------------------------------------------------------------- /tasks/warning_facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This task is used to create variables used in giving a warning summary for manual tasks 3 | # that need attention 4 | # 5 | # The warn_control_list and warn_count vars start life in vars/main.yml but get updated 6 | # as the tasks that have a warning complete 7 | # 8 | # Those two variables are used in the tasks/main.yml to display a list of warnings 9 | # 10 | # warn_control_id is set within the task itself and has the control ID as the value 11 | # 12 | # warn_control_list is the main variable to be used and is a list made up of the warn_control_id’s 13 | # 14 | # warn_count the main variable for the number of warnings and each time a warn_control_id is added 15 | # the count increases by a value of 1 16 | - name: "{{ warn_control_id }} | AUDIT | Set fact for manual task warning." # noqa name[template] 17 | ansible.builtin.set_fact: 18 | warn_control_list: "{{ warn_control_list }} [{{ warn_control_id }}]" 19 | warn_count: "{{ warn_count | int + 1 }}" 20 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | ignore: | 4 | tests/ 5 | molecule/ 6 | .github/ 7 | .gitlab-ci.yml 8 | *molecule.yml 9 | rules: 10 | braces: 11 | max-spaces-inside: 1 12 | level: error 13 | brackets: 14 | max-spaces-inside: 1 15 | level: error 16 | comments: 17 | ignore-shebangs: true 18 | min-spaces-from-content: 1 # prettier compatibility 19 | comments-indentation: enable 20 | empty-lines: 21 | max: 1 22 | indentation: 23 | # Requiring 2 space indentation 24 | spaces: 2 25 | # Requiring consistent indentation within a file, either indented or not 26 | indent-sequences: consistent 27 | key-duplicates: enable 28 | line-length: disable 29 | new-line-at-end-of-file: enable 30 | new-lines: 31 | type: unix 32 | octal-values: 33 | forbid-implicit-octal: true # yamllint defaults to false 34 | forbid-explicit-octal: true 35 | trailing-spaces: enable 36 | truthy: 37 | allowed-values: ['true', 'false'] 38 | check-keys: true 39 | -------------------------------------------------------------------------------- /.github/workflows/export_badges_private.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Export Private Repo Badges 4 | 5 | # Use different minute offsets with the same hourly pattern: 6 | # Repo Group Suggested Cron Expression Explanation 7 | # Group A 0 */6 * * * Starts at top of hour 8 | # Group B 10 */6 * * * Starts at 10 after 9 | # And So On 10 | 11 | on: 12 | push: 13 | branches: 14 | - latest 15 | schedule: 16 | - cron: '0 */6 * * *' 17 | workflow_dispatch: 18 | 19 | jobs: 20 | export-badges: 21 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && startsWith(github.repository, 'ansible-lockdown/Private-')) || (github.event_name == 'push' && github.ref_name == 'latest') 22 | uses: ansible-lockdown/github_linux_IaC/.github/workflows/export_badges_private.yml@self_hosted 23 | with: 24 | # Full org/repo path passed for GitHub API calls (e.g., ansible-lockdown/Private-Windows-2016-CIS) 25 | repo_name: ${{ github.repository }} 26 | secrets: 27 | BADGE_PUSH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }} 28 | -------------------------------------------------------------------------------- /templates/etc/systemd/system/tmp.mount.j2: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1+ 2 | # 3 | # This file is part of systemd. 4 | # 5 | # systemd is free software; you can redistribute it and/or modify it 6 | # under the terms of the GNU Lesser General Public License as published by 7 | # the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | 10 | ## This file is managed by Ansible, YOUR CHANGED WILL BE LOST! 11 | 12 | [Unit] 13 | Description=Temporary Directory (/tmp) 14 | Documentation=man:hier(7) 15 | Documentation=https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems 16 | ConditionPathIsSymbolicLink=!/tmp 17 | DefaultDependencies=no 18 | Conflicts=umount.target 19 | Before=local-fs.target umount.target 20 | After=swap.target 21 | 22 | [Mount] 23 | What=tmpfs 24 | Where=/tmp 25 | Type=tmpfs 26 | Options=mode=1777,strictatime,{% if rhel9cis_rule_1_1_2_1_2 %}nodev,{% endif %}{% if rhel9cis_rule_1_1_2_1_3 %}nosuid,{% endif %}{% if rhel9cis_rule_1_1_2_1_4 %}noexec{% endif %} 27 | 28 | # Make 'systemctl enable tmp.mount' work: 29 | [Install] 30 | WantedBy=local-fs.target 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mindpoint Group - A Tyto Athene Company / Ansible Lockdown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tasks/LE_audit_setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Pre Audit Setup | Set audit package name 3 | block: 4 | - name: Pre Audit Setup | Set audit package name | 64bit 5 | when: ansible_facts.machine == "x86_64" 6 | ansible.builtin.set_fact: 7 | audit_pkg_arch_name: AMD64 8 | 9 | - name: Pre Audit Setup | Set audit package name | ARM64 10 | when: (ansible_facts.machine == "arm64" or ansible_facts.machine == "aarch64") 11 | ansible.builtin.set_fact: 12 | audit_pkg_arch_name: ARM64 13 | 14 | - name: Pre Audit Setup | Download audit binary 15 | when: get_audit_binary_method == 'download' 16 | ansible.builtin.get_url: 17 | url: "{{ audit_bin_url }}{{ audit_pkg_arch_name }}" 18 | dest: "{{ audit_bin }}" 19 | owner: root 20 | group: root 21 | checksum: "{{ audit_bin_version[audit_pkg_arch_name + '_checksum'] }}" 22 | mode: 'u+x,go-w' 23 | 24 | - name: Pre Audit Setup | Copy audit binary 25 | when: get_audit_binary_method == 'copy' 26 | ansible.builtin.copy: 27 | src: "{{ audit_bin_copy_location }}/goss-linux-{{ audit_pkg_arch_name }}" 28 | dest: "{{ audit_bin }}" 29 | owner: root 30 | group: root 31 | mode: 'u+x,go-w' 32 | -------------------------------------------------------------------------------- /tasks/parse_etc_password.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "PRELIM | 5.5.2 | 6.2.7 | 6.2.8 | 6.2.20 | Parse /etc/passwd" 4 | tags: always 5 | block: 6 | - name: "PRELIM | 5.5.2 | 6.2.7 | 6.2.8 | 6.2.20 | Parse /etc/passwd" 7 | ansible.builtin.shell: cat /etc/passwd | grep -v '^#' 8 | changed_when: false 9 | check_mode: false 10 | register: prelim_capture_passwd_file 11 | 12 | - name: "PRELIM | 5.4.2 | 7.2.8 | Split passwd entries" 13 | ansible.builtin.set_fact: 14 | prelim_captured_passwd_data: "{{ prelim_capture_passwd_file.stdout_lines | map('regex_replace', ld_passwd_regex, ld_passwd_yaml) | map('from_yaml') | list }}" 15 | loop: "{{ prelim_capture_passwd_file.stdout_lines }}" 16 | vars: 17 | ld_passwd_regex: >- 18 | ^(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*):(?P[^:]*) 19 | ld_passwd_yaml: | # pragma: allowlist secret 20 | id: >-4 21 | \g 22 | password: >-4 23 | \g 24 | uid: \g 25 | gid: \g 26 | gecos: >-4 27 | \g 28 | dir: >-4 29 | \g 30 | shell: >-4 31 | \g 32 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.3.1 | PATCH | Ensure time synchronization is in use" 4 | when: 5 | - rhel9cis_rule_2_3_1 6 | - not system_is_container 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - NIST800-53R5_AU-3 12 | - NIST800-53R5_AU-12 13 | - rule_2.3.1 14 | ansible.builtin.package: 15 | name: chrony 16 | state: present 17 | 18 | - name: "2.3.2 | PATCH | Ensure chrony is configured" 19 | when: 20 | - rhel9cis_rule_2_3_2 21 | - not system_is_container 22 | tags: 23 | - level1-server 24 | - level1-workstation 25 | - patch 26 | - rule_2.3.2 27 | - NIST800-53R5_AU-3 28 | - NIST800-53R5_AU-12 29 | ansible.builtin.template: 30 | src: etc/chrony.conf.j2 31 | dest: /etc/chrony.conf 32 | owner: root 33 | group: root 34 | mode: 'go-wx' 35 | 36 | - name: "2.3.3 | PATCH | Ensure chrony is not run as the root user" 37 | when: 38 | - rhel9cis_rule_2_3_3 39 | - not system_is_container 40 | tags: 41 | - level1-server 42 | - level1-workstation 43 | - patch 44 | - rule_2.3.3 45 | ansible.builtin.lineinfile: 46 | path: /etc/sysconfig/chronyd 47 | regexp: '^OPTIONS="(?!.* -u chrony.*)(.*)"' 48 | line: OPTIONS="\1 -u chrony" 49 | create: true 50 | backrefs: true 51 | mode: 'go-wx' 52 | -------------------------------------------------------------------------------- /templates/etc/chrony.conf.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | 3 | # Use public servers from the pool.ntp.org project. 4 | # Please consider joining the pool (http://www.pool.ntp.org/join.html). 5 | {% for server in rhel9cis_time_synchronization_servers -%} 6 | server {{ server }} {{ rhel9cis_chrony_server_options }} 7 | {% endfor %} 8 | 9 | # Record the rate at which the system clock gains/losses time. 10 | driftfile /var/lib/chrony/drift 11 | 12 | # Allow the system clock to be stepped in the first three updates 13 | # if its offset is larger than 1 second. 14 | makestep 1.0 3 15 | 16 | # Enable kernel synchronization of the real-time clock (RTC). 17 | rtcsync 18 | 19 | # Enable hardware timestamping on all interfaces that support it. 20 | #hwtimestamp * 21 | 22 | # Increase the minimum number of selectable sources required to adjust 23 | # the system clock. 24 | #minsources 2 25 | 26 | # Allow NTP client access from local network. 27 | #allow 192.168.0.0/16 28 | 29 | # Serve time even if not synchronized to a time source. 30 | #local stratum 10 31 | 32 | # Specify file containing keys for NTP authentication. 33 | keyfile /etc/chrony.keys 34 | 35 | # Get TAI-UTC offset and leap seconds from the system tz database. 36 | leapsectz right/UTC 37 | 38 | # Specify directory for log files. 39 | logdir /var/log/chrony 40 | 41 | # Select which information is logged. 42 | #log measurements statistics tracking 43 | -------------------------------------------------------------------------------- /templates/etc/ansible/compliance_facts.j2: -------------------------------------------------------------------------------- 1 | # CIS Hardening Carried out 2 | # Added as part of ansible-lockdown CIS baseline 3 | # provided by Mindpoint Group - A Tyto Athene Company 4 | 5 | [lockdown_details] 6 | # Benchmark release 7 | Benchmark_release = CIS-{{ benchmark_version }} 8 | Benchmark_run_date = {{ '%Y-%m-%d - %H:%M:%S' | ansible.builtin.strftime }} 9 | # If options set (doesn't mean it ran all controls) 10 | level_1_hardening_enabled = {{ rhel9cis_level_1 }} 11 | level_2_hardening_enabled = {{ rhel9cis_level_2 }} 12 | 13 | {% if ansible_run_tags | length > 0 %} 14 | # If tags used to stipulate run level 15 | {% if 'level1-server' in ansible_run_tags %} 16 | Level_1_Server_tag_run = true 17 | {% endif %} 18 | {% if 'level2-server' in ansible_run_tags %} 19 | Level_2_Server_tag_run = true 20 | {% endif %} 21 | {% if 'level1-workstation' in ansible_run_tags %} 22 | Level_1_workstation_tag_run = true 23 | {% endif %} 24 | {% if 'level2-workstation' in ansible_run_tags %} 25 | Level_2_workstation_tag_run = true 26 | {% endif %} 27 | {% endif %} 28 | 29 | [lockdown_audit_details] 30 | {% if run_audit %} 31 | # Audit run 32 | audit_run_date = {{ '%Y-%m-%d - %H:%M:%S' | ansible.builtin.strftime }} 33 | audit_file_local_location = {{ audit_log_dir }} 34 | {% if not audit_only %} 35 | audit_summary = {{ post_audit_results }} 36 | {% endif %} 37 | {% if fetch_audit_output %} 38 | audit_files_centralized_location = {{ audit_output_destination }} 39 | {% endif %} 40 | {% endif %} 41 | -------------------------------------------------------------------------------- /templates/etc/sysctl.d/60-netipv4_sysctl.conf.j2: -------------------------------------------------------------------------------- 1 | ## This file is managed by Ansible, YOUR CHANGES WILL BE LOST! 2 | 3 | # IPv4 Network sysctl 4 | {% if rhel9cis_rule_3_3_1 %} 5 | # CIS 3.3.1 6 | net.ipv4.ip_forward = 0 7 | {% endif %} 8 | {% if rhel9cis_rule_3_3_2 %} 9 | # CIS 3.3.2 10 | net.ipv4.conf.all.send_redirects = 0 11 | net.ipv4.conf.default.send_redirects = 0 12 | {% endif %} 13 | {% if rhel9cis_rule_3_3_3 %} 14 | # CIS 3.3.3 15 | net.ipv4.icmp_ignore_bogus_error_responses = 1 16 | {% endif %} 17 | {% if rhel9cis_rule_3_3_4 %} 18 | # CIS 3.3.4 19 | net.ipv4.icmp_echo_ignore_broadcasts = 1 20 | {% endif %} 21 | {% if rhel9cis_rule_3_3_5 %} 22 | # CIS 3.3.5 23 | net.ipv4.conf.all.accept_redirects = 0 24 | net.ipv4.conf.default.accept_redirects = 0 25 | {% endif %} 26 | {% if rhel9cis_rule_3_3_6 %} 27 | # CIS 3.3.6 28 | net.ipv4.conf.all.secure_redirects = 0 29 | net.ipv4.conf.default.secure_redirects = 0 30 | {% endif %} 31 | {% if rhel9cis_rule_3_3_7 %} 32 | # CIS 3.3.7 33 | net.ipv4.conf.all.rp_filter = 1 34 | net.ipv4.conf.default.rp_filter = 1 35 | {% endif %} 36 | {% if rhel9cis_rule_3_3_8 %} 37 | # CIS 3.3.8 38 | net.ipv4.conf.all.accept_source_route = 0 39 | net.ipv4.conf.default.accept_source_route = 0 40 | {% endif %} 41 | {% if rhel9cis_rule_3_3_9 %} 42 | # CIS 3.3.9 43 | net.ipv4.conf.all.log_martians = 1 44 | net.ipv4.conf.default.log_martians = 1 45 | {% endif %} 46 | {% if rhel9cis_rule_3_3_10 %} 47 | # CIS 3.3.10 48 | net.ipv4.tcp_syncookies = 1 49 | {% endif %} 50 | -------------------------------------------------------------------------------- /tasks/section_6/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 6.1 | Configure Integrity Checking" 4 | ansible.builtin.import_tasks: 5 | file: cis_6.1.x.yml 6 | 7 | - name: "SECTION | 6.2.1 | Configure systemd-journald service" 8 | when: rhel9cis_syslog == 'journald' 9 | ansible.builtin.import_tasks: 10 | file: cis_6.2.1.x.yml 11 | 12 | - name: "SECTION | 6.2.2.1.x | Configure journald-remote" 13 | when: rhel9cis_syslog == 'journald' 14 | ansible.builtin.import_tasks: 15 | file: cis_6.2.2.1.x.yml 16 | 17 | - name: "SECTION | 6.2.2.x | Configure journald" 18 | when: rhel9cis_syslog == 'journald' 19 | ansible.builtin.import_tasks: 20 | file: cis_6.2.2.x.yml 21 | 22 | - name: "SECTION | 6.2.3 | Configure rsyslog" 23 | when: 24 | - rhel9cis_syslog == 'rsyslog' 25 | - rhel9cis_rsyslog_ansiblemanaged 26 | ansible.builtin.import_tasks: 27 | file: cis_6.2.3.x.yml 28 | 29 | - name: "SECTION | 6.2.4.1 | Configure Logfiles" 30 | ansible.builtin.import_tasks: 31 | file: cis_6.2.4.1.yml 32 | 33 | - name: "SECTION | 6.3.1 | Configure auditd Service" 34 | ansible.builtin.import_tasks: 35 | file: cis_6.3.1.x.yml 36 | 37 | - name: "SECTION | 6.3.2 | Configure Data Retention" 38 | ansible.builtin.import_tasks: 39 | file: cis_6.3.2.x.yml 40 | 41 | - name: "SECTION | 6.3.3 | Configure auditd Rules" 42 | ansible.builtin.import_tasks: 43 | file: cis_6.3.3.x.yml 44 | 45 | - name: "SECTION | 6.3.4 | Configure auditd File Access" 46 | ansible.builtin.import_tasks: 47 | file: cis_6.3.4.x.yml 48 | -------------------------------------------------------------------------------- /tasks/post_remediation_audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Post Audit | Run post_remediation {{ benchmark }} audit # noqa name[template] 4 | ansible.builtin.shell: "umask 0022 && {{ audit_conf_dir }}/run_audit.sh -v {{ audit_vars_path }} -f {{ audit_format }} -m {{ audit_max_concurrent }} -o {{ post_audit_outfile }} -g \"{{ group_names }}\"" # noqa yaml[line-length] 5 | changed_when: true 6 | environment: 7 | AUDIT_BIN: "{{ audit_bin }}" 8 | AUDIT_CONTENT_LOCATION: "{{ audit_conf_dest | default('/opt') }}" 9 | AUDIT_FILE: goss.yml 10 | 11 | - name: Post Audit | Capture audit data if json format 12 | when: audit_format == "json" 13 | block: 14 | - name: Post Audit | Capture audit data if json format 15 | ansible.builtin.shell: grep -E '"summary-line.*Count:.*Failed' "{{ post_audit_outfile }}" | cut -d'"' -f4 16 | changed_when: false 17 | register: post_audit_summary 18 | 19 | - name: Post Audit | Set Fact for audit summary 20 | ansible.builtin.set_fact: 21 | post_audit_results: "{{ post_audit_summary.stdout }}" 22 | 23 | - name: Post Audit | Capture audit data if documentation format 24 | when: audit_format == "documentation" 25 | block: 26 | - name: Post Audit | Capture audit data if documentation format 27 | ansible.builtin.shell: tail -2 "{{ post_audit_outfile }}" | tac | tr '\n' ' ' 28 | changed_when: false 29 | register: post_audit_summary 30 | 31 | - name: Post Audit | Set Fact for audit summary 32 | ansible.builtin.set_fact: 33 | post_audit_results: "{{ post_audit_summary.stdout }}" 34 | -------------------------------------------------------------------------------- /tasks/post.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: POST | Gather the package facts after remediation 4 | tags: always 5 | ansible.builtin.package_facts: 6 | manager: auto 7 | 8 | - name: POST | Update sysctl 9 | when: 10 | - rhel9cis_sysctl_update 11 | - not system_is_container 12 | - "'procps-ng' in ansible_facts.packages" 13 | ansible.builtin.template: 14 | src: "etc/sysctl.d/{{ item }}.j2" 15 | dest: "/etc/sysctl.d/{{ item }}" 16 | owner: root 17 | group: root 18 | mode: 'go-rwx' 19 | notify: Reload sysctl 20 | loop: 21 | - 60-kernel_sysctl.conf 22 | - 60-disable_ipv6.conf 23 | - 60-netipv4_sysctl.conf 24 | - 60-netipv6_sysctl.conf 25 | 26 | - name: Flush handlers 27 | ansible.builtin.meta: flush_handlers 28 | 29 | - name: POST | reboot system if changes require it and not skipped 30 | when: change_requires_reboot 31 | tags: 32 | - always 33 | vars: 34 | warn_control_id: Reboot_required 35 | block: 36 | - name: POST | Reboot system if changes require it and not skipped 37 | when: not skip_reboot 38 | ansible.builtin.reboot: 39 | 40 | - name: POST | Warning a reboot required but skip option set 41 | when: skip_reboot 42 | ansible.builtin.debug: 43 | msg: "Warning!! changes have been made that require a reboot to be implemented but skip reboot was set - Can affect compliance check results" 44 | changed_when: true 45 | 46 | - name: "POST | Warning a reboot required but skip option set | warning count" 47 | when: skip_reboot 48 | ansible.builtin.import_tasks: 49 | file: warning_facts.yml 50 | -------------------------------------------------------------------------------- /tasks/section_4/cis_4.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "4.2.1 | AUDIT | Ensure firewalld drops unnecessary services and ports" 4 | when: rhel9cis_rule_4_2_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - manual 9 | - audit 10 | - rule_4.2.1 11 | - NIST800-55_CA-9 12 | block: 13 | - name: "4.2.1 | AUDIT | Ensure firewalld drops unnecessary services and ports | Get list of services and ports" 14 | ansible.builtin.shell: "firewall-cmd --get-active-zones | awk '!/:/ {print $1}' | while read ZN; do firewall-cmd --list-all --zone=$ZN; done" 15 | changed_when: false 16 | failed_when: false 17 | check_mode: false 18 | register: discovered_services_and_ports 19 | 20 | - name: "4.2.1 | AUDIT | Ensure firewalld drops unnecessary services and ports | Show services and ports" 21 | ansible.builtin.debug: 22 | msg: 23 | - "The items below are the services and ports that are accepted, please correct as needed" 24 | - "{{ discovered_services_and_ports.stdout_lines }}" 25 | 26 | - name: "4.2.2 | PATCH | Ensure firewalld loopback traffic is configured | firewalld" 27 | when: rhel9cis_rule_4_2_2 28 | tags: 29 | - level1-server 30 | - level1-workstation 31 | - patch 32 | - nftables 33 | - rule_4.2.2 34 | - NIST800-55_CA-9 35 | ansible.posix.firewalld: 36 | rich_rule: "{{ item }}" 37 | zone: "{{ rhel9cis_default_zone }}" 38 | permanent: true 39 | immediate: true 40 | state: enabled 41 | loop: 42 | - rule family="ipv4" source address="127.0.0.1" destination not address="127.0.0.1" drop 43 | - rule family="ipv6" source address="::1" destination not address="::1" drop 44 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.4.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.4.3.1 | PATCH | Ensure nologin is not listed in /etc/shells" 4 | when: rhel9cis_rule_5_4_3_1 5 | tags: 6 | - level2-server 7 | - level2-workstation 8 | - patch 9 | - shells 10 | - rule_5.4.3.1 11 | - NIST800-53R5_CM-1 12 | - NIST800-53R5_CM-2 13 | - NIST800-53R5_CM-6 14 | - NIST800-53R5_CM-7 15 | - NIST800-53R5_IA-5 16 | ansible.builtin.replace: 17 | path: /etc/shells 18 | regexp: nologin 19 | replace: "" 20 | 21 | - name: "5.4.3.2 | PATCH | Ensure default user shell timeout is configured" 22 | when: rhel9cis_rule_5_4_3_2 23 | tags: 24 | - level1-server 25 | - level1-workstation 26 | - patch 27 | - shell 28 | - rule_5.4.3.2 29 | ansible.builtin.blockinfile: 30 | path: "{{ item.path }}" 31 | state: "{{ item.state }}" 32 | marker: "# {mark} - CIS benchmark - Ansible-lockdown" 33 | create: true 34 | mode: 'go-wx' 35 | block: | 36 | TMOUT={{ rhel9cis_shell_session_timeout }} 37 | readonly TMOUT 38 | export TMOUT 39 | loop: 40 | - { path: "{{ rhel9cis_shell_session_file }}", state: present } 41 | - { path: /etc/profile, state: "{{ (rhel9cis_shell_session_file == '/etc/profile') | ternary('present', 'absent') }}" } 42 | 43 | - name: "5.4.3.3 | PATCH | Ensure default user umask is configured" 44 | when: rhel9cis_rule_5_4_3_3 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - patch 49 | - umask 50 | - rule_5.4.3.3 51 | - NIST800-53R5_AC-3 52 | - NIST800-53R5_MP-2 53 | ansible.builtin.replace: 54 | path: "{{ item.path }}" 55 | regexp: (?i)(umask\s+\d*) 56 | replace: '{{ item.line }} {{ rhel9cis_bash_umask }}' 57 | loop: 58 | - { path: '/etc/profile', line: 'umask' } 59 | - { path: '/etc/login.defs', line: 'UMASK' } 60 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.3.1.1 | PATCH | Ensure latest version of pam is installed" 4 | when: 5 | - rhel9cis_rule_5_3_1_1 6 | - ansible_facts.packages['pam'][0]['version'] is version('1.5.1-19', '<') or 7 | "'pam' not in ansible_facts.packages" 8 | tags: 9 | - level1-server 10 | - level1-workstation 11 | - patch 12 | - pam 13 | - rule_5.3.1.1 14 | ansible.builtin.package: 15 | name: pam 16 | state: latest 17 | 18 | - name: "5.3.1.2 | PATCH | Ensure latest version of authselect is installed" 19 | when: 20 | - rhel9cis_rule_5_3_1_2 21 | - rhel9cis_authselect_pkg_update 22 | - ansible_facts.packages['authselect'][0]['version'] is version('1.2.6-2', '<') or 23 | "'authselect' not in ansible_facts.packages" 24 | tags: 25 | - level1-server 26 | - level1-workstation 27 | - patch 28 | - pam 29 | - rule_5.3.1.2 30 | block: 31 | - name: "5.3.1.2 | PATCH | Ensure latest version of authselect is installed | Patch" 32 | ansible.builtin.package: 33 | name: authselect 34 | state: latest 35 | register: discovered_authselect_updated 36 | 37 | - name: "5.3.1.2 | AUDIT | Ensure latest version of authselect is installed | Patch" 38 | when: discovered_authselect_updated.changed # noqa no-handler 39 | ansible.builtin.set_fact: 40 | authselect_update: OK 41 | 42 | - name: "5.3.1.3 | PATCH | Ensure libpwquality is installed" 43 | when: 44 | - rhel9cis_rule_5_3_1_3 45 | - ansible_facts.packages['libpwquality'][0]['version'] is version('1.4.4-8', '<') or 46 | "'libpwquality' not in ansible_facts.packages" 47 | tags: 48 | - level1-server 49 | - level1-workstation 50 | - patch 51 | - pam 52 | - rule_5.3.1.3 53 | ansible.builtin.package: 54 | name: libpwquality 55 | state: latest 56 | -------------------------------------------------------------------------------- /tasks/section_4/cis_4.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "4.1.1 | PATCH | Ensure nftables is installed" 4 | when: 5 | - rhel9cis_rule_4_1_1 6 | - rhel9cis_firewall == 'nftables' 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - nftables 12 | - rule_4.1.1 13 | - NIST800-53R5_CA-9 14 | ansible.builtin.package: 15 | name: 16 | - nftables 17 | state: present 18 | 19 | - name: "4.1.2 | PATCH | Ensure a single firewall configuration utility is in use" 20 | when: rhel9cis_rule_4_1_2 21 | tags: 22 | - level1-server 23 | - level1-workstation 24 | - patch 25 | - firewalld 26 | - nftables 27 | - rule_4.1.2 28 | block: 29 | - name: "4.1.2 | PATCH | Ensure a single firewall configuration utility is in use | nftables" 30 | when: 31 | - item in ansible_facts.packages 32 | - rhel9cis_firewall == 'nftables' 33 | ansible.builtin.systemd: 34 | name: "{{ item }}" 35 | masked: true 36 | loop: 37 | - firewalld 38 | 39 | - name: "4.1.2 | PATCH | Ensure a single firewall configuration utility is in use | firewalld" 40 | when: 41 | - item in ansible_facts.packages 42 | - rhel9cis_firewall == 'firewalld' 43 | ansible.builtin.systemd: 44 | name: "{{ item }}" 45 | masked: true 46 | loop: 47 | - nftables 48 | 49 | - name: "4.1.2 | PATCH | Ensure a single firewall configuration utility is in use | package installed" 50 | ansible.builtin.package: 51 | name: "{{ rhel9cis_firewall }}" 52 | state: installed 53 | 54 | - name: "4.1.2 | PATCH | Ensure a single firewall configuration utility is in use | {{ rhel9cis_firewall }} started and enabled" # noqa name[template] 55 | ansible.builtin.systemd: 56 | name: "{{ rhel9cis_firewall }}" 57 | enabled: true 58 | state: started 59 | -------------------------------------------------------------------------------- /tasks/fetch_audit_output.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Stage to copy audit output to a centralised location 4 | 5 | - name: "POST | FETCH | Fetch files and copy to controller" 6 | when: audit_output_collection_method == "fetch" 7 | ansible.builtin.fetch: 8 | src: "{{ item }}" 9 | dest: "{{ audit_output_destination }}" 10 | flat: true 11 | changed_when: true 12 | failed_when: false 13 | register: discovered_audit_fetch_state 14 | loop: 15 | - "{{ pre_audit_outfile }}" 16 | - "{{ post_audit_outfile }}" 17 | become: false 18 | 19 | # Added this option for continuity but could be changed by adjusting the variable audit_conf_dest 20 | # Allowing backup to one location 21 | - name: "POST | FETCH | Copy files to location available to managed node" 22 | when: audit_output_collection_method == "copy" 23 | ansible.builtin.copy: 24 | src: "{{ item }}" 25 | dest: "{{ audit_output_destination }}" 26 | mode: 'u-x,go-wx' 27 | flat: true 28 | failed_when: false 29 | register: discovered_audit_copy_state 30 | loop: 31 | - "{{ pre_audit_outfile }}" 32 | - "{{ post_audit_outfile }}" 33 | 34 | - name: "POST | FETCH | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 35 | when: 36 | - (audit_output_collection_method == "fetch" and not discovered_audit_fetch_state.changed) or 37 | (audit_output_collection_method == "copy" and not discovered_audit_copy_state.changed) 38 | block: 39 | - name: "POST | FETCH | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 40 | ansible.builtin.debug: 41 | msg: "Warning!! Unable to write to localhost {{ audit_output_destination }} for audit file copy" 42 | 43 | - name: "POST | FETCH | Fetch files and copy to controller | Warning if issues with fetch_audit_files" 44 | vars: 45 | warn_control_id: "FETCH_AUDIT_FILES" 46 | ansible.builtin.import_tasks: 47 | file: warning_facts.yml 48 | -------------------------------------------------------------------------------- /tasks/section_5/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Access, Authentication, and Authorization 4 | 5 | - name: "SECTION | 5.1 | Configure SSH Server" 6 | when: 7 | - "'openssh-server' in ansible_facts.packages" 8 | - rhel9cis_section5_1 9 | ansible.builtin.import_tasks: 10 | file: cis_5.1.x.yml 11 | 12 | - name: "SECTION | 5.2 | Configure privilege escalation" 13 | when: 14 | - rhel9cis_section5_2 15 | ansible.builtin.import_tasks: 16 | file: cis_5.2.x.yml 17 | 18 | - name: "SECTION | 5.3" 19 | when: 20 | - rhel9cis_section5_3 21 | block: 22 | - name: "SECTION | 5.3.1.x | Configure PAM software packages" 23 | ansible.builtin.import_tasks: 24 | file: cis_5.3.1.x.yml 25 | 26 | - name: "SECTION | 5.3.2.x | Configure authselect" 27 | ansible.builtin.import_tasks: 28 | file: cis_5.3.2.x.yml 29 | 30 | - name: "SECTION | 5.3.3.1.x | Configure pam_faillock module" 31 | ansible.builtin.import_tasks: 32 | file: cis_5.3.3.1.x.yml 33 | 34 | - name: "SECTION | 5.3.3.2.x | Configure pam_pwquality module" 35 | ansible.builtin.import_tasks: 36 | file: cis_5.3.3.2.x.yml 37 | 38 | - name: "SECTION | 5.3.3.3.x | Configure pam_pwhistory module" 39 | ansible.builtin.import_tasks: 40 | file: cis_5.3.3.3.x.yml 41 | 42 | - name: "SECTION | 5.3.3.4.x | Configure pam_unix module" 43 | ansible.builtin.import_tasks: 44 | file: cis_5.3.3.4.x.yml 45 | 46 | - name: "SECTION | 5.4" 47 | when: 48 | - rhel9cis_section5_4 49 | block: 50 | - name: "SECTION | 5.4.1.x | Configure shadow password suite parameters" 51 | ansible.builtin.import_tasks: 52 | file: cis_5.4.1.x.yml 53 | 54 | - name: "SECTION | 5.4.2.x | Configure root and system accounts and environment" 55 | ansible.builtin.import_tasks: 56 | file: cis_5.4.2.x.yml 57 | 58 | - name: "SECTION | 5.4.3.x | Configure user default environment" 59 | ansible.builtin.import_tasks: 60 | file: cis_5.4.3.x.yml 61 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.2.1 | PATCH | Ensure ftp client is not installed" 4 | when: 5 | - not rhel9cis_ftp_client 6 | - rhel9cis_rule_2_2_1 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - automated 11 | - patch 12 | - ftp 13 | - NIST800-53R5_CM-7 14 | - rule_2.2.1 15 | ansible.builtin.package: 16 | name: ftp 17 | state: absent 18 | 19 | - name: "2.2.2 | PATCH | Ensure ldap client is not installed" 20 | when: 21 | - not rhel9cis_openldap_clients_required 22 | - rhel9cis_rule_2_2_2 23 | tags: 24 | - level2-server 25 | - level2-workstation 26 | - automated 27 | - patch 28 | - ldap 29 | - NIST800-53R5_CM-7 30 | - rule_2.2.2 31 | ansible.builtin.package: 32 | name: openldap-clients 33 | state: absent 34 | 35 | - name: "2.2.3 | PATCH | Ensure nis client is not installed" 36 | when: 37 | - not rhel9cis_ypbind_required 38 | - rhel9cis_rule_2_2_3 39 | tags: 40 | - level1-server 41 | - level1-workstation 42 | - automated 43 | - patch 44 | - nis 45 | - NIST800-53R5_CM-7 46 | - rule_2.2.3 47 | ansible.builtin.package: 48 | name: ypbind 49 | state: absent 50 | 51 | - name: "2.2.4 | PATCH | Ensure telnet client is not installed" 52 | when: 53 | - not rhel9cis_telnet_required 54 | - rhel9cis_rule_2_2_4 55 | tags: 56 | - level1-server 57 | - level1-workstation 58 | - automated 59 | - patch 60 | - telnet 61 | - NIST800-53R5_CM-7 62 | - rule_2.2.4 63 | ansible.builtin.package: 64 | name: telnet 65 | state: absent 66 | 67 | - name: "2.2.5 | PATCH | Ensure TFTP client is not installed" 68 | when: 69 | - not rhel9cis_tftp_client 70 | - rhel9cis_rule_2_2_5 71 | tags: 72 | - level1-server 73 | - level1-workstation 74 | - automated 75 | - patch 76 | - tftp 77 | - NIST800-53R5_CM-7 78 | - rule_2.2.5 79 | ansible.builtin.package: 80 | name: tftp 81 | state: absent 82 | -------------------------------------------------------------------------------- /vars/audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | #### Audit Configuration Settings #### 4 | 5 | # Timeout for those cmds that take longer to run where timeout set 6 | audit_cmd_timeout: 120000 7 | 8 | # if get_audit_binary_method == download change accordingly 9 | audit_bin_url: "https://github.com/goss-org/goss/releases/download/{{ audit_bin_version.release }}/goss-linux-" 10 | 11 | ### Goss Audit Benchmark file ### 12 | ## managed by the control audit_content 13 | # git 14 | audit_file_git: "https://github.com/ansible-lockdown/{{ benchmark }}-Audit.git" 15 | audit_git_version: "benchmark_{{ benchmark_version }}" 16 | 17 | ## Goss configuration information 18 | # Where the goss audit configuration will be stored - NOTE benchmark-audit is expected 19 | audit_conf_dir: "{{ audit_conf_dest | default('/opt') }}/{{ benchmark }}-Audit" 20 | 21 | # If changed these can affect other products 22 | pre_audit_outfile: "{{ audit_log_dir }}/{{ ansible_facts.hostname }}-{{ benchmark }}-{{ benchmark_version }}_pre_scan_{{ ansible_facts.date_time.epoch }}.{{ audit_format }}" 23 | post_audit_outfile: "{{ audit_log_dir }}/{{ ansible_facts.hostname }}-{{ benchmark }}-{{ benchmark_version }}_post_scan_{{ ansible_facts.date_time.epoch }}.{{ audit_format }}" 24 | 25 | ## The following should not need changing 26 | 27 | ### Audit binary settings ### 28 | audit_bin_version: 29 | release: v0.4.8 30 | AMD64_checksum: 'sha256:85d00b7bba5f175bec95de7dfe1f71f8f25204914aad4c6f03c8457868eb6e2f' 31 | ARM64_checksum: 'sha256:bca8c898bfd35b94c51455ece6193c95e2cd7b2b183ac2047b2d76291e73e47d' 32 | audit_bin_path: /usr/local/bin/ 33 | audit_bin: "{{ audit_bin_path }}goss" 34 | audit_format: json 35 | 36 | audit_vars_path: "{{ audit_conf_dir }}/vars/{{ ansible_facts.hostname }}.yml" 37 | audit_results: | 38 | The{% if not audit_only %} pre remediation{% endif %} audit results are: {{ pre_audit_results }} 39 | {% if not audit_only %}The post remediation audit results are: {{ post_audit_results }}{% endif %} 40 | 41 | Full breakdown can be found in {{ audit_log_dir }} 42 | -------------------------------------------------------------------------------- /tasks/section_1/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "SECTION | 1.1.1.x | Disable unused filesystems" 4 | ansible.builtin.import_tasks: 5 | file: cis_1.1.1.x.yml 6 | 7 | - name: "SECTION | 1.1.2.1.x | Configure /tmp" 8 | ansible.builtin.import_tasks: 9 | file: cis_1.1.2.1.x.yml 10 | 11 | - name: "SECTION | 1.1.2.2.x | Configure /dev/shm" 12 | ansible.builtin.import_tasks: 13 | file: cis_1.1.2.2.x.yml 14 | 15 | - name: "SECTION | 1.1.2.3.x | Configure /home" 16 | ansible.builtin.import_tasks: 17 | file: cis_1.1.2.3.x.yml 18 | 19 | - name: "SECTION | 1.1.2.4.x | Configure /var" 20 | ansible.builtin.import_tasks: 21 | file: cis_1.1.2.4.x.yml 22 | 23 | - name: "SECTION | 1.1.2.5.x | Configure /var/tmp" 24 | ansible.builtin.import_tasks: 25 | file: cis_1.1.2.5.x.yml 26 | 27 | - name: "SECTION | 1.1.2.6.x | Configure /var/log" 28 | ansible.builtin.import_tasks: 29 | file: cis_1.1.2.6.x.yml 30 | 31 | - name: "SECTION | 1.1.2.7.x | Configure /var/log/audit" 32 | ansible.builtin.import_tasks: 33 | file: cis_1.1.2.7.x.yml 34 | 35 | - name: "SECTION | 1.2.1.x | Configure Package Repositories" 36 | ansible.builtin.import_tasks: 37 | file: cis_1.2.1.x.yml 38 | 39 | - name: "SECTION | 1.2.2.x | Configure Package Updates" 40 | ansible.builtin.import_tasks: 41 | file: cis_1.2.2.x.yml 42 | 43 | - name: "SECTION | 1.3.1 | Configure SELinux" 44 | ansible.builtin.import_tasks: 45 | file: cis_1.3.1.x.yml 46 | 47 | - name: "SECTION | 1.4 | Configure Bootloader" 48 | ansible.builtin.import_tasks: 49 | file: cis_1.4.x.yml 50 | 51 | - name: "SECTION | 1.5 | Additional Process Hardening" 52 | ansible.builtin.import_tasks: 53 | file: cis_1.5.x.yml 54 | 55 | - name: "SECTION | 1.6 | Configure system wide crypto policy" 56 | ansible.builtin.import_tasks: 57 | file: cis_1.6.x.yml 58 | 59 | - name: "SECTION | 1.7 | Command Line Warning Banners" 60 | ansible.builtin.import_tasks: 61 | file: cis_1.7.x.yml 62 | 63 | - name: "SECTION | 1.8 | Gnome Display Manager" 64 | when: rhel9cis_display_manager == 'gdm' 65 | ansible.builtin.import_tasks: 66 | file: cis_1.8.x.yml 67 | -------------------------------------------------------------------------------- /vars/is_container.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # File to skip controls if container 4 | # Based on standard image no changes 5 | # it expected all pkgs required for the container are alreday installed 6 | 7 | ## controls 8 | 9 | # Firewall 10 | rhel9cis_firewall: None 11 | 12 | # SElinux 13 | rhel9cis_selinux_disable: true 14 | 15 | ## Related individual rules 16 | # Aide 17 | rhel9cis_rule_1_4_1: false 18 | rhel9cis_rule_1_4_2: false 19 | 20 | # auditd 21 | rhel9cis_rule_4_1_1_1: false 22 | rhel9cis_rule_4_1_2_1: false 23 | rhel9cis_rule_4_1_2_2: false 24 | rhel9cis_rule_4_1_2_3: false 25 | 26 | # time sync 27 | rhel9cis_rule_2_2_1_1: false 28 | rhel9cis_rule_2_2_1_2: false 29 | 30 | # cron 31 | rhel9cis_rule_5_1_1: false 32 | rhel9cis_rule_5_1_2: false 33 | rhel9cis_rule_5_1_3: false 34 | rhel9cis_rule_5_1_4: false 35 | rhel9cis_rule_5_1_5: false 36 | rhel9cis_rule_5_1_6: false 37 | rhel9cis_rule_5_1_7: false 38 | rhel9cis_rule_5_1_8: false 39 | 40 | # crypto 41 | rhel9cis_rule_1_10: false 42 | 43 | # grub 44 | rhel9cis_rule_1_5_1: false 45 | rhel9cis_rule_1_5_2: false 46 | rhel9cis_rule_1_5_3: false 47 | 48 | ## mounts 49 | # /tmp 50 | rhel9cis_rule_1_1_2: false 51 | rhel9cis_rule_1_1_3: false 52 | rhel9cis_rule_1_1_4: false 53 | rhel9cis_rule_1_1_5: false 54 | # /var 55 | rhel9cis_rule_1_1_6: false 56 | # /var/tmp 57 | rhel9cis_rule_1_1_7: false 58 | rhel9cis_rule_1_1_8: false 59 | rhel9cis_rule_1_1_9: false 60 | rhel9cis_rule_1_1_10: false 61 | # /var/log 62 | rhel9cis_rule_1_1_11: false 63 | # /var/log/audit 64 | rhel9cis_rule_1_1_12: false 65 | # /home 66 | rhel9cis_rule_1_1_13: false 67 | rhel9cis_rule_1_1_14: false 68 | # /dev/shm 69 | rhel9cis_rule_1_1_15: false 70 | rhel9cis_rule_1_1_16: false 71 | rhel9cis_rule_1_1_17: false 72 | # usb-storage 73 | rhel9cis_rule_1_1_23: false 74 | 75 | # logging 76 | rhel9cis_rule_4_2_1_1: false 77 | rhel9cis_rule_4_2_1_2: false 78 | rhel9cis_rule_4_2_1_3: false 79 | rhel9cis_rule_4_2_1_4: false 80 | rhel9cis_rule_4_2_1_5: false 81 | rhel9cis_rule_4_2_1_6: false 82 | rhel9cis_rule_4_2_2_1: false 83 | rhel9cis_rule_4_2_2_2: false 84 | rhel9cis_rule_4_2_2_3: false 85 | 86 | # systemd 87 | 88 | # Users/passwords/accounts 89 | rhel9cis_rule_5_5_2: false 90 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.2.4.1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.2.4.1 | PATCH | Ensure access to all logfiles has been configured" 4 | when: rhel9cis_rule_6_2_4_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - patch 9 | - logfiles 10 | - rule_6.2.4.1 11 | - NIST800-53R5_AC-3 12 | - NIST800-53R5_MP-2 13 | block: 14 | - name: "6.2.4.1 | AUDIT | Ensure access to all logfiles has been configured | find log files" 15 | ansible.builtin.shell: find /var/log/ -type f -exec ls {} \; 16 | changed_when: false 17 | failed_when: false 18 | register: discovered_logfiles 19 | 20 | - name: "6.2.4.1 | PATCH | Ensure access to all logfiles has been configured | change permissions SSSD min 660" 21 | when: 22 | - discovered_logfiles.stdout_lines | length > 0 23 | - item is match("/var/log/(gdm|sssd)") 24 | ansible.builtin.file: 25 | path: "{{ item }}" 26 | mode: 'ug-x,o-rwx' 27 | failed_when: discovered_logfile_list.state not in '[ file, absent ]' 28 | register: discovered_logfile_list 29 | loop: "{{ discovered_logfiles.stdout_lines }}" 30 | 31 | - name: "6.2.4.1 | PATCH | Ensure access to all logfiles has been configured | change permissions tmp min 664" 32 | when: 33 | - discovered_logfiles.stdout_lines | length > 0 34 | - item is match("/var/log/((u|b|w)tmp*|lastlog)") 35 | ansible.builtin.file: 36 | path: "{{ item }}" 37 | mode: 'ug-x,o-wx' 38 | failed_when: discovered_logfile_list.state not in '[ file, absent ]' 39 | register: discovered_logfile_list 40 | loop: "{{ discovered_logfiles.stdout_lines }}" 41 | 42 | - name: "6.2.4.1 | PATCH | Ensure access to all logfiles has been configured | change permissions else all 640" 43 | when: 44 | - discovered_logfiles.stdout_lines | length > 0 45 | - item is not match("/var/log/((u|b|w)tmp*|lastlog|sssd)") 46 | ansible.builtin.file: 47 | path: "{{ item }}" 48 | mode: 'u-x,g-wx,o-rwx' 49 | failed_when: discovered_logfile_list.state not in '[ file, absent ]' 50 | register: discovered_logfile_list 51 | loop: "{{ discovered_logfiles.stdout_lines }}" 52 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ##### CI for use by github no need for action to be added 3 | ##### Inherited 4 | ci: 5 | autofix_prs: false 6 | skip: [detect-aws-credentials, ansible-lint ] 7 | 8 | repos: 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v6.0.0 11 | hooks: 12 | # Safety 13 | - id: detect-aws-credentials 14 | name: Detect AWS Credentials 15 | - id: detect-private-key 16 | name: Detect Private Keys 17 | 18 | # git checks 19 | - id: check-merge-conflict 20 | name: Check for merge conflicts 21 | - id: check-added-large-files 22 | name: Check for Large files 23 | - id: check-case-conflict 24 | name: Check case conflict 25 | 26 | # General checks 27 | - id: trailing-whitespace 28 | name: Trim Trailing Whitespace 29 | description: This hook trims trailing whitespace. 30 | entry: trailing-whitespace-fixer 31 | language: python 32 | types: [text] 33 | args: [--markdown-linebreak-ext=md] 34 | - id: end-of-file-fixer 35 | name: Ensure line at end of file 36 | 37 | # Scan for passwords 38 | - repo: https://github.com/Yelp/detect-secrets 39 | rev: v1.5.0 40 | hooks: 41 | - id: detect-secrets 42 | 43 | - repo: https://github.com/gitleaks/gitleaks 44 | rev: v8.30.0 45 | hooks: 46 | - id: gitleaks 47 | 48 | - repo: https://github.com/ansible-community/ansible-lint 49 | rev: v25.12.2 50 | hooks: 51 | - id: ansible-lint 52 | name: Ansible-lint 53 | description: This hook runs ansible-lint. 54 | entry: python3 -m ansiblelint --force-color site.yml -c .ansible-lint 55 | language: python 56 | # do not pass files to ansible-lint, see: 57 | # https://github.com/ansible/ansible-lint/issues/611 58 | pass_filenames: false 59 | always_run: true 60 | # additional_dependencies: 61 | # https://github.com/pre-commit/pre-commit/issues/1526 62 | # If you want to use specific version of ansible-core or ansible, feel 63 | # free to override `additional_dependencies` in your own hook config 64 | # file. 65 | # - ansible-core>=2.10.1 66 | 67 | - repo: https://github.com/adrienverge/yamllint.git 68 | rev: v1.37.1 # or higher tag 69 | hooks: 70 | - id: yamllint 71 | name: Check YAML Lint 72 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.5.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.5.1 | PATCH | Ensure address space layout randomization (ASLR) is enabled" 4 | when: rhel9cis_rule_1_5_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - patch 9 | - sysctl 10 | - rule_1.5.1 11 | - NIST800-53R5_CM-6 12 | - NIST800-53R5_CM-6.1 13 | block: 14 | - name: "1.5.1 | PATCH | Ensure address space layout randomization (ASLR) is enabled" 15 | ansible.builtin.set_fact: 16 | rhel9cis_sysctl_update: true 17 | 18 | - name: "1.5.1 | PATCH | Ensure address space layout randomization (ASLR) is enabled" 19 | ansible.builtin.debug: 20 | msg: "Control being set via Handler 'update sysctl' which writes to /etc/sysctl.d/60-kernel_sysctl.conf" 21 | 22 | - name: "1.5.2 | PATCH | Ensure ptrace_scope is restricted" 23 | when: rhel9cis_rule_1_5_2 24 | tags: 25 | - level1-server 26 | - level1-workstation 27 | - patch 28 | - sysctl 29 | - rule_1.5.2 30 | block: 31 | - name: "1.5.2 | PATCH | Ensure ptrace_scope is restricted" 32 | ansible.builtin.set_fact: 33 | rhel9cis_sysctl_update: true 34 | 35 | - name: "1.5.2 | PATCH | Ensure ptrace_scope is restricted" 36 | ansible.builtin.debug: 37 | msg: "Control being set via Handler 'update sysctl' which writes to /etc/sysctl.d/60-kernel_sysctl.conf" 38 | 39 | - name: "1.5.3 | PATCH | Ensure core dump backtraces are disabled" 40 | when: rhel9cis_rule_1_5_3 41 | tags: 42 | - level1-server 43 | - level1-workstation 44 | - patch 45 | - sysctl 46 | - rule_1.5.3 47 | - NIST800-53R5_CM-6b 48 | ansible.builtin.lineinfile: 49 | path: /etc/systemd/coredump.conf 50 | regexp: '(?#)^ProcessSizeMax\s*=\s*.*[1-9].*$' 51 | line: 'ProcessSizeMax=0' 52 | 53 | - name: "1.5.4 | PATCH | Ensure core dump storage is disabled" 54 | when: 55 | - rhel9cis_rule_1_5_4 56 | - prelim_systemd_coredump.stat.exists 57 | tags: 58 | - level1-server 59 | - level1-workstation 60 | - patch 61 | - rule_1.5.4 62 | ansible.builtin.lineinfile: 63 | path: /etc/systemd/coredump.conf 64 | regexp: '^Storage\s*=\s*(?!none).*' 65 | line: 'Storage=none' 66 | notify: Systemd daemon reload 67 | -------------------------------------------------------------------------------- /.github/workflows/benchmark_tracking_controller.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # GitHub schedules all cron jobs in UTC. 4 | # ────────────────────────────────────────────────────────────────────────────── 5 | # Schedule: 6 | # - '0 13 * * *' runs at 13:00 UTC every day. 7 | # - This corresponds to: 8 | # • 9:00 AM Eastern **during Daylight Saving Time** (mid-Mar → early-Nov) 9 | # • 8:00 AM Eastern **during Standard Time** (early-Nov → mid-Mar) 10 | # 11 | # Job routing: 12 | # - call-benchmark-tracker: 13 | # • Runs on manual dispatch, and on pushes to the 'latest' branch. 14 | # - call-monitor-promotions: 15 | # • Runs on schedule or manual dispatch **only in repos named ansible-lockdown/Private-***. 16 | # • Skips automatically in public repos (e.g., Windows-2022-CIS) to avoid false failures. 17 | # 18 | # Defense-in-depth: 19 | # - The called promotion workflow may still keep its own guard to ensure only Private-* repos execute it. 20 | 21 | name: Central Benchmark Orchestrator 22 | 23 | on: 24 | push: 25 | branches: 26 | - latest 27 | schedule: 28 | - cron: '0 13 * * *' # 13:00 UTC → 9 AM ET (DST) / 8 AM ET (Standard Time) 29 | workflow_dispatch: 30 | 31 | jobs: 32 | call-benchmark-tracker: 33 | # Run on manual dispatch OR when 'latest' branch receives a push 34 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_name == 'latest') 35 | name: Start Benchmark Tracker 36 | uses: ansible-lockdown/github_linux_IaC/.github/workflows/benchmark_track.yml@self_hosted 37 | with: 38 | repo_name: ${{ github.repository }} 39 | secrets: 40 | TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }} 41 | BADGE_PUSH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }} 42 | DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} 43 | 44 | call-monitor-promotions: 45 | # Run on schedule or manual dispatch, but only for Private-* repos 46 | if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && startsWith(github.repository, 'ansible-lockdown/Private-') 47 | name: Monitor Promotions and Auto-Promote 48 | uses: ansible-lockdown/github_linux_IaC/.github/workflows/benchmark_promote.yml@self_hosted 49 | with: 50 | repo_name: ${{ github.repository }} 51 | secrets: 52 | TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }} 53 | BADGE_PUSH_TOKEN: ${{ secrets.BADGE_PUSH_TOKEN }} 54 | DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} 55 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.2.2.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.2.2.1.1 | PATCH | Ensure systemd-journal-remote is installed" 4 | when: 5 | - rhel9cis_rule_6_2_2_1_1 6 | - not rhel9cis_system_is_log_server 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - journald 12 | - rule_6.2.2.1.1 13 | - NIST800-53R5_AU-2 14 | - NIST800-53R5_AU-7 15 | - NIST800-53R5_AU-12 16 | ansible.builtin.package: 17 | name: systemd-journal-remote 18 | state: present 19 | 20 | - name: "6.2.2.1.2 | PATCH | Ensure systemd-journal-upload authentication is configured" 21 | when: 22 | - rhel9cis_rule_6_2_2_1_2 23 | - not rhel9cis_system_is_log_server 24 | tags: 25 | - level1-server 26 | - level1-workstation 27 | - patch 28 | - journald 29 | - rule_6.2.2.1.2 30 | - NIST800-53R5_AU-2 31 | - NIST800-53R5_AU-12 32 | notify: Restart journald 33 | ansible.builtin.lineinfile: 34 | path: /etc/systemd/journal-upload.conf 35 | regexp: "{{ item.regexp }}" 36 | line: "{{ item.line }}" 37 | loop: 38 | - { regexp: 'URL=', line: 'URL={{ rhel9cis_journal_upload_url }}'} 39 | - { regexp: 'ServerKeyFile=', line: 'ServerKeyFile={{ rhel9cis_journal_upload_serverkeyfile }}'} 40 | - { regexp: 'ServerCertificateFile=', line: 'ServerCertificateFile={{ rhel9cis_journal_servercertificatefile }}'} 41 | - { regexp: 'TrustedCertificateFile=', line: 'TrustedCertificateFile={{ rhel9cis_journal_trustedcertificatefile }}'} 42 | 43 | - name: "6.2.2.1.3 | PATCH | Ensure systemd-journal-upload is enabled and active" 44 | when: 45 | - not rhel9cis_system_is_log_server 46 | - rhel9cis_rule_6_2_2_1_3 47 | tags: 48 | - level1-server 49 | - level1-workstation 50 | - patch 51 | - journald 52 | - rule_6.2.2.1.3 53 | - NIST800-53R5_AU-2 54 | - NIST800-53R5_AU-12 55 | ansible.builtin.systemd: 56 | name: systemd-journal-upload 57 | masked: false 58 | enabled: true 59 | 60 | - name: "6.2.2.1.4 | PATCH | Ensure systemd-journal-remote service is not in use" 61 | when: 62 | - not rhel9cis_system_is_log_server 63 | - rhel9cis_rule_6_2_2_1_4 64 | tags: 65 | - level1-server 66 | - level1-workstation 67 | - patch 68 | - journald 69 | - rule_6.2.2.1.4 70 | - NIST800-53R5_AU-2 71 | - NIST800-53R5_AU-7 72 | - NIST800-53R5_AU-12 73 | ansible.builtin.systemd: 74 | name: "{{ item }}" 75 | state: stopped 76 | enabled: false 77 | masked: true 78 | loop: 79 | - systemd-journal-remote.socket 80 | - systemd-journal-remote.service 81 | -------------------------------------------------------------------------------- /tasks/auditd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Since auditd rules are dependent on syscalls and syscall tables are architecture specific, 4 | # we need to update the auditd rules depending on the architecture of the system. 5 | # This task passed the syscalls table to the auditd template and updates the auditd rules 6 | 7 | - name: "POST | AUDITD | Set supported_syscalls variable" 8 | ansible.builtin.shell: ausyscall --dump | awk '{print $2}' 9 | changed_when: false 10 | check_mode: false 11 | failed_when: discovered_auditd_syscalls.rc not in [ 0, 1 ] 12 | register: discovered_auditd_syscalls 13 | 14 | - name: POST | AUDITD | Apply auditd template will for section 6.3.3 - only required rules will be added | stat file 15 | ansible.builtin.stat: 16 | path: /etc/audit/rules.d/99_auditd.rules 17 | register: discovered_auditd_rules_file 18 | 19 | - name: POST | Apply auditd template for section 6.3.3.x 20 | when: update_audit_template 21 | vars: 22 | supported_syscalls: "{{ discovered_auditd_syscalls.stdout_lines }}" 23 | ansible.builtin.template: 24 | src: audit/99_auditd.rules.j2 25 | dest: /etc/audit/rules.d/99_auditd.rules 26 | owner: root 27 | group: root 28 | mode: 'u-x,g-wx,o-rwx' 29 | diff: "{{ discovered_auditd_rules_file.stat.exists }}" # Only run diff if not a new file 30 | register: discovered_auditd_rules_template_updated 31 | notify: 32 | - Auditd immutable check 33 | - Audit immutable fact 34 | - Restart auditd 35 | 36 | - name: POST | AUDITD | Add Warning count for changes to template file | Warn Count # noqa no-handler 37 | when: 38 | - discovered_auditd_rules_template_updated.changed 39 | - discovered_auditd_rules_file.stat.exists 40 | ansible.builtin.import_tasks: 41 | file: warning_facts.yml 42 | vars: 43 | warn_control_id: 'Auditd template updated, validate as expected' 44 | 45 | - name: POST | AUDITD | Apply auditd template will for section 4.1.3 - only required rules will be added | stat file 46 | ansible.builtin.stat: 47 | path: /etc/audit/rules.d/98_auditd_exceptions.rules 48 | register: discovered_auditd_exception_file 49 | 50 | - name: POST | Set up auditd user logging exceptions | setup file 51 | when: 52 | - rhel9cis_allow_auditd_uid_user_exclusions 53 | - rhel9cis_auditd_uid_exclude | length > 0 54 | ansible.builtin.template: 55 | src: audit/98_auditd_exception.rules.j2 56 | dest: /etc/audit/rules.d/98_auditd_exceptions.rules 57 | owner: root 58 | group: root 59 | mode: '0640' 60 | diff: "{{ discovered_auditd_exception_file.stat.exists }}" 61 | notify: Restart auditd 62 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for RHEL9-CIS 3 | 4 | min_ansible_version: 2.10.1 5 | rhel9cis_allowed_crypto_policies: 6 | - 'DEFAULT' 7 | - 'FUTURE' 8 | - 'FIPS' 9 | 10 | # Following is left blank for ability to build string 11 | rhel9cis_crypto_policy_module: '' 12 | 13 | # Do not adjust these are recognized as part of the CIS benchmark and used during testing 14 | rhel9cis_allowed_crypto_policies_modules: 15 | # Recognized by CIS as possible extra options 16 | - 'OSPP' 17 | - 'AD-SUPPORT' 18 | - 'AD-SUPPORT-LEGACY' 19 | # The following are already included in 1.6.x controls 20 | - 'NO-SHA1' 21 | - 'NO-SSHCBC' 22 | - 'NO-SSHETM' 23 | - 'NO-SSHWEAKCIPHER' 24 | - 'NO-SSHWEAKMAC' 25 | - 'NO-WEAKMAC' 26 | 27 | # Used to control warning summary 28 | warn_control_list: "" 29 | warn_count: 0 30 | 31 | # list of dicts of interactive users, filled in during prelim.yml 32 | prelim_interactive_users: [] 33 | 34 | # Default empty values for 1.4.2 35 | efi_mount_opts_addition: '' 36 | 37 | gpg_key_package: "{{ ansible_facts.distribution | lower }}-gpg-keys" 38 | 39 | ## Controls 6.3.3.x - Audit template 40 | # This variable is set to true by tasks 6.3.3.1 to 6.3.3.20. As a result, the 41 | # audit settings are overwritten with the role's template. In order to exclude 42 | # specific rules, you must set the variable of form `ubtu24cis_rule_6_3_3_x` above 43 | # to `false`. 44 | update_audit_template: false 45 | 46 | # Defaults 47 | ## Usage on containerized images 48 | # The role discovers dynamically (in tasks/main.yml) whether it 49 | # is executed on a container image and sets the variable 50 | # system_is_container the true. Otherwise, the default value 51 | # 'false' is left unchanged. 52 | system_is_container: false 53 | # The filename of the existing yml file in role's 'vars/' sub-directory 54 | # to be used for managing the role-behavior when a container was detected: 55 | # (de)activating rules or for other tasks(e.g. disabling Selinux or a specific 56 | # firewall-type). 57 | container_vars_file: is_container.yml 58 | # rhel9cis is left off the front of this var for consistency in testing pipeline 59 | # system_is_ec2 toggle will disable tasks that fail on Amazon EC2 instances. Set true to skip and false to run tasks 60 | system_is_ec2: false 61 | 62 | # Aide Packages 63 | 64 | aide_packages: 65 | - aide 66 | # Aide initiate command for new DB creation 67 | aide_initiate_command: /usr/sbin/aide --init 68 | 69 | # Audit vars 70 | audit_bins: 71 | - /sbin/auditctl 72 | - /sbin/aureport 73 | - /sbin/ausearch 74 | - /sbin/autrace 75 | - /sbin/auditd 76 | - /sbin/augenrules 77 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.4.1 | PATCH | Ensure /var is a separate partition" 4 | when: 5 | - rhel9cis_rule_1_1_2_4_1 6 | - required_mount not in prelim_mount_names 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.4.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: '1.1.2.4.1' 16 | required_mount: '/var' 17 | block: 18 | - name: "1.1.2.4.1 | AUDIT | Ensure /var is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_var_mount.rc not in [ 0, 1 ] 22 | register: discovered_var_mount 23 | 24 | - name: "1.1.2.4.1 | AUDIT | Ensure /var is a separate partition | Absent" 25 | when: discovered_var_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.4.1 | AUDIT | Ensure /var is a separate partition | Present" 30 | when: discovered_var_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | - name: "1.1.2.4.2 | PATCH | Ensure nodev option set on /var partition" 35 | when: 36 | - prelim_mount_point_fs_and_options[mount_point] is defined 37 | - rhel9cis_rule_1_1_2_4_2 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - mounts 43 | - rule_1.1.2.4.2 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | vars: 47 | mount_point: "/var" 48 | required_option: nodev 49 | notify: &mount_option_notify 50 | - "Remount {{ mount_point }}" 51 | ansible.builtin.set_fact: &mount_option_set_fact 52 | prelim_mount_point_fs_and_options: | 53 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 54 | changed_when: &mount_option_changed_when 55 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 56 | 57 | - name: "1.1.2.4.3 | PATCH | Ensure nosuid option set on /var partition" 58 | when: 59 | - prelim_mount_point_fs_and_options[mount_point] is defined 60 | - rhel9cis_rule_1_1_2_4_3 61 | tags: 62 | - level1-server 63 | - level1-workstation 64 | - patch 65 | - mounts 66 | - rule_1.1.2.4.3 67 | - NIST800-53R5_AC-3 68 | - NIST800-53R5_MP-2 69 | vars: 70 | mount_point: "/var" 71 | required_option: nosuid 72 | notify: *mount_option_notify 73 | ansible.builtin.set_fact: 74 | <<: *mount_option_set_fact 75 | changed_when: *mount_option_changed_when 76 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "1.1.2.3.1 | PATCH | Ensure /home is a separate partition" 3 | when: 4 | - rhel9cis_rule_1_1_2_3_1 5 | - required_mount not in prelim_mount_names 6 | tags: 7 | - level1-server 8 | - level1-workstation 9 | - audit 10 | - mounts 11 | - rule_1.1.2.3.1 12 | - NIST800-53R5_CM-7 13 | vars: 14 | warn_control_id: "1.1.2.3.1" 15 | required_mount: "/home" 16 | block: 17 | - name: "1.1.2.3.1 | AUDIT | Ensure /home is a separate partition | check for mount" 18 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 19 | changed_when: false 20 | failed_when: discovered_home_mount.rc not in [ 0, 1 ] 21 | register: discovered_home_mount 22 | 23 | - name: "1.1.2.3.1 | AUDIT | Ensure /home is a separate partition | Absent" 24 | when: discovered_home_mount is undefined 25 | ansible.builtin.debug: 26 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 27 | 28 | - name: "1.1.2.3.1 | AUDIT | Ensure /home is a separate partition | Present" 29 | when: discovered_home_mount is undefined 30 | ansible.builtin.import_tasks: 31 | file: warning_facts.yml 32 | 33 | - name: "1.1.2.3.2 | PATCH | Ensure nodev option set on /home partition" 34 | when: 35 | - prelim_mount_point_fs_and_options[mount_point] is defined 36 | - rhel9cis_rule_1_1_2_3_2 37 | tags: 38 | - level1-server 39 | - level1-workstation 40 | - patch 41 | - mounts 42 | - rule_1.1.2.3.2 43 | - NIST800-53R5_AC-3 44 | - NIST800-53R5_MP-2 45 | vars: 46 | mount_point: "/home" 47 | required_option: nodev 48 | notify: &mount_option_notify 49 | - "Remount {{ mount_point }}" 50 | ansible.builtin.set_fact: &mount_option_set_fact 51 | prelim_mount_point_fs_and_options: | 52 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 53 | changed_when: &mount_option_changed_when 54 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 55 | 56 | - name: "1.1.2.3.3 | PATCH | Ensure nosuid option set on /home partition" 57 | when: 58 | - prelim_mount_point_fs_and_options[mount_point] is defined 59 | - rhel9cis_rule_1_1_2_3_3 60 | tags: 61 | - level1-server 62 | - level1-workstation 63 | - patch 64 | - mounts 65 | - rule_1.1.2.3.3 66 | - NIST800-53R5_AC-3 67 | - NIST800-53R5_MP-2 68 | vars: 69 | mount_point: "/home" 70 | required_option: nosuid 71 | notify: *mount_option_notify 72 | ansible.builtin.set_fact: 73 | <<: *mount_option_set_fact 74 | changed_when: *mount_option_changed_when 75 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.7.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.7.1 | PATCH | Ensure message of the day is configured properly" 4 | when: rhel9cis_rule_1_7_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - banner 9 | - patch 10 | - rule_1.7.1 11 | - NIST800-53R5_CM-1 12 | - NIST800-53R5_CM-3 13 | - NIST800-53R5_CM-6 14 | ansible.builtin.template: 15 | src: etc/motd.j2 16 | dest: /etc/motd 17 | owner: root 18 | group: root 19 | mode: 'u-x,go-wx' 20 | 21 | - name: "1.7.2 | PATCH | Ensure local login warning banner is configured properly" 22 | when: rhel9cis_rule_1_7_2 23 | tags: 24 | - level1-server 25 | - level1-workstation 26 | - patch 27 | - rule_1.7.2 28 | - NIST800-53R5_CM-1 29 | - NIST800-53R5_CM-3 30 | - NIST800-53R5_CM-6 31 | ansible.builtin.template: 32 | src: etc/issue.j2 33 | dest: /etc/issue 34 | owner: root 35 | group: root 36 | mode: 'go-wx' 37 | 38 | - name: "1.7.3 | PATCH | Ensure remote login warning banner is configured properly" 39 | when: rhel9cis_rule_1_7_3 40 | tags: 41 | - level1-server 42 | - level1-workstation 43 | - banner 44 | - patch 45 | - rule_1.7.3 46 | - NIST800-53R5_CM-1 47 | - NIST800-53R5_CM-3 48 | - NIST800-53R5_CM-6 49 | ansible.builtin.template: 50 | src: etc/issue.net.j2 51 | dest: /etc/issue.net 52 | owner: root 53 | group: root 54 | mode: 'go-wx' 55 | 56 | - name: "1.7.4 | PATCH | Ensure permissions on /etc/motd are configured" 57 | when: rhel9cis_rule_1_7_4 58 | tags: 59 | - level1-server 60 | - level1-workstation 61 | - perms 62 | - patch 63 | - rule_1.7.4 64 | - NIST800-53R5_AC-3 65 | - NIST800-53R5_MP-2 66 | ansible.builtin.file: 67 | path: /etc/motd 68 | owner: root 69 | group: root 70 | mode: 'go-wx' 71 | 72 | - name: "1.7.5 | PATCH | Ensure permissions on /etc/issue are configured" 73 | when: rhel9cis_rule_1_7_5 74 | tags: 75 | - level1-server 76 | - level1-workstation 77 | - perms 78 | - patch 79 | - rule_1.7.5 80 | - NIST800-53R5_AC-3 81 | - NIST800-53R5_MP-2 82 | ansible.builtin.file: 83 | path: /etc/issue 84 | owner: root 85 | group: root 86 | mode: 'go-wx' 87 | 88 | - name: "1.7.6 | PATCH | Ensure permissions on /etc/issue.net are configured" 89 | when: rhel9cis_rule_1_7_6 90 | tags: 91 | - level1-server 92 | - level1-workstation 93 | - perms 94 | - patch 95 | - rule_1.7.6 96 | - NIST800-53R5_AC-3 97 | - NIST800-53R5_MP-2 98 | ansible.builtin.file: 99 | path: /etc/issue.net 100 | owner: root 101 | group: root 102 | mode: 'go-wx' 103 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.2.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.2.2.2 | PATCH | Ensure journald ForwardToSyslog is disabled" 4 | when: rhel9cis_rule_6_2_2_2 5 | tags: 6 | - level1-server 7 | - level2-workstation 8 | - patch 9 | - journald 10 | - rule_6.2.2.2 11 | - NIST800-53R5_AU-2 12 | - NIST800-53R5_AU-6 13 | - NIST800-53R5_AU-7 14 | - NIST800-53R5_AU-12 15 | notify: Restart journald 16 | block: 17 | - name: "6.2.2.2 | PATCH | Ensure journald ForwardToSyslog is disabled | Add file" 18 | ansible.builtin.template: 19 | src: etc/systemd/journald.conf.d/forwardtosyslog.conf.j2 20 | dest: /etc/systemd/journald.conf.d/forwardtosyslog.conf 21 | owner: root 22 | group: root 23 | mode: 'g-wx,o-rwx' 24 | 25 | - name: "6.2.2.2 | PATCH | Ensure journald ForwardToSyslog is disabled | comment out current entries" 26 | ansible.builtin.replace: 27 | path: /etc/systemd/journald.conf 28 | regexp: ^(\s*ForwardToSyslog) 29 | replace: '#\1' 30 | 31 | - name: "6.2.2.3 | PATCH | Ensure journald Compress is configured" 32 | when: rhel9cis_rule_6_2_2_3 33 | tags: 34 | - level1-server 35 | - level1-workstation 36 | - patch 37 | - journald 38 | - rule_6.2.2.3 39 | - NIST800-53R5_AU-4 40 | notify: Restart journald 41 | block: 42 | - name: "6.2.2.3 | PATCH | Ensure journald Compress is configured | Add file" 43 | ansible.builtin.template: 44 | src: etc/systemd/journald.conf.d/storage.conf.j2 # Added to the same file as 6.2.1.1.4 45 | dest: /etc/systemd/journald.conf.d/storage.conf 46 | owner: root 47 | group: root 48 | mode: 'g-wx,o-rwx' 49 | 50 | - name: "6.2.2.3 | PATCH | Ensure journald Compress is configured | comment out current entries" 51 | ansible.builtin.replace: 52 | path: /etc/systemd/journald.conf 53 | regexp: (?i)(\s*compress=) 54 | replace: '#\1' 55 | 56 | - name: "6.2.2.4 | PATCH | Ensure journald Storage is configured" 57 | when: rhel9cis_rule_6_2_2_4 58 | tags: 59 | - level1-server 60 | - level1-workstation 61 | - patch 62 | - journald 63 | - rule_6.2.2.4 64 | - NIST800-53R5_AU-3 65 | - NIST800-53R5_AU-12 66 | notify: Restart journald 67 | block: 68 | - name: "6.2.2.4 | PATCH | Ensure journald Storage is configured | Add file" 69 | ansible.builtin.template: 70 | src: etc/systemd/journald.conf.d/storage.conf.j2 71 | dest: /etc/systemd/journald.conf.d/storage.conf 72 | owner: root 73 | group: root 74 | mode: 'g-wx,o-rwx' 75 | 76 | - name: "6.2.2.4 | PATCH | Ensure journald Storage is configured | comment out current entries" 77 | ansible.builtin.replace: 78 | path: /etc/systemd/journald.conf 79 | regexp: (?i)(\s*storage=) 80 | replace: '#\1' 81 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.4.1 | PATCH | Ensure bootloader password is set" 4 | when: 5 | - rhel9cis_set_boot_pass 6 | - rhel9cis_rule_1_4_1 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - grub 11 | - patch 12 | - rule_1.4.1 13 | - NIST800-53R5_AC-3 14 | ansible.builtin.copy: 15 | dest: /boot/grub2/user.cfg 16 | content: "GRUB2_PASSWORD={{ rhel9cis_bootloader_password_hash }}" # noqa template-instead-of-copy 17 | owner: root 18 | group: root 19 | mode: 'go-rwx' 20 | notify: Grub2cfg 21 | 22 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured" 23 | when: rhel9cis_rule_1_4_2 24 | tags: 25 | - level1-server 26 | - level1-workstation 27 | - grub 28 | - patch 29 | - rule_1.4.2 30 | - NIST800-53R5_AC-3 31 | block: 32 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured | bios based system" 33 | when: rhel9cis_legacy_boot 34 | ansible.builtin.file: 35 | path: "/boot/grub2/{{ item.path }}" 36 | owner: root 37 | group: root 38 | mode: "{{ item.mode }}" 39 | state: touch 40 | modification_time: preserve 41 | access_time: preserve 42 | loop: 43 | - { path: 'grub.cfg', mode: 'u-x,go-rwx' } 44 | - { path: 'grubenv', mode: 'u-x,go-rwx' } 45 | - { path: 'user.cfg', mode: 'u-x,go-rwx' } 46 | 47 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured | efi based system" 48 | when: not rhel9cis_legacy_boot 49 | vars: 50 | efi_mount_options: ['umask=0077', 'fmask=0077', 'uid=0', 'gid=0'] 51 | block: 52 | - name: "1.4.2 | AUDIT | Ensure permissions on bootloader config are configured | efi based system | capture current state" 53 | ansible.builtin.shell: grep "^[^#;]" /etc/fstab | grep '/boot/efi' | awk -F" " '{print $4}' 54 | changed_when: false 55 | check_mode: false 56 | register: discovered_efi_fstab 57 | 58 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured | efi based system | Build Options" 59 | when: item not in discovered_efi_fstab.stdout 60 | ansible.builtin.set_fact: 61 | efi_mount_opts_addition: "{{ efi_mount_opts_addition + ',' + item }}" 62 | loop: "{{ efi_mount_options }}" 63 | 64 | - name: "1.4.2 | PATCH | Ensure permissions on bootloader config are configured | efi based system | Add mount options" 65 | when: efi_mount_opts_addition | length > 0 66 | ansible.builtin.lineinfile: 67 | path: /etc/fstab 68 | regexp: (.*/boot/efi\s*\w*\s*){{ discovered_efi_fstab.stdout }}(.*) 69 | line: \1{{ discovered_efi_fstab.stdout + efi_mount_opts_addition }}\2 70 | backrefs: true 71 | notify: Remount /boot/efi 72 | -------------------------------------------------------------------------------- /files/fs_with_cves.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # Based on original Script provided by CIS 4 | # CVEs correct at time of creation - April2024 5 | 6 | { 7 | a_output=(); a_output2=(); a_modprope_config=(); a_excluded=(); a_available_modules=() 8 | a_ignore=("xfs" "vfat" "ext2" "ext3" "ext4") 9 | a_cve_exists=("afs" "ceph" "cifs" "exfat" "ext" "fat" "fscache" "fuse" "gfs2" "nfs_common" "nfsd" "smbfs_common") 10 | f_module_chk() 11 | { 12 | l_out2=""; grep -Pq -- "\b$l_mod_name\b" <<< "${a_cve_exists[*]}" && l_out2=" <- CVE exists!" 13 | if ! grep -Pq -- '\bblacklist\h+'"$l_mod_name"'\b' <<< "${a_modprope_config[*]}"; then 14 | a_output2+=(" - Kernel module: \"$l_mod_name\" is not fully disabled $l_out2") 15 | elif ! grep -Pq -- '\binstall\h+'"$l_mod_name"'\h+\/bin\/(false|true)\b' <<< "${a_modprope_config[*]}"; then 16 | a_output2+=(" - Kernel module: \"$l_mod_name\" is not fully disabled $l_out2") 17 | fi 18 | if lsmod | grep "$l_mod_name" &> /dev/null; then # Check if the module is currently loaded 19 | l_output2+=(" - Kernel module: \"$l_mod_name\" is loaded" "") 20 | fi 21 | } 22 | while IFS= read -r -d $'\0' l_module_dir; do 23 | a_available_modules+=("$(basename "$l_module_dir")") 24 | done < <(find "$(readlink -f /lib/modules/"$(uname -r)"/kernel/fs)" -mindepth 1 -maxdepth 1 -type d ! -empty -print0) 25 | while IFS= read -r l_exclude; do 26 | if grep -Pq -- "\b$l_exclude\b" <<< "${a_cve_exists[*]}"; then 27 | a_output2+=(" - ** WARNING: kernel module: \"$l_exclude\" has a CVE and is currently mounted! **") 28 | elif 29 | grep -Pq -- "\b$l_exclude\b" <<< "${a_available_modules[*]}"; then 30 | a_output+=(" - Kernel module: \"$l_exclude\" is currently mounted - do NOT unload or disable") 31 | fi 32 | ! grep -Pq -- "\b$l_exclude\b" <<< "${a_ignore[*]}" && a_ignore+=("$l_exclude") 33 | done < <(findmnt -knD | awk '{print $2}' | sort -u) 34 | while IFS= read -r l_config; do 35 | a_modprope_config+=("$l_config") 36 | done < <(modprobe --showconfig | grep -P '^\h*(blacklist|install)') 37 | for l_mod_name in "${a_available_modules[@]}"; do # Iterate over all filesystem modules 38 | [[ "$l_mod_name" =~ overlay ]] && l_mod_name="${l_mod_name::-2}" 39 | if grep -Pq -- "\b$l_mod_name\b" <<< "${a_ignore[*]}"; then 40 | a_excluded+=(" - Kernel module: \"$l_mod_name\"") 41 | else 42 | f_module_chk 43 | fi 44 | done 45 | # Output findings 46 | 47 | echo "### Script can be found at ${BASH_SOURCE} ##" 48 | if [ "${#a_output2[@]}" -le 0 ]; then 49 | printf '%s\n' "" " - No unused filesystem kernel modules are enabled" "${a_output[@]}" "" 50 | else 51 | printf '%s\n' "" "-- Audit Result: --" " ** REVIEW the following **" "${a_output2[@]}" 52 | # Changed return value to capture error 53 | exit 99 54 | #[ "${#a_output[@]}" -gt 0 ] && printf '%s\n' "" "-- Correctly set: --" "${a_output[@]}" "" 55 | fi 56 | } 57 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.3.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.3.2.1 | PATCH | Ensure audit log storage size is configured" 4 | when: rhel9cis_rule_6_3_2_1 5 | tags: 6 | - level2-server 7 | - level2-workstation 8 | - patch 9 | - auditd 10 | - rule_6.3.2.1 11 | - NIST800-53R5_AU-8 12 | ansible.builtin.lineinfile: 13 | path: /etc/audit/auditd.conf 14 | regexp: "^max_log_file( |=)" 15 | line: "max_log_file = {{ rhel9cis_auditd_max_log_file_size }}" 16 | notify: Restart auditd 17 | 18 | - name: "6.3.2.2 | PATCH | Ensure audit logs are not automatically deleted" 19 | when: rhel9cis_rule_6_3_2_2 20 | tags: 21 | - level2-server 22 | - level2-workstation 23 | - patch 24 | - auditd 25 | - rule_6.3.2.2 26 | - NIST800-53R5_AU-8 27 | ansible.builtin.lineinfile: 28 | path: /etc/audit/auditd.conf 29 | regexp: "^max_log_file_action" 30 | line: "max_log_file_action = {{ rhel9cis_auditd_max_log_file_action }}" 31 | notify: Restart auditd 32 | 33 | - name: "6.3.2.3 | PATCH | Ensure system is disabled when audit logs are full" 34 | when: rhel9cis_rule_6_3_2_3 35 | tags: 36 | - level2-server 37 | - level2-workstation 38 | - patch 39 | - auditd 40 | - rule_6.3.2.3 41 | - NIST800-53R5_AU-2 42 | - NIST800-53R5_AU-8 43 | - NIST800-53R5_AU-12 44 | - NIST800-53R5_SI-5 45 | ansible.builtin.lineinfile: 46 | path: /etc/audit/auditd.conf 47 | regexp: "{{ item.regexp }}" 48 | line: "{{ item.line }}" 49 | notify: Restart auditd 50 | loop: 51 | - { regexp: '^disk_full_action', line: 'disk_full_action = {{ rhel9cis_auditd_disk_full_action }}' } 52 | - { regexp: '^disk_error_action', line: 'disk_error_action = {{ rhel9cis_auditd_disk_error_action }}' } 53 | 54 | - name: "6.3.2.4 | PATCH | Ensure system warns when audit logs are low on space" 55 | when: rhel9cis_rule_6_3_2_4 56 | tags: 57 | - level2-server 58 | - level2-workstation 59 | - patch 60 | - auditd 61 | - rule_6.3.2.4 62 | - NIST800-53R5_AU-2 63 | - NIST800-53R5_AU-8 64 | - NIST800-53R5_AU-12 65 | - NIST800-53R5_SI-5 66 | ansible.builtin.lineinfile: 67 | path: /etc/audit/auditd.conf 68 | regexp: "{{ item.regexp }}" 69 | line: "{{ item.line }}" 70 | notify: Restart auditd 71 | loop: 72 | - { regexp: '^space_left_action', line: 'space_left_action = {{ rhel9cis_auditd_space_left_action }}' } 73 | - { regexp: '^admin_space_left_action', line: 'admin_space_left_action = {{ rhel9cis_auditd_admin_space_left_action }}' } 74 | 75 | - name: "PATCH | Configure other keys for auditd.conf" 76 | when: 77 | - rhel9cis_auditd_extra_conf.keys() | length > 0 78 | - rhel9cis_auditd_extra_conf_usage 79 | tags: 80 | - level2-server 81 | - level2-workstation 82 | - patch 83 | - auditd 84 | - NIST800-53R5_AU-2 85 | - NIST800-53R5_AU-8 86 | - NIST800-53R5_AU-12 87 | - NIST800-53R5_SI-5 88 | ansible.builtin.lineinfile: 89 | path: /etc/audit/auditd.conf 90 | regexp: "^{{ item }}( |=)" 91 | line: "{{ item }} = {{ rhel9cis_auditd_extra_conf[item] }}" 92 | loop: "{{ rhel9cis_auditd_extra_conf.keys() }}" 93 | notify: Restart auditd 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing to MindPoint Group Projects 2 | ======================================== 3 | 4 | Rules 5 | ----- 6 | 1) All commits must be GPG signed (details in Signing section) 7 | 2) All commits must have Signed-off-by (Signed-off-by: Joan Doe ) in the commit message (details in Signing section) 8 | 3) All work is done in your own branch 9 | 4) All pull requests go into the devel branch. There are automated checks for signed commits, signoff in commit message, and functional testing) 10 | 5) Be open and nice to eachother 11 | 12 | Workflow 13 | -------- 14 | - Your work is done in your own individual branch. Make sure to to Signed-off and GPG sign all commits you intend to merge 15 | - All community Pull Requests are into the devel branch. There are automated checks for GPG signed, Signed-off in commits, and functional tests before being approved. If your pull request comes in from outside of our repo, the pull request will go into a staging branch. There is info needed from our repo for our CI/CD testing. 16 | - Once your changes are merged and a more detailed review is complete, an authorized member will merge your changes into the main branch for a new release 17 | 18 | Signing your contribution 19 | ------------------------- 20 | 21 | We've chosen to use the Developer's Certificate of Origin (DCO) method 22 | that is employed by the Linux Kernel Project, which provides a simple 23 | way to contribute to MindPoint Group projects. 24 | 25 | The process is to certify the below DCO 1.1 text 26 | :: 27 | 28 | Developer's Certificate of Origin 1.1 29 | 30 | By making a contribution to this project, I certify that: 31 | 32 | (a) The contribution was created in whole or in part by me and I 33 | have the right to submit it under the open source license 34 | indicated in the file; or 35 | 36 | (b) The contribution is based upon previous work that, to the best 37 | of my knowledge, is covered under an appropriate open source 38 | license and I have the right under that license to submit that 39 | work with modifications, whether created in whole or in part 40 | by me, under the same open source license (unless I am 41 | permitted to submit under a different license), as indicated 42 | in the file; or 43 | 44 | (c) The contribution was provided directly to me by some other 45 | person who certified (a), (b) or (c) and I have not modified 46 | it. 47 | 48 | (d) I understand and agree that this project and the contribution 49 | are public and that a record of the contribution (including all 50 | personal information I submit with it, including my sign-off) is 51 | maintained indefinitely and may be redistributed consistent with 52 | this project or the open source license(s) involved. 53 | :: 54 | 55 | Then, when it comes time to submit a contribution, include the 56 | following text in your contribution commit message: 57 | 58 | :: 59 | 60 | Signed-off-by: Joan Doe 61 | 62 | :: 63 | 64 | This message can be entered manually, or if you have configured git 65 | with the correct `user.name` and `user.email`, you can use the `-s` 66 | option to `git commit` to automatically include the signoff message. 67 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.2.1 | PATCH | Ensure /dev/shm is a separate partition" 4 | when: 5 | - rhel9cis_rule_1_1_2_2_1 6 | - required_mount not in prelim_mount_names 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.2.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: "1.1.2.2.1" 16 | required_mount: "/dev/shm" 17 | block: 18 | - name: "1.1.2.2.1 | AUDIT | Ensure /dev/shm is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_dev_shm_mount.rc not in [ 0, 1 ] 22 | register: discovered_dev_shm_mount 23 | 24 | - name: "1.1.2.2.1 | AUDIT | Ensure /dev/shm is a separate partition | Absent" 25 | when: discovered_dev_shm_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.2.1 | AUDIT | Ensure /dev/shm is a separate partition | Present" 30 | when: discovered_dev_shm_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | - name: "1.1.2.2.2 | PATCH | Ensure nodev option set on /dev/shm partition" 35 | when: 36 | - prelim_mount_point_fs_and_options[mount_point] is defined 37 | - rhel9cis_rule_1_1_2_2_2 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - mounts 43 | - rule_1.1.2.2.2 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | vars: 47 | mount_point: "/dev/shm" 48 | required_option: nodev 49 | notify: &mount_option_notify 50 | - "Remount {{ mount_point }}" 51 | ansible.builtin.set_fact: &mount_option_set_fact 52 | prelim_mount_point_fs_and_options: | 53 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 54 | changed_when: &mount_option_changed_when 55 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 56 | 57 | - name: "1.1.2.2.3 | PATCH | Ensure nosuid option set on /dev/shm partition" 58 | when: 59 | - prelim_mount_point_fs_and_options[mount_point] is defined 60 | - rhel9cis_rule_1_1_2_2_3 61 | tags: 62 | - level1-server 63 | - level1-workstation 64 | - patch 65 | - mounts 66 | - rule_1.1.2.2.3 67 | - NIST800-53R5_AC-3 68 | - NIST800-53R5_MP-2 69 | vars: 70 | mount_point: "/dev/shm" 71 | required_option: nosuid 72 | notify: *mount_option_notify 73 | ansible.builtin.set_fact: 74 | <<: *mount_option_set_fact 75 | changed_when: *mount_option_changed_when 76 | 77 | - name: "1.1.2.2.4 | PATCH | Ensure noexec option set on /dev/shm partition" 78 | when: 79 | - prelim_mount_point_fs_and_options[mount_point] is defined 80 | - rhel9cis_rule_1_1_2_2_4 81 | tags: 82 | - level1-server 83 | - level1-workstation 84 | - patch 85 | - mounts 86 | - rule_1.1.2.2.4 87 | - NIST800-53R5_AC-3 88 | - NIST800-53R5_MP-2 89 | vars: 90 | mount_point: "/dev/shm" 91 | required_option: noexec 92 | notify: *mount_option_notify 93 | ansible.builtin.set_fact: 94 | <<: *mount_option_set_fact 95 | changed_when: *mount_option_changed_when 96 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.5.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.5.1 | PATCH | Ensure /var/tmp is a separate partition" 4 | when: 5 | - rhel9cis_rule_1_1_2_5_1 6 | - required_mount not in prelim_mount_names 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.5.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: '1.1.2.5.1' 16 | required_mount: '/var/tmp' 17 | block: 18 | - name: "1.1.2.5.1 | AUDIT | Ensure /var/tmp is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_var_tmp_mount.rc not in [ 0, 1 ] 22 | register: discovered_var_tmp_mount 23 | 24 | - name: "1.1.2.5.1 | AUDIT | Ensure /var/tmp is a separate partition | Absent" 25 | when: discovered_var_tmp_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.5.1 | AUDIT | Ensure /var/tmp is a separate partition | Present" 30 | when: discovered_var_tmp_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | - name: "1.1.2.5.2 | PATCH | Ensure nodev option set on /var/tmp partition" 35 | when: 36 | - prelim_mount_point_fs_and_options[mount_point] is defined 37 | - rhel9cis_rule_1_1_2_5_2 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - mounts 43 | - rule_1.1.2.5.2 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | vars: 47 | mount_point: "/var/tmp" 48 | required_option: nodev 49 | notify: &mount_option_notify 50 | - "Remount {{ mount_point }}" 51 | ansible.builtin.set_fact: &mount_option_set_fact 52 | prelim_mount_point_fs_and_options: | 53 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 54 | changed_when: &mount_option_changed_when 55 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 56 | 57 | - name: "1.1.2.5.3 | PATCH | Ensure nosuid option set on /var/tmp partition" 58 | when: 59 | - prelim_mount_point_fs_and_options[mount_point] is defined 60 | - rhel9cis_rule_1_1_2_5_3 61 | tags: 62 | - level1-server 63 | - level1-workstation 64 | - patch 65 | - mounts 66 | - rule_1.1.2.5.3 67 | - NIST800-53R5_AC-3 68 | - NIST800-53R5_MP-2 69 | vars: 70 | mount_point: "/var/tmp" 71 | required_option: nosuid 72 | notify: *mount_option_notify 73 | ansible.builtin.set_fact: 74 | <<: *mount_option_set_fact 75 | changed_when: *mount_option_changed_when 76 | 77 | - name: "1.1.2.5.4 | PATCH | Ensure noexec option set on /var/tmp partition" 78 | when: 79 | - prelim_mount_point_fs_and_options[mount_point] is defined 80 | - rhel9cis_rule_1_1_2_5_4 81 | tags: 82 | - level1-server 83 | - level1-workstation 84 | - patch 85 | - mounts 86 | - rule_1.1.2.5.4 87 | - NIST800-53R5_AC-3 88 | - NIST800-53R5_MP-2 89 | vars: 90 | mount_point: "/var/tmp" 91 | required_option: noexec 92 | notify: *mount_option_notify 93 | ansible.builtin.set_fact: 94 | <<: *mount_option_set_fact 95 | changed_when: *mount_option_changed_when 96 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.6.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.6.1 | PATCH | Ensure /var/log is a separate partition" 4 | when: 5 | - rhel9cis_rule_1_1_2_6_1 6 | - required_mount not in prelim_mount_names 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.6.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: '1.1.2.6.1' 16 | required_mount: '/var/log' 17 | block: 18 | - name: "1.1.2.6.1 | AUDIT | Ensure /var/log is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_var_log_mount.rc not in [ 0, 1 ] 22 | register: discovered_var_log_mount 23 | 24 | - name: "1.1.2.6.1 | AUDIT | Ensure /var/log is a separate partition | Absent" 25 | when: discovered_var_log_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.6.1 | AUDIT | Ensure /var/log is a separate partition | Present" 30 | when: discovered_var_log_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | - name: "1.1.2.6.2 | PATCH | Ensure nodev option set on /var/log partition" 35 | when: 36 | - prelim_mount_point_fs_and_options[mount_point] is defined 37 | - rhel9cis_rule_1_1_2_6_2 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - mounts 43 | - rule_1.1.2.6.2 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | vars: 47 | mount_point: "/var/log" 48 | required_option: nodev 49 | notify: &mount_option_notify 50 | - "Remount {{ mount_point }}" 51 | ansible.builtin.set_fact: &mount_option_set_fact 52 | prelim_mount_point_fs_and_options: | 53 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 54 | changed_when: &mount_option_changed_when 55 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 56 | 57 | - name: "1.1.2.6.3 | PATCH | Ensure nosuid option set on /var/log partition" 58 | when: 59 | - prelim_mount_point_fs_and_options[mount_point] is defined 60 | - rhel9cis_rule_1_1_2_6_3 61 | tags: 62 | - level1-server 63 | - level1-workstation 64 | - patch 65 | - mounts 66 | - rule_1.1.2.6.3 67 | - NIST800-53R5_AC-3 68 | - NIST800-53R5_MP-2 69 | vars: 70 | mount_point: "/var/log" 71 | required_option: nosuid 72 | notify: *mount_option_notify 73 | ansible.builtin.set_fact: 74 | <<: *mount_option_set_fact 75 | changed_when: *mount_option_changed_when 76 | 77 | - name: "1.1.2.6.4 | PATCH | Ensure noexec option set on /var/log partition" 78 | when: 79 | - prelim_mount_point_fs_and_options[mount_point] is defined 80 | - rhel9cis_rule_1_1_2_6_4 81 | tags: 82 | - level1-server 83 | - level1-workstation 84 | - patch 85 | - mounts 86 | - rule_1.1.2.6.4 87 | - NIST800-53R5_AC-3 88 | - NIST800-53R5_MP-2 89 | vars: 90 | mount_point: "/var/log" 91 | required_option: noexec 92 | notify: *mount_option_notify 93 | ansible.builtin.set_fact: 94 | <<: *mount_option_set_fact 95 | changed_when: *mount_option_changed_when 96 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.7.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.7.1 | PATCH | Ensure /var/log/audit is a separate partition" 4 | when: 5 | - rhel9cis_rule_1_1_2_7_1 6 | - required_mount not in prelim_mount_names 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.7.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: '1.1.2.7.1' 16 | required_mount: '/var/log/audit' 17 | block: 18 | - name: "1.1.2.7.1 | AUDIT | Ensure /var/log/audit is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_var_log_audit_mount.rc not in [ 0, 1 ] 22 | register: discovered_var_log_audit_mount 23 | 24 | - name: "1.1.2.7.1 | AUDIT | Ensure /var/log/audit is a separate partition | Absent" 25 | when: discovered_var_log_audit_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.7.1 | AUDIT | Ensure /var/log/audit is a separate partition | Present" 30 | when: discovered_var_log_audit_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | - name: "1.1.2.7.2 | PATCH | Ensure nodev option set on /var/log/audit partition" 35 | when: 36 | - prelim_mount_point_fs_and_options[mount_point] is defined 37 | - rhel9cis_rule_1_1_2_7_2 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - mounts 43 | - rule_1.1.2.7.2 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | vars: 47 | mount_point: "/var/log/audit" 48 | required_option: nodev 49 | notify: &mount_option_notify 50 | - "Remount {{ mount_point }}" 51 | ansible.builtin.set_fact: &mount_option_set_fact 52 | prelim_mount_point_fs_and_options: | 53 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 54 | changed_when: &mount_option_changed_when 55 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 56 | 57 | - name: "1.1.2.7.3 | PATCH | Ensure nosuid option set on /var/log/audit partition" 58 | when: 59 | - prelim_mount_point_fs_and_options[mount_point] is defined 60 | - rhel9cis_rule_1_1_2_7_3 61 | tags: 62 | - level1-server 63 | - level1-workstation 64 | - patch 65 | - mounts 66 | - rule_1.1.2.7.3 67 | - NIST800-53R5_AC-3 68 | - NIST800-53R5_MP-2 69 | vars: 70 | mount_point: "/var/log/audit" 71 | required_option: nosuid 72 | notify: *mount_option_notify 73 | ansible.builtin.set_fact: 74 | <<: *mount_option_set_fact 75 | changed_when: *mount_option_changed_when 76 | 77 | - name: "1.1.2.7.4 | PATCH | Ensure noexec option set on /var/log/audit partition" 78 | when: 79 | - prelim_mount_point_fs_and_options[mount_point] is defined 80 | - rhel9cis_rule_1_1_2_7_4 81 | tags: 82 | - level1-server 83 | - level1-workstation 84 | - patch 85 | - mounts 86 | - rule_1.1.2.7.4 87 | - NIST800-53R5_AC-3 88 | - NIST800-53R5_MP-2 89 | vars: 90 | mount_point: "/var/log/audit" 91 | required_option: noexec 92 | notify: *mount_option_notify 93 | ansible.builtin.set_fact: 94 | <<: *mount_option_set_fact 95 | changed_when: *mount_option_changed_when 96 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.3.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.3.4.1 | PATCH | Ensure the audit log file directory mode is configured" 4 | when: rhel9cis_rule_6_3_4_1 5 | tags: 6 | - level2-server 7 | - level2-workstation 8 | - patch 9 | - auditd 10 | - rule_6.3.4.1 11 | - NIST800-53R5_AU-3 12 | ansible.builtin.file: 13 | path: "{{ prelim_auditd_logfile.stdout | dirname }}" 14 | state: directory 15 | mode: 'g-w,o-rwx' 16 | 17 | - name: | 18 | "6.3.4.2 | PATCH | Ensure audit log files mode is configured" 19 | "6.3.4.3 | PATCH | Ensure audit log files owner is configured" 20 | "6.3.4.4 | PATCH | Ensure only authorized groups are assigned ownership of audit log files" 21 | when: 22 | - rhel9cis_rule_6_3_4_2 or 23 | rhel9cis_rule_6_3_4_3 or 24 | rhel9cis_rule_6_3_4_4 25 | tags: 26 | - level2-server 27 | - level2-workstation 28 | - patch 29 | - auditd 30 | - rule_6.3.4.2 31 | - rule_6.3.4.3 32 | - rule_6.3.4.4 33 | - NIST800-53R5_AU-3 34 | ansible.builtin.file: 35 | path: "{{ prelim_auditd_logfile.stdout }}" 36 | mode: 'o-x,g-wx,o-rwx' 37 | owner: root 38 | group: root 39 | 40 | - name: "6.3.4.5 | PATCH | Ensure audit configuration files mode is configured" 41 | when: rhel9cis_rule_6_3_4_5 42 | tags: 43 | - level2-server 44 | - level2-workstation 45 | - patch 46 | - auditd 47 | - rule_6.3.4.5 48 | ansible.builtin.file: 49 | path: "{{ item.path }}" 50 | mode: 'u-x,g-wx,o-rwx' 51 | failed_when: discovered_audit_conf_file_list.state not in '[ file, absent ]' 52 | register: discovered_audit_conf_file_list 53 | loop: "{{ prelim_auditd_conf_files.files }}" 54 | loop_control: 55 | label: "{{ item.path }}" 56 | 57 | - name: "6.3.4.6 | PATCH | Ensure audit configuration files owner is configured" 58 | when: rhel9cis_rule_6_3_4_6 59 | tags: 60 | - level2-server 61 | - level2-workstation 62 | - patch 63 | - auditd 64 | - rule_6.3.4.6 65 | ansible.builtin.file: 66 | path: "{{ item.path }}" 67 | owner: root 68 | failed_when: discovered_audit_conf_file_list.state not in '[ file, absent ]' 69 | register: discovered_audit_conf_file_list 70 | loop: "{{ prelim_auditd_conf_files.files | default([]) }}" 71 | loop_control: 72 | label: "{{ item.path }}" 73 | 74 | - name: "6.3.4.7 | PATCH | Ensure audit configuration files group owner is configured" 75 | when: rhel9cis_rule_6_3_4_7 76 | tags: 77 | - level2-server 78 | - level2-workstation 79 | - patch 80 | - auditd 81 | - rule_6.3.4.7 82 | ansible.builtin.file: 83 | path: "{{ item.path }}" 84 | group: root 85 | failed_when: discovered_audit_conf_file_list.state not in '[ file, absent ]' 86 | register: discovered_audit_conf_file_list 87 | loop: "{{ prelim_auditd_conf_files.files | default([]) }}" 88 | loop_control: 89 | label: "{{ item.path }}" 90 | 91 | - name: "6.3.4.8 | PATCH | Ensure audit tools mode is configured" 92 | when: rhel9cis_rule_6_3_4_8 93 | tags: 94 | - level2-server 95 | - level2-workstation 96 | - patch 97 | - auditd 98 | - rule_6.3.4.8 99 | - NIST800-53R5_AU-3 100 | ansible.builtin.file: 101 | path: "{{ item }}" 102 | mode: 'go-w' 103 | loop: "{{ audit_bins }}" 104 | 105 | - name: "6.3.4.9 | PATCH | Ensure audit tools owner is configured" 106 | when: rhel9cis_rule_6_3_4_9 107 | tags: 108 | - level2-server 109 | - level2-workstation 110 | - patch 111 | - auditd 112 | - rule_6.3.4.9 113 | ansible.builtin.file: 114 | path: "{{ item }}" 115 | owner: root 116 | group: root 117 | loop: "{{ audit_bins }}" 118 | 119 | - name: "6.3.4.10 | PATCH | Ensure audit tools group owner is configured" 120 | when: rhel9cis_rule_6_3_4_10 121 | tags: 122 | - level2-server 123 | - level2-workstation 124 | - patch 125 | - auditd 126 | - rule_6.3.4.10 127 | - NIST800-53R5_AU-3 128 | ansible.builtin.file: 129 | path: "{{ item }}" 130 | group: root 131 | loop: "{{ audit_bins }}" 132 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "3.2.1 | PATCH | Ensure dccp kernel module is not available" 4 | when: rhel9cis_rule_3_2_1 5 | tags: 6 | - level2-server 7 | - level2-workstation 8 | - patch 9 | - rule_3.2.1 10 | - dccp 11 | - NIST800-53R5_CM-7 12 | - NIST800-53R5_SI-4 13 | block: 14 | - name: "3.2.1 | PATCH | Ensure dccp kernel module is not available | modprobe" 15 | ansible.builtin.lineinfile: 16 | path: /etc/modprobe.d/dccp.conf 17 | regexp: '^(#)?install dccp(\\s|$)' 18 | line: "{{ item }}" 19 | create: true 20 | mode: 'u-x,go-rwx' 21 | loop: 22 | - install dccp /bin/true 23 | - blacklist dccp 24 | 25 | - name: "3.2.1 | PATCH | Ensure dccp kernel module is not available | blacklist" 26 | ansible.builtin.lineinfile: 27 | path: /etc/modprobe.d/blacklist.conf 28 | regexp: "^(#)?blacklist dccp(\\s|$)" 29 | line: "blacklist dccp" 30 | create: true 31 | mode: 'u-x,go-rwx' 32 | 33 | - name: "3.2.2 | PATCH | Ensure tipc kernel module is not available" 34 | when: rhel9cis_rule_3_2_2 35 | tags: 36 | - level2-server 37 | - level2-workstation 38 | - patch 39 | - rule_3.2.2 40 | - tipc 41 | - NIST800-53R5_CM-7 42 | - NIST800-53R5_SI-4 43 | block: 44 | - name: "3.2.2 | PATCH | Ensure tipc kernel module is not available | modprobe" 45 | ansible.builtin.lineinfile: 46 | path: /etc/modprobe.d/tipc.conf 47 | regexp: '^(#)?install tipc(\\s|$)' 48 | line: "{{ item }}" 49 | create: true 50 | mode: 'u-x,go-rwx' 51 | loop: 52 | - install tipc /bin/true 53 | - blacklist tipc 54 | 55 | - name: "3.2.2 | PATCH | Ensure tipc kernel module is not available | blacklist" 56 | ansible.builtin.lineinfile: 57 | path: /etc/modprobe.d/blacklist.conf 58 | regexp: "^(#)?blacklist tipc(\\s|$)" 59 | line: "blacklist tipc" 60 | create: true 61 | mode: 'u-x,go-rwx' 62 | 63 | - name: "3.2.3 | PATCH | Ensure rds kernel module is not available" 64 | when: rhel9cis_rule_3_2_3 65 | tags: 66 | - level2-server 67 | - level2-workstation 68 | - patch 69 | - rule_3.2.3 70 | - rds 71 | - NIST800-53R5_CM-7 72 | - NIST800-53R5_SI-4 73 | block: 74 | - name: "3.2.3 | PATCH | Ensure rds kernel module is not available | modprobe" 75 | ansible.builtin.lineinfile: 76 | path: /etc/modprobe.d/rds.conf 77 | regexp: '^(#)?install rds(\\s|$)' 78 | line: "{{ item }}" 79 | create: true 80 | mode: 'u-x,go-rwx' 81 | loop: 82 | - install rds /bin/true 83 | - blacklist rds 84 | 85 | - name: "3.2.3 | PATCH | Ensure rds kernel module is not available | blacklist" 86 | ansible.builtin.lineinfile: 87 | path: /etc/modprobe.d/blacklist.conf 88 | regexp: "^(#)?blacklist rds(\\s|$)" 89 | line: "blacklist rds" 90 | create: true 91 | mode: 'u-x,go-rwx' 92 | 93 | - name: "3.2.4 | PATCH | Ensure sctp kernel module is not available" 94 | when: rhel9cis_rule_3_2_4 95 | tags: 96 | - level2-server 97 | - level2-workstation 98 | - patch 99 | - rule_3.2.4 100 | - sctp 101 | - NIST800-53R5_CM-7 102 | - NIST800-53R5_SI-4 103 | block: 104 | - name: "3.2.4 | PATCH | Ensure sctp kernel module is not available | modprobe" 105 | ansible.builtin.lineinfile: 106 | path: /etc/modprobe.d/sctp.conf 107 | regexp: '^(#)?install sctp(\\s|$)' 108 | line: "{{ item }}" 109 | create: true 110 | mode: 'u-x,go-rwx' 111 | loop: 112 | - install sctp /bin/true 113 | - blacklist sctp 114 | 115 | - name: "3.2.4 | PATCH | Ensure sctp kernel module is not available | blacklist" 116 | ansible.builtin.lineinfile: 117 | path: /etc/modprobe.d/blacklist.conf 118 | regexp: "^(#)?blacklist sctp(\\s|$)" 119 | line: "blacklist sctp" 120 | create: true 121 | mode: 'u-x,go-rwx' 122 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.3.1.1 | PATCH | Ensure auditd is installed" 4 | when: rhel9cis_rule_6_3_1_1 5 | tags: 6 | - level2-server 7 | - level2-workstation 8 | - patch 9 | - auditd 10 | - rule_6.3.1.1 11 | - NIST800-53R5_AU-2 12 | - NIST800-53R5_AU-3 13 | - NIST800-53R5_AU-12 14 | - NIST800-53R5_SI-5 15 | block: 16 | - name: "6.3.1.1 | PATCH | Ensure auditd is installed | Install auditd packages" 17 | when: '"auditd" not in ansible_facts.packages' 18 | ansible.builtin.package: 19 | name: audit 20 | state: present 21 | 22 | - name: "6.3.1.1 | PATCH | Ensure auditd is installed | Install auditd-lib packages" 23 | when: '"auditd-lib" not in ansible_facts.packages' 24 | ansible.builtin.package: 25 | name: audit-libs 26 | state: present 27 | 28 | - name: "6.3.1.2 | PATCH | Ensure auditing for processes that start prior to auditd is enabled" 29 | when: rhel9cis_rule_6_3_1_2 30 | tags: 31 | - level2-server 32 | - level2-workstation 33 | - patch 34 | - auditd 35 | - grub 36 | - rule_6.3.1.2 37 | block: 38 | - name: "6.3.1.2 | PATCH | Ensure auditing for processes that start prior to auditd is enabled | Grubby existence of current value" 39 | ansible.builtin.shell: grubby --info=ALL | grep args | sed -n 's/.*audit=\([[:alnum:]]\+\).*/\1/p' 40 | changed_when: false 41 | failed_when: false 42 | check_mode: false 43 | register: discovered_grubby_curr_value_audit_linux 44 | 45 | - name: "6.3.1.2 | PATCH | Ensure auditing for processes that start prior to auditd is enabled | Grubby update, if needed" 46 | when: 47 | - discovered_grubby_curr_value_audit_linux.stdout == '' or 48 | '0' in discovered_grubby_curr_value_audit_linux.stdout or 49 | 'off' in discovered_grubby_curr_value_audit_linux.stdout|lower 50 | ansible.builtin.command: grubby --update-kernel=ALL --args="audit=1" 51 | changed_when: true 52 | 53 | - name: "6.3.1.3 | PATCH | Ensure audit_backlog_limit is sufficient" 54 | when: rhel9cis_rule_6_3_1_3 55 | tags: 56 | - level2-server 57 | - level2-workstation 58 | - patch 59 | - auditd 60 | - grub 61 | - rule_6.3.1.3 62 | - NIST800-53R5_AU-2 63 | - NIST800-53R5_AU-12 64 | - NIST800-53R5_SI-5 65 | block: 66 | - name: "6.3.1.3 | AUDIT | Ensure audit_backlog_limit is sufficient | Grubby existence of current value" 67 | ansible.builtin.shell: 68 | cmd: 'grubby --info=ALL | grep args | grep -o -E "audit_backlog_limit=([[:digit:]])+" | grep -o -E "([[:digit:]])+"' 69 | changed_when: false 70 | failed_when: false 71 | check_mode: false 72 | register: discovered_grubby_curr_value_backlog_linux 73 | 74 | - name: "6.3.1.3 | AUDIT | Check to see if limits are set" 75 | when: 76 | - discovered_grubby_curr_value_backlog_linux is not defined or 77 | discovered_grubby_curr_value_backlog_linux.stdout_lines == [] 78 | ansible.builtin.set_fact: 79 | discovered_reset_backlog_limits: true 80 | 81 | - name: "6.3.1.3 | AUDIT | Check to see if any limits are too low" 82 | when: (item | int < rhel9cis_audit_back_log_limit) 83 | ansible.builtin.set_fact: 84 | discovered_reset_backlog_limits: true 85 | loop: "{{ discovered_grubby_curr_value_backlog_linux.stdout_lines }}" 86 | 87 | - name: "6.3.1.3 | AUDIT | Ensure audit_backlog_limit is sufficient | Grubby update applied" 88 | when: discovered_reset_backlog_limits is defined 89 | ansible.builtin.command: 90 | cmd: 'grubby --update-kernel=ALL --args="audit_backlog_limit={{ rhel9cis_audit_back_log_limit }}"' 91 | changed_when: true 92 | 93 | - name: "6.3.1.4 | PATCH | Ensure auditd service is enabled and active" 94 | when: rhel9cis_rule_6_3_1_4 95 | tags: 96 | - level2-server 97 | - level2-workstation 98 | - patch 99 | - auditd 100 | - rule_6.3.1.4 101 | - NIST800-53R5_AU-2 102 | - NIST800-53R5_AU-12 103 | - NIST800-53R5_SI-5 104 | ansible.builtin.systemd: 105 | name: auditd 106 | state: started 107 | enabled: true 108 | -------------------------------------------------------------------------------- /tasks/pre_remediation_audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Pre Audit Setup | Setup the LE audit 4 | when: setup_audit 5 | tags: setup_audit 6 | ansible.builtin.include_tasks: 7 | file: LE_audit_setup.yml 8 | 9 | - name: Pre Audit Setup | Ensure existence of {{ audit_conf_dir }} # noqa name[template] 10 | ansible.builtin.file: 11 | path: "{{ audit_conf_dir }}" 12 | mode: 'go-w' 13 | state: directory 14 | 15 | - name: Pre Audit Setup | If using git for content set up 16 | when: audit_content == 'git' 17 | block: 18 | - name: Pre Audit Setup | Install git 19 | ansible.builtin.package: 20 | name: git 21 | state: present 22 | 23 | - name: Pre Audit Setup | Retrieve audit content files from git 24 | ansible.builtin.git: 25 | repo: "{{ audit_file_git }}" 26 | dest: "{{ audit_conf_dir }}" 27 | version: "{{ audit_git_version }}" 28 | 29 | - name: Pre Audit Setup | Copy to audit content files to server 30 | when: audit_content == 'copy' 31 | ansible.builtin.copy: 32 | src: "{{ audit_conf_source }}" 33 | dest: "{{ audit_conf_dest }}" 34 | mode: preserve 35 | 36 | - name: Pre Audit Setup | Unarchive audit content files on server 37 | when: audit_content == 'archive' 38 | ansible.builtin.unarchive: 39 | src: "{{ audit_conf_source }}" 40 | dest: "{{ audit_conf_dest }}" 41 | 42 | - name: Pre Audit Setup | Get audit content from url 43 | when: audit_content == 'get_url' 44 | ansible.builtin.unarchive: 45 | src: "{{ audit_conf_source }}" 46 | dest: "{{ audit_conf_dest }}/{{ benchmark }}-Audit" 47 | remote_src: "{{ (audit_conf_source is contains('http')) | ternary(true, false) }}" 48 | extra_opts: "{{ (audit_conf_source is contains('github')) | ternary('--strip-components=1', []) }}" 49 | 50 | - name: Pre Audit Setup | Check Goss is available 51 | when: run_audit 52 | block: 53 | - name: Pre Audit Setup | Check for goss file 54 | ansible.builtin.stat: 55 | path: "{{ audit_bin }}" 56 | register: prelim_goss_available 57 | 58 | - name: Pre Audit Setup | If audit ensure goss is available 59 | when: not prelim_goss_available.stat.exists 60 | ansible.builtin.assert: 61 | that: prelim_goss_available['stat']['exists'] == true 62 | msg: "Audit has been selected: unable to find goss binary at {{ audit_bin }}" 63 | 64 | - name: Pre Audit Setup | Copy ansible default vars values to test audit 65 | when: run_audit 66 | tags: 67 | - goss_template 68 | - run_audit 69 | ansible.builtin.template: 70 | src: ansible_vars_goss.yml.j2 71 | dest: "{{ audit_vars_path }}" 72 | mode: 'go-rwx' 73 | 74 | - name: Pre Audit | Run pre_remediation audit {{ benchmark }} # noqa name[template] 75 | ansible.builtin.shell: "umask 0022 && {{ audit_conf_dir }}/run_audit.sh -v {{ audit_vars_path }} -f {{ audit_format }} -m {{ audit_max_concurrent }} -o {{ pre_audit_outfile }} -g \"{{ group_names }}\"" # noqa yaml[line-length] 76 | changed_when: true 77 | environment: 78 | AUDIT_BIN: "{{ audit_bin }}" 79 | AUDIT_CONTENT_LOCATION: "{{ audit_conf_dest | default('/opt') }}" 80 | AUDIT_FILE: goss.yml 81 | 82 | - name: Pre Audit | Capture audit data if json format 83 | when: audit_format == "json" 84 | block: 85 | - name: Pre Audit | Capture audit data if json format 86 | ansible.builtin.shell: grep -E '\"summary-line.*Count:.*Failed' "{{ pre_audit_outfile }}" | cut -d'"' -f4 87 | changed_when: false 88 | failed_when: pre_audit_summary.stderr | length > 0 89 | register: pre_audit_summary 90 | 91 | - name: Pre Audit | Set Fact for audit summary 92 | ansible.builtin.set_fact: 93 | pre_audit_results: "{{ pre_audit_summary.stdout }}" 94 | 95 | - name: Pre Audit | Capture audit data if documentation format 96 | when: audit_format == "documentation" 97 | block: 98 | - name: Pre Audit | Capture audit data if documentation format 99 | ansible.builtin.shell: tail -2 "{{ pre_audit_outfile }}" | tac | tr '\n' ' ' 100 | changed_when: false 101 | failed_when: pre_audit_summary.stderr | length > 0 102 | register: pre_audit_summary 103 | 104 | - name: Pre Audit | Set Fact for audit summary 105 | ansible.builtin.set_fact: 106 | pre_audit_results: "{{ pre_audit_summary.stdout }}" 107 | 108 | - name: Audit_Only | Run Audit Only 109 | when: audit_only 110 | ansible.builtin.import_tasks: 111 | file: audit_only.yml 112 | -------------------------------------------------------------------------------- /tasks/section_3/cis_3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # The CIS Control wants IPv6 disabled if not in use. 4 | # We are using the rhel9cis_ipv6_required to specify if you have IPv6 in use 5 | - name: "3.1.1 | PATCH | Ensure IPv6 status is identified" 6 | when: 7 | - not rhel9cis_ipv6_required 8 | - rhel9cis_rule_3_1_1 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - manual 13 | - patch 14 | - ipv6 15 | - networking 16 | - rule_3.1.1 17 | - NIST800-53R5_CM-7 18 | block: 19 | - name: "3.1.1 | PATCH | Ensure IPv6 status is identified | Set vars for sysctl template" 20 | when: "'sysctl' in rhel9cis_ipv6_disable_method" 21 | ansible.builtin.set_fact: 22 | rhel9cis_sysctl_update: true 23 | rhel9cis_flush_ipv6_route: true 24 | 25 | - name: "3.1.1 | AUDIT | Ensure IPv6 status is identified | Message out implementation info" 26 | when: "'sysctl' in rhel9cis_ipv6_disable_method" 27 | ansible.builtin.debug: 28 | msg: "Control being set via Handler 'update sysctl' which writes to /etc/sysctl.d/60-disable_ipv6.conf" 29 | 30 | - name: "3.1.1 | AUDIT | Ensure IPv6 status is identified | Find IPv6 status" 31 | when: "'kernel' in rhel9cis_ipv6_disable_method" 32 | ansible.builtin.command: grubby --info=ALL 33 | changed_when: false 34 | failed_when: false 35 | register: discovered_rhel9cis_3_1_1_ipv6_status 36 | 37 | - name: "3.1.1 | PATCH | Ensure IPv6 status is identified | Disable IPV6 via Kernel" 38 | when: 39 | - "'kernel' in rhel9cis_ipv6_disable_method" 40 | - "'ipv6.disable=1' not in discovered_rhel9cis_3_1_1_ipv6_status.stdout" 41 | ansible.builtin.shell: grubby --update-kernel=ALL --args="ipv6.disable=1" 42 | 43 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled" 44 | when: 45 | - rhel9cis_rule_3_1_2 46 | - discover_wireless_adapters.rc == 0 47 | tags: 48 | - level1-server 49 | - patch 50 | - rule_3.1.2 51 | - wireless 52 | - NIST800-53R5_CM-7 53 | vars: 54 | warn_control_id: '3.1.2' 55 | block: 56 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Check for network-manager tool" 57 | when: "rhel9cis_network_manager_package_name in ansible_facts.packages" 58 | ansible.builtin.command: nmcli radio wifi 59 | changed_when: false 60 | failed_when: false 61 | check_mode: false 62 | register: discovered_wifi_status 63 | 64 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Disable wireless if network-manager installed" 65 | when: 66 | - "rhel9cis_network_manager_package_name in ansible_facts.packages" 67 | - "'enabled' in discovered_wifi_status.stdout" 68 | ansible.builtin.command: nmcli radio all off 69 | changed_when: discovered_nmcli_radio_off.rc == 0 70 | register: discovered_nmcli_radio_off 71 | 72 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Warn about wireless if network-manager not installed" 73 | when: "rhel9cis_network_manager_package_name not in ansible_facts.packages" 74 | ansible.builtin.debug: 75 | msg: "Warning!! You need to disable wireless interfaces manually since network-manager is not installed" 76 | 77 | - name: "3.1.2 | PATCH | Ensure wireless interfaces are disabled | Set warning count" 78 | when: "rhel9cis_network_manager_package_name not in ansible_facts.packages" 79 | ansible.builtin.import_tasks: 80 | file: warning_facts.yml 81 | 82 | - name: "3.1.3 | PATCH | Ensure bluetooth services are not in use" 83 | when: rhel9cis_rule_3_1_3 84 | tags: 85 | - level1-server 86 | - level2-workstation 87 | - patch 88 | - bluetooth 89 | - rule_3.1.3 90 | - NIST800-53R5_CM-7 91 | block: 92 | - name: "3.1.3 | PATCH | Ensure bluetooth services are not in use | pkg" 93 | when: 94 | - not rhel9cis_bluetooth_service 95 | - not rhel9cis_bluetooth_mask 96 | ansible.builtin.package: 97 | name: bluez 98 | state: absent 99 | 100 | - name: "3.1.3 | PATCH | Ensure bluetooth services are not in use | mask" 101 | when: 102 | - not rhel9cis_bluetooth_service 103 | - rhel9cis_bluetooth_mask 104 | notify: Systemd daemon reload 105 | ansible.builtin.systemd: 106 | name: bluetooth.service 107 | enabled: false 108 | state: stopped 109 | masked: true 110 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.2.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.2.1.1 | PATCH | Ensure journald service is enabled and active" 4 | when: rhel9cis_rule_6_2_1_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - audit 9 | - journald 10 | - rule_6.2.1.1 11 | ansible.builtin.systemd: 12 | name: systemd-journald.service 13 | masked: false 14 | state: started 15 | 16 | - name: "6.2.1.2 | PATCH | Ensure journald log file access is configured" 17 | when: rhel9cis_rule_6_2_1_2 18 | tags: 19 | - level1-server 20 | - level1-workstation 21 | - audit 22 | - journald 23 | - rule_6.2.1.2 24 | block: 25 | - name: "6.2.1.2 | PATCH | Ensure journald log file access is configured | Default file permissions" 26 | ansible.builtin.file: 27 | path: /usr/lib/tmpfiles.d/systemd.conf 28 | mode: 'g-wx,o-rwx' 29 | 30 | - name: "6.2.1.2 | AUDIT | Ensure journald log file access is configured | Check for override file" 31 | ansible.builtin.stat: 32 | path: /etc/tmpfiles.d/systemd.conf 33 | register: discovered_tmpfile_override 34 | 35 | - name: "6.2.1.2 | AUDIT | Ensure journald log file access is configured | If override file check for journal" 36 | when: discovered_tmpfile_override.stat.exists 37 | ansible.builtin.shell: grep -E 'z /var/log/journal/%m/system.journal \d*' /usr/lib/tmpfiles.d/systemd.conf 38 | register: discovered_journald_fileperms_override 39 | changed_when: false 40 | failed_when: discovered_journald_fileperms_override.rc not in [ 0, 1 ] 41 | 42 | - name: "6.2.1.2 | AUDIT | Ensure journald log file access is configured | Warning if override found" 43 | when: 44 | - discovered_tmpfile_override.stat.exists 45 | - discovered_journald_fileperms_override.stdout | length > 0 46 | ansible.builtin.debug: 47 | msg: "Warning!! - tmpfiles override found /usr/lib/tmpfiles.d/systemd.conf affecting journald files please confirm matches site policy" 48 | 49 | - name: "6.2.1.2 | AUDIT | Ensure journald log file access is configured | Warning if override found" 50 | when: 51 | - discovered_tmpfile_override.stat.exists 52 | - discovered_journald_fileperms_override.stdout | length > 0 53 | ansible.builtin.import_tasks: 54 | file: warning_facts.yml 55 | vars: 56 | warn_control_id: '6.2.1.2' 57 | 58 | - name: "6.2.1.3 | PATCH | Ensure journald log file rotation is configured" 59 | when: rhel9cis_rule_6_2_1_3 60 | tags: 61 | - level1-server 62 | - level1-workstation 63 | - patch 64 | - journald 65 | - rule_6.2.1.3 66 | notify: Restart journald 67 | block: 68 | - name: "6.2.1.3 | PATCH | Ensure journald log file rotation is configured | Add file" 69 | ansible.builtin.template: 70 | src: etc/systemd/journald.conf.d/rotation.conf.j2 71 | dest: /etc/systemd/journald.conf.d/rotation.conf 72 | owner: root 73 | group: root 74 | mode: 'g-wx,o-rwx' 75 | 76 | - name: "6.2.1.3 | PATCH | Ensure journald log file rotation is configured | comment out current entries" 77 | ansible.builtin.replace: 78 | path: /etc/systemd/journald.conf 79 | regexp: "{{ item }}" 80 | replace: '#\1' 81 | loop: 82 | - '^(\s*SystemMaxUse\s*=.*)' 83 | - '^(\s*SystemKeepFree\s*=.*)' 84 | - '^(\s*RuntimeMaxUse\s*=)' 85 | - '^(\s*RuntimeKeepFree\s*=.*)' 86 | - '^(\s*MaxFileSec\s*=.*)' 87 | 88 | - name: "6.2.1.4 | PATCH | Ensure only one logging system is in use" 89 | when: rhel9cis_rule_6_2_1_4 90 | tags: 91 | - level1-server 92 | - level1-workstation 93 | - patch 94 | - journald 95 | - syslog 96 | - rule_6.2.1.4 97 | block: 98 | - name: "6.2.1.4 | PATCH | Ensure only one logging system is in use | when rsyslog" 99 | when: 100 | - rhel9cis_syslog == "rsyslog" 101 | - "'systemd-journald' in ansible_facts.packages" 102 | ansible.builtin.systemd: 103 | name: systemd-journald 104 | state: stopped 105 | enabled: false 106 | 107 | - name: "6.2.1.4 | PATCH | Ensure only one logging system is in use | when journald" 108 | when: 109 | - rhel9cis_syslog == "journald" 110 | - "'rsyslog' in ansible_facts.packages" 111 | ansible.builtin.systemd: 112 | name: rsyslog 113 | state: stopped 114 | enabled: false 115 | register: discovered_rsyslog_service 116 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.3.3.3.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.3.3.3.1 | PATCH | Ensure password history remember is configured" 4 | when: rhel9cis_rule_5_3_3_3_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - patch 9 | - rule_5.3.3.3.1 10 | - pam 11 | block: 12 | - name: "5.3.3.3.1 | AUDIT | Ensure password history remember is configured | Check existing files" 13 | ansible.builtin.shell: grep -Psi -- '^\h*password\s+[^#\n\r]+\h+pam_pwhistory\.so\s+([^#\n\r]+\s+)?remember=\d+\b' /etc/pam.d/password-auth /etc/pam.d/system-auth 14 | changed_when: false 15 | failed_when: discovered_pwhistory_remember.rc not in [0, 1] 16 | register: discovered_pwhistory_remember 17 | 18 | - name: "5.3.3.3.1 | PATCH | Ensure password number of changed characters is configured | Ensure remember is set pwhistory file" 19 | ansible.builtin.lineinfile: 20 | path: "/etc/security/pwhistory.conf" 21 | regexp: remember\s*=\s*\d* 22 | line: remember = {{ rhel9cis_pamd_pwhistory_remember }} 23 | 24 | - name: "5.3.3.3.1 | PATCH | Ensure password number of changed characters is configured | Remove remember from pam files NOT AuthSelect" 25 | when: 26 | - not rhel9cis_allow_authselect_updates 27 | - rhel9cis_disruption_high 28 | ansible.builtin.replace: 29 | path: "/etc/pam.d/{{ item }}-auth" 30 | regexp: ^(\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so)(.*)\sremember=\d*(.*$) 31 | replace: \1\2\3 32 | loop: 33 | - password 34 | - system 35 | 36 | - name: "5.3.3.3.1 | PATCH | Ensure password number of changed characters is configured | Remove remember from pam files AuthSelect" 37 | when: 38 | - rhel9cis_allow_authselect_updates 39 | - rhel9cis_disruption_high 40 | ansible.builtin.replace: 41 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 42 | regexp: ^(\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so)(.*)\sremember=\d*(.*$) 43 | replace: \1\2\3 44 | loop: 45 | - password 46 | - system 47 | notify: Authselect update 48 | 49 | - name: "5.3.3.3.2 | PATCH | Ensure password history is enforced for the root user" 50 | when: rhel9cis_rule_5_3_3_3_2 51 | tags: 52 | - level1-server 53 | - level1-workstation 54 | - patch 55 | - rule_5.3.3.3.2 56 | - pam 57 | ansible.builtin.lineinfile: 58 | path: "/etc/security/pwhistory.conf" 59 | regexp: ^\s*(?#)enforce_for_root 60 | line: enforce_for_root 61 | 62 | - name: "5.3.3.3.3 | PATCH | Ensure pam_pwhistory includes use_authtok" 63 | when: rhel9cis_rule_5_3_3_3_3 64 | tags: 65 | - level1-server 66 | - level1-workstation 67 | - patch 68 | - rule_5.3.3.3.3 69 | - pam 70 | block: 71 | - name: "5.3.3.3.3 | AUDIT | Ensure pam_pwhistory includes use_authtok | Check existing files" 72 | ansible.builtin.shell: grep -Psic -- '^\h*password\h+[^#\n\r]+\h+pam_pwhistory\.so\h+([^#\n\r]+\h+)?use_authtok\b' /etc/pam.d/{system,password}-auth 73 | register: discovered_pwhistory_use_authtok 74 | changed_when: false 75 | failed_when: discovered_pwhistory_use_authtok.rc not in [0, 1] 76 | 77 | - name: "5.3.3.3.3 | PATCH | Ensure pam_pwhistory includes use_authtok | Ensure use_authtok is set" 78 | when: 79 | - not rhel9cis_allow_authselect_updates 80 | - discovered_pwhistory_use_authtok.stdout | length == 0 81 | - rhel9cis_disruption_high 82 | ansible.builtin.lineinfile: 83 | path: "{{ item }}" 84 | regexp: ^password\s*pam_pwhistory\.so\s*.*\s(!?use_authtok) 85 | line: password required pam_pwhistory.so use_authtok 86 | insertbefore: ^password.*pam_deny.so 87 | loop: 88 | - /etc/pam.d/password-auth 89 | - /etc/pam.d/system-auth 90 | 91 | - name: "PATCH | Ensure pam_pwhistory includes use_authtok | add authtok to pam files AuthSelect" 92 | when: 93 | - rhel9cis_allow_authselect_updates 94 | - discovered_pwhistory_use_authtok.stdout | length == 0 95 | - rhel9cis_disruption_high 96 | ansible.builtin.lineinfile: 97 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 98 | regexp: ^(\s*password\s+(requisite|required|sufficient)\s+pam_pwhistory\.so)(.*)\suse_authtok(.*$) 99 | line: \1\2 use_authtok\3 100 | backrefs: true 101 | loop: 102 | - password 103 | - system 104 | notify: Authselect update 105 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.2.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.2.1.1 | AUDIT | Ensure GPG keys are configured" 4 | when: 5 | - rhel9cis_rule_1_2_1_1 6 | - ansible_facts.distribution == "RedHat" or 7 | ansible_facts.distribution == "Rocky" or 8 | ansible_facts.distribution == "AlmaLinux" 9 | tags: 10 | - level1-server 11 | - level1-workstation 12 | - manual 13 | - patch 14 | - rule_1.2.1.1 15 | - NIST800-53R5_SI-2 16 | block: 17 | - name: "1.2.1.1 | AUDIT | Ensure GPG keys are configured | List installed pubkey keys" 18 | ansible.builtin.shell: "rpm -qa | grep {{ os_gpg_key_pubkey_name }}" # noqa command-instead-of-module 19 | changed_when: false 20 | failed_when: false 21 | register: discovered_os_installed_pub_keys 22 | 23 | - name: "1.2.1.1 | AUDIT | Ensure GPG keys are configured | Query found keys" 24 | ansible.builtin.shell: | 25 | 'rpm -q --queryformat "%{PACKAGER} %{VERSION}\\n" {{ os_gpg_key_pubkey_name }} | grep "{{ os_gpg_key_pubkey_content }}"' 26 | changed_when: false 27 | failed_when: false 28 | register: discovered_os_gpg_key_check 29 | 30 | - name: "1.2.1.1 | AUDIT | Ensure GPG keys are configured | If expected keys fail" 31 | when: 32 | - discovered_os_installed_pub_keys.rc == 1 or 33 | discovered_os_gpg_key_check.rc == 1 34 | ansible.builtin.fail: 35 | msg: Installed GPG Keys do not meet expected values or expected keys are not installed 36 | 37 | - name: "1.2.1.2 | PATCH | Ensure gpgcheck is globally activated" 38 | when: rhel9cis_rule_1_2_1_2 39 | tags: 40 | - level1-server 41 | - level1-workstation 42 | - patch 43 | - rule_1.2.1.2 44 | - NIST800-53R5_SI-2 45 | block: 46 | - name: "1.2.1.2 | AUDIT | Ensure gpgcheck is globally activated | Find repos" 47 | ansible.builtin.find: 48 | paths: /etc/yum.repos.d 49 | patterns: "*.repo" 50 | register: discovered_yum_repos 51 | 52 | - name: "1.2.1.2 | PATCH | Ensure gpgcheck is globally activated | Update yum.repos" 53 | ansible.builtin.replace: 54 | name: "{{ item.path }}" 55 | regexp: ^gpgcheck\s*=\s*0 56 | replace: "gpgcheck=1" 57 | loop: "{{ discovered_yum_repos.files }}" 58 | loop_control: 59 | label: "{{ item.path }}" 60 | 61 | - name: "1.2.1.3 | AUDIT | Ensure repo_gpgcheck is globally activated" 62 | when: 63 | - rhel9cis_rule_1_2_1_3 64 | - rhel9cis_rule_enable_repogpg 65 | - not rhel9cis_rhel_default_repo 66 | tags: 67 | - level1-server 68 | - level1-workstation 69 | - manual 70 | - audit 71 | - rule_1.2.1.3 72 | - NIST800-53R5_SI-2 73 | block: 74 | - name: "1.2.1.3 | PATCH | Ensure repo_gpgcheck is globally activated | dnf.conf" 75 | ansible.builtin.lineinfile: 76 | path: /etc/dnf/dnf.conf 77 | regexp: '^repo_gpgcheck' 78 | line: repo_gpgcheck=1 79 | 80 | - name: "1.2.1.3 | AUDIT| Ensure repo_gpgcheck is globally activated | get repo files" 81 | ansible.builtin.find: 82 | paths: /etc/yum.repos.d 83 | patterns: "*.repo" 84 | register: discovered_repo_files 85 | 86 | - name: "1.2.1.3 | PATCH | Ensure repo_gpgcheck is globally activated | amend repo files" 87 | ansible.builtin.replace: 88 | path: "{{ item.path }}" 89 | regexp: ^repo_gpgcheck\s*=s*0 90 | replace: repo_gpgcheck=1 91 | loop: "{{ discovered_repo_files.files }}" 92 | loop_control: 93 | label: "{{ item.path }}" 94 | 95 | - name: "1.2.1.4 | AUDIT | Ensure package manager repositories are configured" 96 | when: rhel9cis_rule_1_2_1_4 97 | tags: 98 | - level1-server 99 | - level1-workstation 100 | - manual 101 | - audit 102 | - rule_1.2.1.4 103 | - NIST800-53R5_SI-2 104 | vars: 105 | warn_control_id: '1.2.1.4' 106 | block: 107 | - name: "1.2.1.4 | AUDIT | Ensure package manager repositories are configured | Get repo list" 108 | ansible.builtin.command: dnf repolist 109 | changed_when: false 110 | failed_when: false 111 | check_mode: false 112 | register: discovered_dnf_configured 113 | 114 | - name: "1.2.1.4 | AUDIT | Ensure package manager repositories are configured | Display repo list" 115 | ansible.builtin.debug: 116 | msg: 117 | - "Warning!! Below are the configured repos. Please review and make sure all align with site policy" 118 | - "{{ discovered_dnf_configured.stdout_lines }}" 119 | 120 | - name: "1.2.1.4 | AUDIT | Ensure package manager repositories are configured | Warn Count" 121 | ansible.builtin.import_tasks: 122 | file: warning_facts.yml 123 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.1.2.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.1.2.1.1 | PATCH | Ensure /tmp is a separate partition" 4 | when: 5 | - required_mount not in prelim_mount_names 6 | - rhel9cis_rule_1_1_2_1_1 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - audit 11 | - mounts 12 | - rule_1.1.2.1.1 13 | - NIST800-53R5_CM-7 14 | vars: 15 | warn_control_id: "1.1.2.1.1" 16 | required_mount: "/tmp" 17 | block: 18 | - name: "1.1.2.1.1 | AUDIT | Ensure /tmp is a separate partition | check for mount" 19 | ansible.builtin.command: findmnt -kn "{{ required_mount }}" 20 | changed_when: false 21 | failed_when: discovered_tmp_mount.rc not in [ 0, 1 ] 22 | register: discovered_tmp_mount 23 | 24 | - name: "1.1.2.1.1 | AUDIT | Ensure /tmp is a separate partition | Absent" 25 | when: discovered_tmp_mount is undefined 26 | ansible.builtin.debug: 27 | msg: "Warning!! {{ required_mount }} is not mounted on a separate partition" 28 | 29 | - name: "1.1.2.1.1 | AUDIT | Ensure /tmp is a separate partition | Present" 30 | when: discovered_tmp_mount is undefined 31 | ansible.builtin.import_tasks: 32 | file: warning_facts.yml 33 | 34 | # via fstab 35 | - name: "1.1.2.1.2 | PATCH | Ensure nodev option set on /tmp partition" 36 | when: 37 | - prelim_mount_point_fs_and_options[mount_point] is defined 38 | - not prelim_mount_point_fs_and_options[mount_point]['src'] == "tmpfs" 39 | - rhel9cis_rule_1_1_2_1_2 40 | - not rhel9cis_tmp_svc 41 | tags: 42 | - level1-server 43 | - level1-workstation 44 | - patch 45 | - mounts 46 | - rule_1.1.2.1.2 47 | - NIST800-53R5_AC-3 48 | - NIST800-53R5_MP-2 49 | vars: 50 | mount_point: "/tmp" 51 | required_option: nodev 52 | notify: &mount_option_notify 53 | - "Remount {{ mount_point }}" 54 | ansible.builtin.set_fact: &mount_option_set_fact 55 | prelim_mount_point_fs_and_options: | 56 | {{ prelim_mount_point_fs_and_options | combine({mount_point: {'options': (prelim_mount_point_fs_and_options[mount_point]['options'] + [required_option])}}, recursive=True) }} 57 | changed_when: &mount_option_changed_when 58 | - required_option not in prelim_mount_point_fs_and_options[mount_point]['original_options'] 59 | 60 | - name: "1.1.2.1.3 | PATCH | Ensure nosuid option set on /tmp partition" 61 | when: 62 | - prelim_mount_point_fs_and_options[mount_point] is defined 63 | - not prelim_mount_point_fs_and_options[mount_point]['src'] == "tmpfs" 64 | - rhel9cis_rule_1_1_2_1_3 65 | - not rhel9cis_tmp_svc 66 | tags: 67 | - level1-server 68 | - level1-workstation 69 | - patch 70 | - mounts 71 | - rule_1.1.2.1.3 72 | - NIST800-53R5_AC-3 73 | - NIST800-53R5_MP-2 74 | vars: 75 | mount_point: "/tmp" 76 | required_option: nosuid 77 | notify: *mount_option_notify 78 | ansible.builtin.set_fact: 79 | <<: *mount_option_set_fact 80 | changed_when: *mount_option_changed_when 81 | 82 | - name: "1.1.2.1.4 | PATCH | Ensure noexec option set on /tmp partition" 83 | when: 84 | - prelim_mount_point_fs_and_options[mount_point] is defined 85 | - not prelim_mount_point_fs_and_options[mount_point]['src'] == "tmpfs" 86 | - rhel9cis_rule_1_1_2_1_4 87 | - not rhel9cis_tmp_svc 88 | tags: 89 | - level1-server 90 | - level1-workstation 91 | - patch 92 | - mounts 93 | - rule_1.1.2.1.4 94 | - NIST800-53R5_AC-3 95 | - NIST800-53R5_MP-2 96 | vars: 97 | mount_point: "/tmp" 98 | required_option: noexec 99 | notify: *mount_option_notify 100 | ansible.builtin.set_fact: 101 | <<: *mount_option_set_fact 102 | changed_when: *mount_option_changed_when 103 | 104 | # via systemd 105 | - name: | 106 | "1.1.2.1.1 | PATCH | Ensure /tmp is configured 107 | 1.1.2.1.2 | PATCH | Ensure nodev option set on /tmp partition 108 | 1.1.2.1.3 | PATCH | Ensure noexec option set on /tmp partition 109 | 1.1.2.1.4 | PATCH | Ensure nosuid option set on /tmp partition" 110 | when: 111 | - rhel9cis_tmp_svc 112 | - rhel9cis_rule_1_1_2_1_1 or rhel9cis_rule_1_1_2_1_2 or rhel9cis_rule_1_1_2_1_3 or rhel9cis_rule_1_1_2_1_4 113 | tags: 114 | - level1-server 115 | - level1-workstation 116 | - patch 117 | - mounts 118 | - rule_1.1.2.1.1 119 | - rule_1.1.2.1.2 120 | - rule_1.1.2.1.3 121 | - rule_1.1.2.1.4 122 | - NIST800-53R5_AC-3 123 | - NIST800-53R5_MP-2 124 | vars: 125 | mount_point: "/tmp" 126 | ansible.builtin.template: 127 | src: etc/systemd/system/tmp.mount.j2 128 | dest: /etc/systemd/system/tmp.mount 129 | owner: root 130 | group: root 131 | mode: 'go-wx' 132 | notify: *mount_option_notify 133 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.3.1.1 | PATCH | Ensure SELinux is installed" 4 | when: 5 | - rhel9cis_rule_1_3_1_1 6 | - not rhel9cis_selinux_disable 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - rule_1.3.1.1 12 | - NIST800-53R5_AC-3 13 | - NIST800-53R5_MP-2 14 | ansible.builtin.package: 15 | name: libselinux 16 | state: present 17 | 18 | - name: "1.3.1.2 | PATCH | Ensure SELinux is not disabled in bootloader configuration" 19 | when: 20 | - rhel9cis_rule_1_3_1_2 21 | - not rhel9cis_selinux_disable 22 | tags: 23 | - level1-server 24 | - level1-workstation 25 | - scored 26 | - patch 27 | - rule_1.3.1.2 28 | - NIST800-53R5_AC-3 29 | - NIST800-53R5_MP-2 30 | ansible.builtin.replace: 31 | path: /etc/default/grub 32 | regexp: '{{ item }}' 33 | replace: '' 34 | loop: 35 | - selinux=0 36 | - enforcing=0 37 | ignore_errors: true # noqa ignore-errors 38 | notify: Grub2cfg 39 | 40 | # State set to enforcing because control 1.3.1.5 requires enforcing to be set 41 | - name: "1.3.1.3 | PATCH | Ensure SELinux policy is configured" 42 | when: 43 | - rhel9cis_rule_1_3_1_3 44 | - not rhel9cis_selinux_disable 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - selinux 49 | - patch 50 | - rule_1.3.1.3 51 | - NIST800-53R5_AC-3 52 | - NIST800-53R5_MP-2 53 | ansible.posix.selinux: 54 | conf: /etc/selinux/config 55 | policy: "{{ rhel9cis_selinux_pol }}" 56 | state: "{{ rhel9cis_selinux_enforce }}" 57 | 58 | - name: "1.3.1.4 | PATCH | Ensure the SELinux state is not disabled" 59 | when: 60 | - rhel9cis_rule_1_3_1_4 61 | - not rhel9cis_selinux_disable 62 | tags: 63 | - level1-server 64 | - level1-workstation 65 | - selinux 66 | - patch 67 | - rule_1.3.1.4 68 | - NIST800-53R5_AC-3 69 | - NIST800-53R5_MP-2 70 | ansible.posix.selinux: 71 | conf: /etc/selinux/config 72 | policy: "{{ rhel9cis_selinux_pol }}" 73 | state: "{{ rhel9cis_selinux_enforce }}" 74 | 75 | - name: "1.3.1.5 | PATCH | Ensure the SELinux state is enforcing" 76 | when: 77 | - rhel9cis_selinux_enforce == 'enforcing' 78 | - rhel9cis_rule_1_3_1_5 79 | - not rhel9cis_selinux_disable 80 | tags: 81 | - level2-server 82 | - level2-workstation 83 | - selinux 84 | - patch 85 | - rule_1.3.1.5 86 | - NIST800-53R4_AC-3 87 | - NIST800-53R4_SI-6 88 | ansible.posix.selinux: 89 | conf: /etc/selinux/config 90 | policy: "{{ rhel9cis_selinux_pol }}" 91 | state: enforcing 92 | 93 | - name: "1.3.1.6 | AUDIT | Ensure no unconfined services exist" 94 | when: 95 | - rhel9cis_rule_1_3_1_6 96 | - not rhel9cis_selinux_disable 97 | tags: 98 | - level1-server 99 | - level1-workstation 100 | - audit 101 | - services 102 | - rule_1.3.1.6 103 | - NIST800-53R5_AC-3 104 | - NIST800-53R5_MP-2 105 | vars: 106 | warn_control_id: '1.3.1.6' 107 | block: 108 | - name: "1.3.1.6 | AUDIT | Ensure no unconfined services exist | Find the unconfined services" 109 | ansible.builtin.shell: ps -eZ | grep unconfined_service_t | grep -Evw "tr|ps|egrep|bash|awk" | tr ':' ' ' | awk '{ print $NF }' 110 | register: discovered_unconf_services 111 | failed_when: false 112 | changed_when: false 113 | 114 | - name: "1.3.1.6 | AUDIT | Ensure no unconfined services exist | Message on unconfined services" 115 | when: discovered_unconf_services.stdout | length > 0 116 | ansible.builtin.debug: 117 | msg: "Warning!! You have unconfined services: {{ discovered_unconf_services.stdout_lines }}" 118 | 119 | - name: "1.3.1.6 | AUDIT | Ensure no unconfined services exist | warning count" 120 | when: discovered_unconf_services.stdout | length > 0 121 | ansible.builtin.import_tasks: 122 | file: warning_facts.yml 123 | 124 | - name: "1.3.1.7 | PATCH | Ensure the MCS Translation Service (mcstrans) is not installed" 125 | when: rhel9cis_rule_1_3_1_7 126 | tags: 127 | - level1-server 128 | - level1-workstation 129 | - patch 130 | - rule_1.3.1.7 131 | - NIST800-53R5_AC-3 132 | - NIST800-53R5_MP-2 133 | ansible.builtin.package: 134 | name: mcstrans 135 | state: absent 136 | 137 | - name: "1.3.1.8 | PATCH | Ensure SETroubleshoot is not installed" 138 | when: 139 | - rhel9cis_rule_1_3_1_8 140 | - "'setroubleshoot' in ansible_facts.packages" 141 | tags: 142 | - level1-server 143 | - selinux 144 | - patch 145 | - rule_1.3.1.8 146 | - NIST800-53R5_AC-3 147 | - NIST800-53R5_MP-2 148 | ansible.builtin.package: 149 | name: setroubleshoot 150 | state: absent 151 | -------------------------------------------------------------------------------- /tasks/section_6/cis_6.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "6.1.1 | PATCH | Ensure AIDE is installed" 4 | when: 5 | - rhel9cis_config_aide 6 | - rhel9cis_rule_6_1_1 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - aide 11 | - patch 12 | - rule_6.1.1 13 | - NIST800-53R5_AU-2 14 | block: 15 | - name: "6.1.1 | PATCH | Ensure AIDE is installed" 16 | ansible.builtin.package: 17 | name: "{{ aide_packages }}" 18 | state: present 19 | update_cache: true 20 | register: discovered_aide_pkg_added 21 | 22 | - name: "6.1.1 | PATCH | Ensure AIDE is installed | Recapture packages" 23 | when: discovered_aide_pkg_added.skipped is not defined 24 | ansible.builtin.package_facts: 25 | manager: auto 26 | 27 | - name: "6.1.1 | AUDIT | Ensure AIDE is installed | Check file exists" 28 | ansible.builtin.stat: 29 | path: "{{ rhel9cis_aide_db_file }}" 30 | register: discovered_aide_db_file 31 | 32 | - name: "6.1.1 | AUDIT | Ensure AIDE is installed | Check current db file age" 33 | when: discovered_aide_db_file.stat.exists 34 | ansible.builtin.find: 35 | path: "{{ rhel9cis_aide_db_file | dirname }}" 36 | pattern: "{{ rhel9cis_aide_db_file | basename }}" 37 | age: "{{ rhel9cis_aide_db_file_age }}" 38 | register: discovered_aide_db_age 39 | 40 | - name: "6.1.1 | PATCH | Ensure AIDE is installed | Configure AIDE" 41 | when: 42 | - not ansible_check_mode 43 | - not discovered_aide_db_file.stat.exists or 44 | (discovered_aide_db_age.files | length > 0) or 45 | rhel9cis_aide_db_recreate 46 | block: 47 | - name: "6.1.1 | PATCH | Ensure AIDE is installed | Build AIDE DB" 48 | ansible.builtin.command: "{{ aide_initiate_command }}" 49 | changed_when: true 50 | 51 | - name: "6.1.1 | PATCH | Ensure AIDE is installed | Build AIDE DB | Wait for file before continuing" 52 | ansible.builtin.wait_for: 53 | path: /var/lib/aide/aide.db.new.gz 54 | 55 | - name: "6.1.1 | PATCH | Ensure AIDE is installed | Build AIDE DB |copy AIDE DB" 56 | ansible.builtin.copy: 57 | src: /var/lib/aide/aide.db.new.gz 58 | dest: /var/lib/aide/aide.db.gz 59 | remote_src: true 60 | mode: 'ug-wx,o-rwx' 61 | register: aide_db_cp 62 | failed_when: 63 | - not ansible_check_mode 64 | - aide_db_cp.failed 65 | 66 | - name: "6.1.2 | PATCH | Ensure filesystem integrity is regularly checked" 67 | when: 68 | - rhel9cis_rule_6_1_2 69 | - not system_is_ec2 70 | tags: 71 | - level1-server 72 | - level1-workstation 73 | - aide 74 | - file_integrity 75 | - patch 76 | - rule_6.1.2 77 | - NIST800-53R5_AU-2 78 | block: 79 | - name: "6.1.2 | PATCH | Ensure filesystem integrity is regularly checked" 80 | when: rhel9cis_aide_scan == "cron" 81 | ansible.builtin.cron: 82 | name: Run AIDE integrity check 83 | cron_file: "{{ rhel9cis_aide_cron['cron_file'] }}" 84 | user: "{{ rhel9cis_aide_cron['cron_user'] }}" 85 | minute: "{{ rhel9cis_aide_cron['aide_minute'] | default('0') }}" 86 | hour: "{{ rhel9cis_aide_cron['aide_hour'] | default('5') }}" 87 | day: "{{ rhel9cis_aide_cron['aide_day'] | default('*') }}" 88 | month: "{{ rhel9cis_aide_cron['aide_month'] | default('*') }}" 89 | weekday: "{{ rhel9cis_aide_cron['aide_weekday'] | default('*') }}" 90 | job: "{{ rhel9cis_aide_cron['aide_job'] }}" 91 | 92 | - name: "6.1.2 | PATCH | Ensure filesystem integrity is regularly checked | aide service" 93 | when: rhel9cis_aide_scan == "timer" 94 | ansible.builtin.systemd: 95 | name: aidecheck.service 96 | enabled: true 97 | 98 | - name: "6.1.2 | PATCH | Ensure filesystem integrity is regularly checked | aide service" 99 | when: rhel9cis_aide_scan == "timer" 100 | ansible.builtin.systemd: 101 | name: aidecheck.timer 102 | state: started 103 | enabled: true 104 | 105 | - name: "6.1.3 | PATCH | Ensure cryptographic mechanisms are used to protect the integrity of audit tools" 106 | when: 107 | - rhel9cis_rule_6_1_3 108 | - not system_is_ec2 109 | tags: 110 | - level1-server 111 | - level1-workstation 112 | - aide 113 | - file_integrity 114 | - patch 115 | - rule_6.1.3 116 | ansible.builtin.blockinfile: 117 | path: /etc/aide.conf 118 | marker: "# {mark} Audit tools - CIS benchmark - Ansible-lockdown" 119 | block: | 120 | /usr/sbin/auditctl p+i+n+u+g+s+b+acl+xattrs+sha512 121 | /usr/sbin/auditd p+i+n+u+g+s+b+acl+xattrs+sha512 122 | /usr/sbin/augenrules p+i+n+u+g+s+b+acl+xattrs+sha512 123 | /usr/sbin/aureport p+i+n+u+g+s+b+acl+xattrs+sha512 124 | /usr/sbin/ausearch p+i+n+u+g+s+b+acl+xattrs+sha512 125 | /usr/sbin/autrace p+i+n+u+g+s+b+acl+xattrs+sha512 126 | register: aide_file_integrity_check 127 | failed_when: 128 | - not ansible_check_mode 129 | - aide_file_integrity_check.failed 130 | -------------------------------------------------------------------------------- /tasks/section_2/cis_2.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "2.4.1.1 | PATCH | Ensure cron daemon is enabled" 4 | when: rhel9cis_rule_2_4_1_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - patch 9 | - cron 10 | - rule_2.4.1.1 11 | - NIST800-53R5_CM-1 12 | - NIST800-53R5_CM-2 13 | - NIST800-53R5_CM-6 14 | - NIST800-53R5_CM-7 15 | - NIST800-53R5_IA-5 16 | ansible.builtin.service: 17 | name: crond 18 | enabled: true 19 | 20 | - name: "2.4.1.2 | PATCH | Ensure permissions on /etc/crontab are configured" 21 | when: rhel9cis_rule_2_4_1_2 22 | tags: 23 | - level1-server 24 | - level1-workstation 25 | - patch 26 | - cron 27 | - rule_2.4.1.2 28 | - NIST800-53R5_AC-3 29 | - NIST800-53R5_MP-2 30 | ansible.builtin.file: 31 | path: /etc/crontab 32 | owner: root 33 | group: root 34 | mode: 'og-rwx' 35 | 36 | - name: "2.4.1.3 | PATCH | Ensure permissions on /etc/cron.hourly are configured" 37 | when: rhel9cis_rule_2_4_1_3 38 | tags: 39 | - level1-server 40 | - level1-workstation 41 | - patch 42 | - cron 43 | - rule_2.4.1.3 44 | - NIST800-53R5_AC-3 45 | - NIST800-53R5_MP-2 46 | ansible.builtin.file: 47 | path: /etc/cron.hourly 48 | state: directory 49 | owner: root 50 | group: root 51 | mode: 'og-rwx' 52 | 53 | - name: "2.4.1.4 | PATCH | Ensure permissions on /etc/cron.daily are configured" 54 | when: rhel9cis_rule_2_4_1_4 55 | tags: 56 | - level1-server 57 | - level1-workstation 58 | - patch 59 | - cron 60 | - rule_2.4.1.4 61 | ansible.builtin.file: 62 | path: /etc/cron.daily 63 | state: directory 64 | owner: root 65 | group: root 66 | mode: 'og-rwx' 67 | 68 | - name: "2.4.1.5 | PATCH | Ensure permissions on /etc/cron.weekly are configured" 69 | when: rhel9cis_rule_2_4_1_5 70 | tags: 71 | - level1-server 72 | - level1-workstation 73 | - patch 74 | - rule_2.4.1.5 75 | - NIST800-53R5_AC-3 76 | - NIST800-53R5_MP-2 77 | ansible.builtin.file: 78 | path: /etc/cron.weekly 79 | state: directory 80 | owner: root 81 | group: root 82 | mode: 'og-rwx' 83 | 84 | - name: "2.4.1.6 | PATCH | Ensure permissions on /etc/cron.monthly are configured" 85 | when: rhel9cis_rule_2_4_1_6 86 | tags: 87 | - level1-server 88 | - level1-workstation 89 | - patch 90 | - rule_2.4.1.6 91 | - NIST800-53R5_AC-3 92 | - NIST800-53R5_MP-2 93 | ansible.builtin.file: 94 | path: /etc/cron.monthly 95 | state: directory 96 | owner: root 97 | group: root 98 | mode: 'og-rwx' 99 | 100 | - name: "2.4.1.7 | PATCH | Ensure permissions on /etc/cron.d are configured" 101 | when: rhel9cis_rule_2_4_1_7 102 | tags: 103 | - level1-server 104 | - level1-workstation 105 | - patch 106 | - cron 107 | - rule_2.4.1.7 108 | - NIST800-53R5_AC-3 109 | - NIST800-53R5_MP-2 110 | ansible.builtin.file: 111 | path: /etc/cron.d 112 | state: directory 113 | owner: root 114 | group: root 115 | mode: 'og-rwx' 116 | 117 | - name: "2.4.1.8 | PATCH | Ensure crontab is restricted to authorized users" 118 | when: rhel9cis_rule_2_4_1_8 119 | tags: 120 | - level1-server 121 | - level1-workstation 122 | - patch 123 | - cron 124 | - rule_2.4.1.8 125 | - NIST800-53R5_AC-3 126 | - NIST800-53R5_MP-2 127 | block: 128 | - name: "2.4.1.8 | PATCH | Ensure crontab is restricted to authorized users | Remove cron.deny" 129 | ansible.builtin.file: 130 | path: /etc/cron.deny 131 | state: absent 132 | 133 | - name: "2.4.1.8 | PATCH | Ensure crontab is restricted to authorized users | Check if cron.allow exists" 134 | ansible.builtin.stat: 135 | path: "/etc/cron.allow" 136 | register: discovered_cron_allow_state 137 | 138 | - name: "2.4.1.8 | PATCH | Ensure crontab is restricted to authorized users | Ensure cron.allow is restricted to authorized users" 139 | ansible.builtin.file: 140 | path: /etc/cron.allow 141 | state: '{{ "file" if discovered_cron_allow_state.stat.exists else "touch" }}' 142 | owner: root 143 | group: root 144 | mode: 'u-x,g-wx,o-rwx' 145 | 146 | - name: "2.4.2.1 | PATCH | Ensure at is restricted to authorized users" 147 | when: rhel9cis_rule_2_4_2_1 148 | tags: 149 | - level1-server 150 | - level1-workstation 151 | - patch 152 | - cron 153 | - rule_2.4.2.1 154 | - NIST800-53R5_AC-3 155 | - NIST800-53R5_MP-2 156 | block: 157 | - name: "2.4.2.1 | PATCH | Ensure at is restricted to authorized users | Remove at.deny" 158 | ansible.builtin.file: 159 | path: /etc/at.deny 160 | state: absent 161 | 162 | - name: "2.4.2.1 | PATCH | Ensure at is restricted to authorized users | Check if at.allow exists" 163 | ansible.builtin.stat: 164 | path: "/etc/at.allow" 165 | register: discovered_at_allow_state 166 | 167 | - name: "2.4.2.1 | PATCH | Ensure at is restricted to authorized users | Ensure at.allow is restricted to authorized users" 168 | ansible.builtin.file: 169 | path: /etc/at.allow 170 | state: '{{ "file" if discovered_at_allow_state.stat.exists else "touch" }}' 171 | owner: root 172 | group: root 173 | mode: 'u-x,g-wx,o-rwx' 174 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.3.3.1.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.3.3.1.1 | PATCH | Ensure password failed attempts lockout is configured" 4 | when: rhel9cis_rule_5_3_3_1_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - automated 9 | - patch 10 | - pam 11 | - rule_5.3.3.1.1 12 | block: 13 | - name: "5.3.3.1.1 | PATCH | Ensure password failed attempts lockout is configured | faillock.conf" 14 | ansible.builtin.lineinfile: 15 | path: /etc/security/faillock.conf 16 | state: present 17 | regexp: '^(#|)\s*deny\s*=\s*\d' 18 | line: "deny = {{ rhel9cis_pam_faillock_deny }}" 19 | 20 | - name: "5.3.3.1.1 | PATCH | Ensure password failed attempts lockout is configured | remove deny from pam files NOT AuthSelect" 21 | when: 22 | - not rhel9cis_allow_authselect_updates 23 | - rhel9cis_disruption_high 24 | ansible.builtin.replace: 25 | path: "/etc/pam.d/{{ item }}-auth" 26 | regexp: ^(\s*auth\s+(?:requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s+deny\s*=\s*\S+(.*$) 27 | replace: \1 \2\3 28 | loop: 29 | - password 30 | - system 31 | 32 | - name: "5.3.3.1.1 | PATCH | Ensure password failed attempts lockout is configured | remove deny from AuthSelect config" 33 | when: 34 | - rhel9cis_allow_authselect_updates 35 | - rhel9cis_disruption_high 36 | ansible.builtin.replace: 37 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 38 | regexp: ^(\s*auth\s+(requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s+deny\s*=\s*\S+(.*$) 39 | replace: \1\2\3 40 | loop: 41 | - password 42 | - system 43 | notify: Authselect update 44 | 45 | - name: "5.3.3.1.2 | PATCH | Ensure password unlock time is configured" 46 | when: rhel9cis_rule_5_3_3_1_2 47 | tags: 48 | - level1-server 49 | - level1-workstation 50 | - automated 51 | - patch 52 | - pam 53 | - rule_5.3.3.1.2 54 | block: 55 | - name: "5.3.3.1.2 | PATCH | Ensure password unlock time is configured | faillock.conf" 56 | ansible.builtin.lineinfile: 57 | path: /etc/security/faillock.conf 58 | state: present 59 | regexp: '^(#|)\s*unlock_time\s*=\s*\d' 60 | line: "unlock_time = {{ rhel9cis_pam_faillock_unlock_time }}" 61 | 62 | - name: "5.3.3.1.2 | PATCH | Ensure password unlock time is configured | remove unlock from pam files NOT AuthSelect" 63 | when: 64 | - rhel9cis_disruption_high 65 | - not rhel9cis_allow_authselect_updates 66 | ansible.builtin.replace: 67 | path: "/etc/pam.d/{{ item }}-auth" 68 | regexp: ^(\s*auth\s+(requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s+unlock_time\s*=\s*\S+(.*$) 69 | replace: \1\2\3 70 | loop: 71 | - password 72 | - system 73 | 74 | - name: "5.3.3.1.2 | PATCH | Ensure password unlock time is configured | remove unlock from pam files AuthSelect" 75 | when: 76 | - rhel9cis_allow_authselect_updates 77 | - rhel9cis_disruption_high 78 | ansible.builtin.replace: 79 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 80 | regexp: ^(\s*auth\s+(requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s+unlock_time\s*=\s*\S+(.*$) 81 | replace: \1\2\3 82 | loop: 83 | - password 84 | - system 85 | notify: Authselect update 86 | 87 | - name: "5.3.3.1.3 | PATCH | Ensure password failed attempts lockout includes root account" 88 | when: rhel9cis_rule_5_3_3_1_3 89 | tags: 90 | - level1-server 91 | - level1-workstation 92 | - automated 93 | - patch 94 | - pam 95 | - rule_5.3.3.1.3 96 | block: 97 | - name: "5.3.3.1.3 | PATCH | Ensure password failed attempts lockout includes root account | configure faillock.conf" 98 | ansible.builtin.lineinfile: 99 | path: /etc/security/faillock.conf 100 | regexp: '^{{ rhel9cis_pamroot_lock_option }}' 101 | line: "{{ rhel9cis_pamroot_lock_option }}" 102 | insertafter: '^# end of pam-auth-update config' 103 | create: true 104 | mode: 'u-x,go-wx' 105 | 106 | - name: "5.3.3.1.3 | PATCH | Ensure password failed attempts lockout includes root account | remove lockout from pam files NOT AuthSelect" 107 | when: 108 | - rhel9cis_disruption_high 109 | - not rhel9cis_allow_authselect_updates 110 | ansible.builtin.replace: 111 | path: "/etc/pam.d/{{ item }}-auth" 112 | regexp: ^(\s*auth\s+(requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s(even_deny_root|root_unlock_time=\d*)"(\s*=\s*\d|.*)\S+(.*$) 113 | replace: \1\2\4 114 | loop: 115 | - password 116 | - system 117 | 118 | - name: "5.3.3.1.3 | PATCH | Ensure password failed attempts lockout includes root account | remove lockout from pam files AuthSelect" 119 | when: 120 | - rhel9cis_allow_authselect_updates 121 | - rhel9cis_disruption_high 122 | ansible.builtin.replace: 123 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 124 | regexp: ^(\s*auth\s+(requisite|required|sufficient)\s+pam_faillock\.so)(.*)\s(even_deny_root|root_unlock_time=\d*)"(\s*=\s*\d|.*)\S+(.*$) 125 | replace: \1\2\4 126 | loop: 127 | - password 128 | - system 129 | -------------------------------------------------------------------------------- /.github/workflows/main_pipeline_validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Main pipeline 4 | 5 | on: # yamllint disable-line rule:truthy 6 | pull_request_target: 7 | types: [opened, reopened, synchronize] 8 | branches: 9 | - main 10 | - latest 11 | paths: 12 | - '**.yml' 13 | - '**.sh' 14 | - '**.j2' 15 | - '**.ps1' 16 | - '**.cfg' 17 | 18 | # Allow permissions for AWS auth 19 | permissions: 20 | id-token: write 21 | contents: read 22 | pull-requests: read 23 | 24 | # A workflow run is made up of one or more jobs 25 | # that can run sequentially or in parallel 26 | jobs: 27 | # This workflow contains a single job that tests the playbook 28 | playbook-test: 29 | # The type of runner that the job will run on 30 | runs-on: self-hosted 31 | env: 32 | ENABLE_DEBUG: ${{ vars.ENABLE_DEBUG }} 33 | # Imported as a variable by terraform 34 | TF_VAR_repository: ${{ github.event.repository.name }} 35 | AWS_REGION : "us-east-1" 36 | ANSIBLE_VERSION: ${{ vars.ANSIBLE_RUNNER_VERSION }} 37 | defaults: 38 | run: 39 | shell: bash 40 | working-directory: .github/workflows/github_linux_IaC 41 | # working-directory: .github/workflows 42 | 43 | steps: 44 | 45 | - name: Git clone the lockdown repository to test 46 | uses: actions/checkout@v4 47 | with: 48 | ref: ${{ github.event.pull_request.head.sha }} 49 | 50 | - name: If a variable for IAC_BRANCH is set use that branch 51 | working-directory: .github/workflows 52 | run: | 53 | if [ ${{ vars.IAC_BRANCH }} != '' ]; then 54 | echo "IAC_BRANCH=${{ vars.IAC_BRANCH }}" >> $GITHUB_ENV 55 | echo "Pipeline using the following IAC branch ${{ vars.IAC_BRANCH }}" 56 | else 57 | echo IAC_BRANCH=main >> $GITHUB_ENV 58 | fi 59 | 60 | # Pull in terraform code for linux servers 61 | - name: Clone GitHub IaC plan 62 | uses: actions/checkout@v4 63 | with: 64 | repository: ansible-lockdown/github_linux_IaC 65 | path: .github/workflows/github_linux_IaC 66 | ref: ${{ env.IAC_BRANCH }} 67 | 68 | # Uses dedicated restricted role and policy to enable this only for this task 69 | # No credentials are part of github for AWS auth 70 | - name: configure aws credentials 71 | uses: aws-actions/configure-aws-credentials@main 72 | with: 73 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} 74 | role-session-name: ${{ secrets.AWS_ROLE_SESSION }} 75 | aws-region: ${{ env.AWS_REGION }} 76 | 77 | - name: DEBUG - Show IaC files 78 | if: env.ENABLE_DEBUG == 'true' 79 | run: | 80 | echo "OSVAR = $OSVAR" 81 | echo "benchmark_type = $benchmark_type" 82 | pwd 83 | ls 84 | env: 85 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 86 | OSVAR: ${{ vars.OSVAR }} 87 | benchmark_type: ${{ vars.BENCHMARK_TYPE }} 88 | 89 | - name: Tofu init 90 | id: init 91 | run: tofu init 92 | env: 93 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 94 | OSVAR: ${{ vars.OSVAR }} 95 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 96 | 97 | - name: Tofu validate 98 | id: validate 99 | run: tofu validate 100 | env: 101 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 102 | OSVAR: ${{ vars.OSVAR }} 103 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 104 | 105 | - name: Tofu apply 106 | id: apply 107 | env: 108 | OSVAR: ${{ vars.OSVAR }} 109 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 110 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 111 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 112 | run: tofu apply -var-file "${OSVAR}.tfvars" --auto-approve -input=false 113 | 114 | ## Debug Section 115 | - name: DEBUG - Show Ansible hostfile 116 | if: env.ENABLE_DEBUG == 'true' 117 | run: cat hosts.yml 118 | 119 | # Aws deployments taking a while to come up insert sleep or playbook fails 120 | 121 | - name: Sleep to allow system to come up 122 | run: sleep ${{ vars.BUILD_SLEEPTIME }} 123 | 124 | # Run the Ansible playbook 125 | - name: Run_Ansible_Playbook 126 | env: 127 | ANSIBLE_HOST_KEY_CHECKING: "false" 128 | ANSIBLE_DEPRECATION_WARNINGS: "false" 129 | run: | 130 | /opt/ansible_${{ env.ANSIBLE_VERSION }}_venv/bin/ansible-playbook -i hosts.yml --private-key ~/.ssh/le_runner ../../../site.yml 131 | 132 | # Remove test system - User secrets to keep if necessary 133 | 134 | - name: Tofu Destroy 135 | if: always() && env.ENABLE_DEBUG == 'false' 136 | env: 137 | OSVAR: ${{ vars.OSVAR }} 138 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 139 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 140 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 141 | run: tofu destroy -var-file "${OSVAR}.tfvars" --auto-approve -input=false 142 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.2.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.2.1 | PATCH | Ensure sudo is installed" 4 | when: rhel9cis_rule_5_2_1 5 | tags: 6 | - level1-server 7 | - level1-workstation 8 | - patch 9 | - sudo 10 | - rule_5.2.1 11 | - NIST800-53R5_AC-6 12 | ansible.builtin.package: 13 | name: sudo 14 | state: present 15 | 16 | - name: "5.2.2 | PATCH | Ensure sudo commands use pty" 17 | when: rhel9cis_rule_5_2_2 18 | tags: 19 | - level1-server 20 | - level1-workstation 21 | - patch 22 | - sudo 23 | - rule_5.2.2 24 | - NIST800-53R5_AC-6 25 | ansible.builtin.lineinfile: 26 | path: /etc/sudoers 27 | line: "Defaults use_pty" 28 | validate: '/usr/sbin/visudo -cf %s' 29 | 30 | - name: "5.2.3 | PATCH | Ensure sudo log file exists" 31 | when: rhel9cis_rule_5_2_3 32 | tags: 33 | - level1-server 34 | - level1-workstation 35 | - patch 36 | - sudo 37 | - rule_5.2.3 38 | - NIST800-53R5_AU-3 39 | - NIST800-53R5_AU-12 40 | ansible.builtin.lineinfile: 41 | path: /etc/sudoers 42 | regexp: '^Defaults logfile=' 43 | line: 'Defaults logfile="{{ rhel9cis_sudolog_location }}"' 44 | validate: '/usr/sbin/visudo -cf %s' 45 | 46 | - name: "5.2.4 | PATCH | Ensure users must provide password for escalation" 47 | when: rhel9cis_rule_5_2_4 48 | tags: 49 | - level2-server 50 | - level2-workstation 51 | - patch 52 | - sudo 53 | - rule_5.2.4 54 | - NIST800-53R5_AC-6 55 | block: 56 | - name: "5.2.4 | AUDIT | Ensure users must provide password for escalation | Discover accts with NOPASSWD" 57 | ansible.builtin.shell: grep -Ei '(nopasswd)' /etc/sudoers /etc/sudoers.d/* | cut -d':' -f1 58 | become: true 59 | changed_when: false 60 | failed_when: false 61 | register: discovered_nopasswd_sudoers 62 | 63 | - name: "5.2.4 | PATCH | Ensure users must provide password for escalation | Remove nopasswd for accounts not excluded" 64 | when: discovered_nopasswd_sudoers.stdout | length > 0 65 | ansible.builtin.replace: 66 | path: "{{ item }}" 67 | regexp: '^((?!#|{% for name in rhel9cis_sudoers_exclude_nopasswd_list %}{{ name }}{% if not loop.last -%}|{%- endif -%}{% endfor %}).*)NOPASSWD(.*)' 68 | replace: '\1PASSWD\2' 69 | validate: '/usr/sbin/visudo -cf %s' 70 | loop: "{{ discovered_nopasswd_sudoers.stdout_lines }}" 71 | 72 | - name: "5.2.5 | PATCH | Ensure re-authentication for privilege escalation is not disabled globally" 73 | when: rhel9cis_rule_5_2_5 74 | tags: 75 | - level1-server 76 | - level1-workstation 77 | - patch 78 | - sudo 79 | - rule_5.2.5 80 | - NIST800-53R5_AC-6 81 | block: 82 | - name: "5.2.5 | AUDIT | Ensure re-authentication for privilege escalation is not disabled globally" 83 | ansible.builtin.shell: grep -Ei '(!authenticate)' /etc/sudoers /etc/sudoers.d/* | cut -d':' -f1 84 | become: true 85 | changed_when: false 86 | failed_when: false 87 | register: discovered_priv_reauth 88 | 89 | - name: "5.2.5 | PATCH | Ensure re-authentication for privilege escalation is not disabled globally" 90 | when: discovered_priv_reauth.stdout | length > 0 91 | ansible.builtin.replace: 92 | path: "{{ item }}" 93 | regexp: '^([^#].*)!authenticate(.*)' 94 | replace: '\1authenticate\2' 95 | validate: '/usr/sbin/visudo -cf %s' 96 | loop: "{{ discovered_priv_reauth.stdout_lines }}" 97 | 98 | - name: "5.2.6 | PATCH | Ensure sudo authentication timeout is configured correctly" 99 | when: rhel9cis_rule_5_2_6 100 | tags: 101 | - level1-server 102 | - level1-workstation 103 | - patch 104 | - sudo 105 | - rule_5.2.6 106 | block: 107 | - name: "5.2.6 | AUDIT | Ensure sudo authentication timeout is configured correctly | Get files with timeout set" 108 | ansible.builtin.shell: grep -is 'timestamp_timeout' /etc/sudoers /etc/sudoers.d/* | cut -d":" -f1 | uniq | sort 109 | changed_when: false 110 | failed_when: false 111 | register: discovered_sudo_timeout_files 112 | 113 | - name: "5.2.6 | PATCH | Ensure sudo authentication timeout is configured correctly | Set value if no results" 114 | when: discovered_sudo_timeout_files.stdout | length == 0 115 | ansible.builtin.lineinfile: 116 | path: /etc/sudoers 117 | regexp: 'Defaults timestamp_timeout=' 118 | line: "Defaults timestamp_timeout={{ rhel9cis_sudo_timestamp_timeout }}" 119 | validate: '/usr/sbin/visudo -cf %s' 120 | 121 | - name: "5.2.6 | PATCH | Ensure sudo authentication timeout is configured correctly | Set value if has results" 122 | when: discovered_sudo_timeout_files.stdout | length > 0 123 | ansible.builtin.replace: 124 | path: "{{ item }}" 125 | regexp: 'timestamp_timeout=(\d+)' 126 | replace: "timestamp_timeout={{ rhel9cis_sudo_timestamp_timeout }}" 127 | validate: '/usr/sbin/visudo -cf %s' 128 | loop: "{{ discovered_sudo_timeout_files.stdout_lines }}" 129 | 130 | - name: "5.2.7 | PATCH | Ensure access to the su command is restricted" 131 | when: rhel9cis_rule_5_2_7 132 | tags: 133 | - level1-server 134 | - level1-workstation 135 | - patch 136 | - sudo 137 | - rule_5.2.7 138 | - NIST800-53R5_AC-3 139 | - NIST800-53R5_MP-2 140 | block: 141 | - name: "5.2.7 | PATCH | Ensure access to the su command is restricted | Ensure sugroup exists" 142 | ansible.builtin.group: 143 | name: "{{ rhel9cis_sugroup }}" 144 | state: present 145 | register: discovered_sugroup 146 | 147 | - name: "5.2.7 | PATCH | Ensure access to the su command is restricted | remove users from group" 148 | ansible.builtin.lineinfile: 149 | path: /etc/group 150 | regexp: '^{{ rhel9cis_sugroup }}(:.:.*:).*$' 151 | line: '{{ rhel9cis_sugroup }}\g<1>' 152 | backrefs: true 153 | 154 | - name: "5.2.7 | PATCH | Ensure access to the su command is restricted | Setting pam_wheel to use_uid" 155 | ansible.builtin.lineinfile: 156 | path: /etc/pam.d/su 157 | regexp: '^(#)?auth\s+required\s+pam_wheel\.so' 158 | line: 'auth required pam_wheel.so use_uid group={{ rhel9cis_sugroup }}' 159 | -------------------------------------------------------------------------------- /.github/workflows/devel_pipeline_validation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Devel pipeline 4 | 5 | on: # yamllint disable-line rule:truthy 6 | pull_request_target: 7 | types: [opened, reopened, synchronize] 8 | branches: 9 | - devel 10 | - benchmark* 11 | paths: 12 | - '**.yml' 13 | - '**.sh' 14 | - '**.j2' 15 | - '**.ps1' 16 | - '**.cfg' 17 | # Allow manual running of workflow 18 | workflow_dispatch: 19 | 20 | # A workflow run is made up of one or more jobs 21 | # that can run sequentially or in parallel 22 | jobs: 23 | # This will create messages for first time contributers and direct them to the Discord server 24 | welcome: 25 | runs-on: ubuntu-latest 26 | 27 | permissions: 28 | issues: write 29 | pull-requests: write 30 | 31 | steps: 32 | - uses: actions/first-interaction@main 33 | with: 34 | repo_token: ${{ secrets.GITHUB_TOKEN }} 35 | issue_message: |- 36 | Congrats on opening your first issue and thank you for taking the time to help improve Ansible-Lockdown! 37 | Please join in the conversation happening on the [Discord Server](https://www.lockdownenterprise.com/discord) as well. 38 | pr_message: |- 39 | Congrats on opening your first pull request and thank you for taking the time to help improve Ansible-Lockdown! 40 | Please join in the conversation happening on the [Discord Server](https://www.lockdownenterprise.com/discord) as well. 41 | 42 | # This workflow contains a single job that tests the playbook 43 | playbook-test: 44 | # The type of runner that the job will run on 45 | runs-on: self-hosted 46 | 47 | # Allow permissions for AWS auth 48 | permissions: 49 | id-token: write 50 | contents: read 51 | pull-requests: read 52 | 53 | env: 54 | ENABLE_DEBUG: ${{ vars.ENABLE_DEBUG }} 55 | # Imported as a variable by terraform 56 | TF_VAR_repository: ${{ github.event.repository.name }} 57 | AWS_REGION: "us-east-1" 58 | ANSIBLE_VERSION: ${{ vars.ANSIBLE_RUNNER_VERSION }} 59 | defaults: 60 | run: 61 | shell: bash 62 | working-directory: .github/workflows/github_linux_IaC 63 | # working-directory: .github/workflows 64 | 65 | steps: 66 | 67 | - name: Git clone the lockdown repository to test 68 | uses: actions/checkout@v4 69 | with: 70 | ref: ${{ github.event.pull_request.head.sha }} 71 | 72 | - name: If a variable for IAC_BRANCH is set use that branch 73 | working-directory: .github/workflows 74 | run: | 75 | if [ ${{ vars.IAC_BRANCH }} != '' ]; then 76 | echo "IAC_BRANCH=${{ vars.IAC_BRANCH }}" >> $GITHUB_ENV 77 | echo "Pipeline using the following IAC branch ${{ vars.IAC_BRANCH }}" 78 | else 79 | echo IAC_BRANCH=main >> $GITHUB_ENV 80 | fi 81 | 82 | # Pull in terraform code for linux servers 83 | - name: Clone GitHub IaC plan 84 | uses: actions/checkout@v4 85 | with: 86 | repository: ansible-lockdown/github_linux_IaC 87 | path: .github/workflows/github_linux_IaC 88 | ref: ${{ env.IAC_BRANCH }} 89 | 90 | # Uses dedicated restricted role and policy to enable this only for this task 91 | # No credentials are part of github for AWS auth 92 | - name: configure aws credentials 93 | uses: aws-actions/configure-aws-credentials@main 94 | with: 95 | role-to-assume: ${{ secrets.AWS_ASSUME_ROLE }} 96 | role-session-name: ${{ secrets.AWS_ROLE_SESSION }} 97 | aws-region: ${{ env.AWS_REGION }} 98 | 99 | - name: DEBUG - Show IaC files 100 | if: env.ENABLE_DEBUG == 'true' 101 | run: | 102 | echo "OSVAR = $OSVAR" 103 | echo "benchmark_type = $benchmark_type" 104 | pwd 105 | env: 106 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 107 | OSVAR: ${{ vars.OSVAR }} 108 | benchmark_type: ${{ vars.BENCHMARK_TYPE }} 109 | 110 | - name: Tofu init 111 | id: init 112 | run: tofu init 113 | env: 114 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 115 | OSVAR: ${{ vars.OSVAR }} 116 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 117 | 118 | - name: Tofu validate 119 | id: validate 120 | run: tofu validate 121 | env: 122 | # Imported from GitHub variables this is used to load the relevant OS.tfvars file 123 | OSVAR: ${{ vars.OSVAR }} 124 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 125 | 126 | - name: Tofu apply 127 | id: apply 128 | env: 129 | OSVAR: ${{ vars.OSVAR }} 130 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 131 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 132 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 133 | run: tofu apply -var-file "${OSVAR}.tfvars" --auto-approve -input=false 134 | 135 | ## Debug Section 136 | - name: DEBUG - Show Ansible hostfile 137 | if: env.ENABLE_DEBUG == 'true' 138 | run: cat hosts.yml 139 | 140 | # Aws deployments taking a while to come up insert sleep or playbook fails 141 | 142 | - name: Sleep to allow system to come up 143 | run: sleep ${{ vars.BUILD_SLEEPTIME }} 144 | 145 | # Run the Ansible playbook 146 | - name: Run_Ansible_Playbook 147 | env: 148 | ANSIBLE_HOST_KEY_CHECKING: "false" 149 | ANSIBLE_DEPRECATION_WARNINGS: "false" 150 | run: | 151 | /opt/ansible_${{ env.ANSIBLE_VERSION }}_venv/bin/ansible-playbook -i hosts.yml --private-key ~/.ssh/le_runner ../../../site.yml 152 | 153 | # Remove test system - User secrets to keep if necessary 154 | 155 | - name: Tofu Destroy 156 | if: always() && env.ENABLE_DEBUG == 'false' 157 | env: 158 | OSVAR: ${{ vars.OSVAR }} 159 | TF_VAR_benchmark_type: ${{ vars.BENCHMARK_TYPE }} 160 | TF_VAR_privsubnet_id: ${{ secrets.AWS_PRIVSUBNET_ID }} 161 | TF_VAR_vpc_secgrp_id: ${{ secrets.AWS_VPC_SECGRP_ID }} 162 | run: tofu destroy -var-file "${OSVAR}.tfvars" --auto-approve -input=false 163 | -------------------------------------------------------------------------------- /tasks/section_5/cis_5.3.3.4.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "5.3.3.4.1 | PATCH | Ensure pam_unix does not include nullok" 4 | when: 5 | - rhel9cis_rule_5_3_3_4_1 6 | - rhel9cis_disruption_high 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - patch 11 | - rule_5.3.3.4.1 12 | - pam 13 | block: 14 | - name: "5.3.3.4.1 | PATCH | Ensure pam_unix does not include nullok | capture state" 15 | ansible.builtin.shell: grep -E "pam_unix.so.*nullok" /etc/pam.d/*-auth | cut -d ':' -f1 | uniq 16 | changed_when: false 17 | failed_when: discovered_pam_nullok.rc not in [ 0, 1 ] 18 | register: discovered_pam_nullok 19 | 20 | - name: "5.3.3.4.1 | PATCH | Ensure pam_unix does not include nullok | Ensure nullok removed" 21 | when: 22 | - discovered_pam_nullok.stdout | length > 0 23 | - not rhel9cis_allow_authselect_updates 24 | ansible.builtin.replace: 25 | path: "{{ item }}" 26 | regexp: nullok 27 | replace: '' 28 | loop: "{{ discovered_pam_nullok.stdout_lines }}" 29 | 30 | - name: "5.3.3.4.1 | PATCH | Ensure password number of changed characters is configured | Remove nullok from pam files AuthSelect" 31 | when: rhel9cis_allow_authselect_updates 32 | ansible.builtin.replace: 33 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 34 | regexp: ^(\s*password\s+(requisite|required|sufficient)\s+pam_unix\.so)(.*)\snullok(.*$) 35 | replace: \1\2\3 36 | loop: 37 | - password 38 | - system 39 | notify: Authselect update 40 | 41 | - name: "5.3.3.4.2 | PATCH | Ensure pam_unix does not include remember" 42 | when: 43 | - rhel9cis_rule_5_3_3_4_2 44 | - rhel9cis_disruption_high 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - patch 49 | - pam 50 | - rule_5.3.3.4.2 51 | block: 52 | - name: "5.3.3.4.2 | AUDIT | Ensure pam_unix does not include remember | capture state" 53 | ansible.builtin.shell: grep -E "password.*pam_unix.so.*remember" /etc/pam.d/*-auth | cut -d ':' -f1 | uniq 54 | changed_when: false 55 | failed_when: discovered_pam_remember.rc not in [ 0, 1 ] 56 | register: discovered_pam_remember 57 | 58 | - name: "5.3.3.4.2 | PATCH | Ensure pam_unix does not include remember | Ensure remember removed" 59 | when: 60 | - not rhel9cis_allow_authselect_updates 61 | - discovered_pam_remember.stdout | length > 0 62 | ansible.builtin.replace: 63 | path: "{{ item }}" 64 | regexp: remember 65 | replace: '' 66 | loop: "{{ discovered_pam_remember.stdout_lines }}" 67 | 68 | - name: "5.3.3.4.2 | PATCH | Ensure pam_unix does not include remember | Remove remember from pam files AuthSelect" 69 | when: rhel9cis_allow_authselect_updates 70 | ansible.builtin.replace: 71 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 72 | regexp: ^(\s*password\s+(requisite|required|sufficient)\s+pam_unix\.so)(.*)\sremember\s*=\s*=\d*(.*$) 73 | replace: \1\2\3 74 | loop: 75 | - password 76 | - system 77 | notify: Authselect update 78 | 79 | - name: "5.3.3.4.3 | PATCH | Ensure pam_unix includes a strong password hashing algorithm" 80 | when: 81 | - rhel9cis_rule_5_3_3_4_3 82 | - rhel9cis_disruption_high 83 | tags: 84 | - level1-server 85 | - level1-workstation 86 | - patch 87 | - pam 88 | - rule_5.3.3.4.3 89 | - NIST800-53R5_IA-5 90 | block: 91 | - name: "5.3.3.4.3 | AUDIT | Ensure pam_unix includes a strong password hashing algorithm | capture state" 92 | ansible.builtin.shell: grep -E "password.*pam_unix.so.*(sha512|yescrypt)" /etc/pam.d/*-auth | cut -d ':' -f1 | uniq 93 | changed_when: false 94 | failed_when: discovered_pam_pwhash.rc not in [ 0, 1 ] 95 | register: discovered_pam_pwhash 96 | 97 | - name: "5.3.3.4.3 | PATCH | Ensure pam_unix includes a strong password hashing algorithm | Ensure hash algorithm set" 98 | when: 99 | - not rhel9cis_allow_authselect_updates 100 | - discovered_pam_remember.stdout | length > 0 101 | ansible.builtin.replace: 102 | path: "{{ item }}" 103 | regexp: "(md5|bigcrypt|sha256|blowfish|gost_yescrypt|sha512|yescrypt)" 104 | replace: '{{ rhel9cis_passwd_hash_algo }}' 105 | loop: "{{ discovered_pam_remember.stdout_lines }}" 106 | 107 | - name: "5.3.3.4.3 | PATCH | Ensure pam_unix includes a strong password hashing algorithm | Add hash algorithm to pam files AuthSelect" 108 | when: rhel9cis_allow_authselect_updates 109 | ansible.builtin.lineinfile: 110 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 111 | regexp: ^(\s*password\s+)(requisite|required|sufficient)(\s+pam_unix.so\s)(.*)(sha512|yescrypt)(.*$) 112 | line: \1\2\3\4{{ rhel9cis_passwd_hash_algo }}\6 113 | backrefs: true 114 | loop: 115 | - password 116 | - system 117 | notify: Authselect update 118 | 119 | - name: "5.3.3.4.4 | PATCH | Ensure pam_unix includes use_authtok" 120 | when: 121 | - rhel9cis_rule_5_3_3_4_4 122 | - rhel9cis_disruption_high 123 | tags: 124 | - level1-server 125 | - level1-workstation 126 | - patch 127 | - pam 128 | - rule_5.3.3.4.4 129 | - NIST800-53R5_IA-5 130 | block: 131 | - name: "5.3.3.4.4 | PATCH | Ensure pam_unix includes use_authtok | capture state" 132 | ansible.builtin.shell: grep -PH -- '^\h*^password\h*[^#\n\r]+\h+pam_unix.so\b' /etc/pam.d/{password,system}-auth | grep -Pv -- '\buse_authtok\b' 133 | changed_when: false 134 | failed_when: discovered_pam_authtok.rc not in [ 0, 1 ] 135 | register: discovered_pam_authtok 136 | 137 | - name: "5.3.3.4.4 | PATCH | Ensure pam_unix includes use_authtok | pam_files" 138 | when: 139 | - not rhel9cis_allow_authselect_updates 140 | - discovered_pam_authtok is defined 141 | - discovered_pam_authtok.stdout | length > 0 142 | ansible.builtin.lineinfile: 143 | path: "{{ item }}" 144 | regexp: ^(\s*password\s+)(requisite|required|sufficient)(\s+pam_unix.so\s)(.*)use_authtok(.*$) 145 | line: \1\2\3\4use_authtok \5 146 | backrefs: true 147 | loop: "{{ discovered_pam_authtok.stdout_lines }}" 148 | 149 | - name: "5.3.3.4.4 | PATCH | Ensure pam_unix includes use_authtok | Add use_authtok pam files AuthSelect" 150 | when: rhel9cis_allow_authselect_updates 151 | ansible.builtin.lineinfile: 152 | path: "/etc/authselect/custom/{{ rhel9cis_authselect_custom_profile_name }}/{{ item }}-auth" 153 | regexp: ^(\s*password\s+)(requisite|required|sufficient)(\s+pam_unix.so\s)(.*)use_authtok(.*$) 154 | line: \1\2\3\4use_authtok\5 155 | backrefs: true 156 | loop: 157 | - password 158 | - system 159 | notify: Authselect update 160 | -------------------------------------------------------------------------------- /tasks/section_1/cis_1.6.x.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: "1.6.1 | AUDIT | Ensure system-wide crypto policy is not legacy" 4 | when: 5 | - rhel9cis_rule_1_6_1 6 | - rhel9cis_crypto_policy_ansiblemanaged 7 | tags: 8 | - level1-server 9 | - level1-workstation 10 | - automated 11 | - patch 12 | - crypto 13 | - rule_1.6.1 14 | - NIST800-53R5_SC-6 15 | ansible.builtin.debug: 16 | msg: "Captured in prelim to ensure not LEGACY. Runs handler to update" 17 | notify: 18 | - Update Crypto Policy 19 | - Set Crypto Policy 20 | 21 | - name: "1.6.2 | PATCH | Ensure system wide crypto policy is not set in sshd configuration" 22 | when: rhel9cis_rule_1_6_2 23 | tags: 24 | - level1-server 25 | - level1-workstation 26 | - sshd 27 | - automated 28 | - patch 29 | - rule_1.6.2 30 | - NIST800-53R5_SC-8 31 | - NIST800-53R5_IA-5 32 | - NIST800-53R5_AC-17 33 | - NIST800-53R5_SC-6 34 | ansible.builtin.lineinfile: 35 | path: /etc/sysconfig/sshd 36 | regexp: ^CRYPTO_POLICY\s*= 37 | state: absent 38 | notify: Restart sshd 39 | 40 | - name: "1.6.3 | PATCH | Ensure system wide crypto policy disables sha1 hash and signature support | Add submodule exclusion" 41 | when: 42 | - rhel9cis_rule_1_6_3 43 | - "'NO-SHA1' not in rhel9cis_crypto_policy_module" 44 | - rhel9cis_crypto_policy_ansiblemanaged 45 | tags: 46 | - level1-server 47 | - level1-workstation 48 | - automated 49 | - patch 50 | - crypto 51 | - rule_1.6.3 52 | - NIST800-53R5_SC-6 53 | block: 54 | - name: "1.6.3 | PATCH | Ensure system wide crypto policy disables sha1 hash and signature support" 55 | ansible.builtin.template: 56 | src: etc/crypto-policies/policies/modules/NO-SHA1.pmod.j2 57 | dest: /etc/crypto-policies/policies/modules/NO-SHA1.pmod 58 | owner: root 59 | group: root 60 | mode: 'g-wx,o-rwx' 61 | register: discovered_no_sha1_template 62 | 63 | - name: "1.6.3 | PATCH | Ensure system wide crypto policy disables sha1 hash and signature support | submodule to crypto policy modules" 64 | ansible.builtin.set_fact: 65 | rhel9cis_crypto_policy_module: "{{ rhel9cis_crypto_policy_module + ':' + 'NO-SHA1' }}" 66 | changed_when: discovered_no_sha1_template is changed # noqa: no-handler 67 | notify: 68 | - Update Crypto Policy 69 | - Set Crypto Policy 70 | 71 | - name: "1.6.4 | PATCH | Ensure system wide crypto policy disables macs less than 128 bits" 72 | when: 73 | - rhel9cis_rule_1_6_4 74 | - "'NO-WEAKMAC' not in rhel9cis_crypto_policy_module" 75 | - rhel9cis_crypto_policy_ansiblemanaged 76 | tags: 77 | - level1-server 78 | - level1-workstation 79 | - automated 80 | - patch 81 | - crypto 82 | - rule_1.6.4 83 | - NIST800-53R5_SC-6 84 | block: 85 | - name: "1.6.4 | PATCH | Ensure system wide crypto policy disables macs less than 128 bits | Add submodule exclusion" 86 | ansible.builtin.template: 87 | src: etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod.j2 88 | dest: /etc/crypto-policies/policies/modules/NO-WEAKMAC.pmod 89 | owner: root 90 | group: root 91 | mode: 'g-wx,o-rwx' 92 | register: discovered_no_weakmac_template 93 | 94 | - name: "1.6.4 | PATCH | Ensure system wide crypto policy disables macs less than 128 bits | submodule to crypto policy modules" 95 | ansible.builtin.set_fact: 96 | rhel9cis_crypto_policy_module: "{{ rhel9cis_crypto_policy_module + ':' + 'NO-WEAKMAC' }}" 97 | changed_when: discovered_no_weakmac_template is changed # noqa: no-handler 98 | notify: 99 | - Update Crypto Policy 100 | - Set Crypto Policy 101 | 102 | - name: "1.6.5 | PATCH | Ensure system wide crypto policy disables cbc for ssh" 103 | when: 104 | - rhel9cis_rule_1_6_5 105 | - "'NO-SSHCBC' not in rhel9cis_crypto_policy_module" 106 | - rhel9cis_crypto_policy_ansiblemanaged 107 | tags: 108 | - level1-server 109 | - level1-workstation 110 | - automated 111 | - patch 112 | - crypto 113 | - rule_1.6.5 114 | - NIST800-53R5_SC-6 115 | block: 116 | - name: "1.6.5 | PATCH | Ensure system wide crypto policy disables cbc for ssh | Add submodule exclusion" 117 | ansible.builtin.template: 118 | src: etc/crypto-policies/policies/modules/NO-SSHCBC.pmod.j2 119 | dest: /etc/crypto-policies/policies/modules/NO-SSHCBC.pmod 120 | owner: root 121 | group: root 122 | mode: 'g-wx,o-rwx' 123 | register: discovered_no_sshcbc_template 124 | 125 | - name: "1.6.5 | PATCH | Ensure system wide crypto policy disables cbc for ssh | submodule to crypto policy modules" 126 | ansible.builtin.set_fact: 127 | rhel9cis_crypto_policy_module: "{{ rhel9cis_crypto_policy_module + ':' + 'NO-SSHCBC' }}" 128 | changed_when: discovered_no_sshcbc_template is changed # noqa: no-handler 129 | notify: 130 | - Update Crypto Policy 131 | - Set Crypto Policy 132 | 133 | - name: "1.6.6 | PATCH | Ensure system wide crypto policy disables chacha20-poly1305 for ssh" 134 | when: 135 | - rhel9cis_rule_1_6_6 136 | - "'NO-SSHWEAKCIPHERS' not in rhel9cis_crypto_policy_module" 137 | - rhel9cis_crypto_policy_ansiblemanaged 138 | tags: 139 | - level1-server 140 | - level1-workstation 141 | - automated 142 | - patch 143 | - crypto 144 | - rule_1.6.6 145 | - NIST800-53R5_SC-6 146 | block: 147 | - name: "1.6.6 | PATCH | Ensure system wide crypto policy disables chacha20-poly1305 for ssh | Add submodule exclusion" 148 | ansible.builtin.template: 149 | src: etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod.j2 150 | dest: /etc/crypto-policies/policies/modules/NO-SSHWEAKCIPHERS.pmod 151 | owner: root 152 | group: root 153 | mode: 'g-wx,o-rwx' 154 | register: discovered_no_sshweakciphers_template 155 | 156 | - name: "1.6.6 | PATCH | Ensure system wide crypto policy disables chacha20-poly1305 for ssh | submodule to crypto policy modules" 157 | ansible.builtin.set_fact: 158 | rhel9cis_crypto_policy_module: "{{ rhel9cis_crypto_policy_module + ':' + 'NO-SSHWEAKCIPHERS' }}" 159 | changed_when: discovered_no_sshweakciphers_template is changed # noqa: no-handler 160 | notify: 161 | - Update Crypto Policy 162 | - Set Crypto Policy 163 | 164 | - name: "1.6.7 | PATCH | Ensure system wide crypto policy disables EtM for ssh" 165 | when: 166 | - rhel9cis_rule_1_6_7 167 | - "'NO-SSHETM' not in rhel9cis_crypto_policy_module" 168 | - rhel9cis_crypto_policy_ansiblemanaged 169 | tags: 170 | - level1-server 171 | - level1-workstation 172 | - automated 173 | - patch 174 | - crypto 175 | - rule_1.6.7 176 | - NIST800-53R5_SC-6 177 | block: 178 | - name: "1.6.7 | PATCH | Ensure system wide crypto policy disables EtM for ssh | Add submodule exclusion" 179 | ansible.builtin.template: 180 | src: etc/crypto-policies/policies/modules/NO-SSHETM.pmod.j2 181 | dest: /etc/crypto-policies/policies/modules/NO-SSHETM.pmod 182 | owner: root 183 | group: root 184 | mode: 'g-wx,o-rwx' 185 | register: discovered_no_sshetm_template 186 | 187 | - name: "1.6.7 | PATCH | Ensure system wide crypto policy disables EtM for ssh | submodule to crypto policy modules" 188 | ansible.builtin.set_fact: 189 | rhel9cis_crypto_policy_module: "{{ rhel9cis_crypto_policy_module + ':' + 'NO-SSHETM' }}" 190 | changed_when: discovered_no_sshetm_template is changed # noqa: no-handler 191 | notify: 192 | - Update Crypto Policy 193 | - Set Crypto Policy 194 | --------------------------------------------------------------------------------