diff --git a/.ansible-lint.yml b/.ansible-lint.yml new file mode 100644 index 0000000..80eb0fc --- /dev/null +++ b/.ansible-lint.yml @@ -0,0 +1,12 @@ +--- +exclude_paths: + - .ansible/ + - .cache/ + - .forgejo/ + - venv/ +skip_list: + - package-latest # it would be lovely to pin versions and test before deployment but lack resources + - no-changed-when # TODO: remove this eventually, needs more thinking case by case + - risky-file-permissions # TODO: remove this eventually, needs more thinking case by case + - yaml[line-length] # TODO: remove this eventually, whitespace changes can cause unforseen problems + - galaxy[no-changelog] # TODO: remove this once we tag a release diff --git a/.forgejo/workflows/ansible-lint.yml b/.forgejo/workflows/ansible-lint.yml new file mode 100644 index 0000000..2e4118e --- /dev/null +++ b/.forgejo/workflows/ansible-lint.yml @@ -0,0 +1,41 @@ +--- +name: Ansible Lint Check + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: docker + + container: + image: ghcr.io/catthehacker/ubuntu:runner-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Create a virtual environment + run: | + python -m venv venv + + - name: Install Ansible and ansible-dev-tools + run: | + source venv/bin/activate + pip install --upgrade pip + pip install ansible ansible-dev-tools + shell: bash + + - name: Run ansible-lint + run: | + source venv/bin/activate + ansible-lint + shell: bash diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a67fef --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.ansible +.cache diff --git a/README.md b/README.md index 65b5142..4a1d976 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,23 @@ roles: - src: git+https://github.com/ansible-lockdown/RHEL9-CIS.git version: "2.0.0" ``` + +## Licence + +Copyright © SR2 Communications Limited 2021-2025. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/galaxy.yml b/galaxy.yml index a176966..f52d7ac 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -16,3 +16,7 @@ dependencies: community.crypto: "*" community.general: "*" freeipa.ansible_freeipa: "1.15.1" +tags: + - linux + - infrastructure + - security diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..1e85b01 --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,2 @@ +--- +requires_ansible: ">=2.15.0" diff --git a/playbooks/core_services.yml b/playbooks/core_services.yml index 427bdf5..60fa95c 100644 --- a/playbooks/core_services.yml +++ b/playbooks/core_services.yml @@ -1,5 +1,6 @@ --- -- hosts: +- name: Deploy and update the FreeIPA servers + hosts: - ipaservers become: true # Required by FreeIPA roles vars: @@ -31,12 +32,13 @@ rhel9cis_rule_5_4_2_6: false rhel9cis_rule_5_4_3_3: false roles: - - name: sr2c.core.baseline + - role: sr2c.core.baseline tags: bootstrap - - name: sr2c.core.freeipa + - role: sr2c.core.freeipa tags: freeipa -- hosts: +- name: Deploy and update the Keycloak server + hosts: - keycloak become: true vars: @@ -50,10 +52,10 @@ rhel9cis_allow_authselect_updates: false podman_host_rootless_users: ["identity"] roles: - - name: sr2c.core.baseline + - role: sr2c.core.baseline tags: bootstrap - - name: freeipa.ansible_freeipa.ipaclient + - role: freeipa.ansible_freeipa.ipaclient state: present tags: bootstrap - - name: sr2c.core.podman_keycloak + - role: sr2c.core.podman_keycloak tags: keycloak diff --git a/roles/baseline/handlers/main.yml b/roles/baseline/handlers/main.yml index a35a3e4..81f84d5 100644 --- a/roles/baseline/handlers/main.yml +++ b/roles/baseline/handlers/main.yml @@ -4,6 +4,7 @@ cmd: grub2-mkconfig -o /boot/grub2/grub.cfg - name: Restart systemd-resolved - service: + ansible.builtin.systemd_service: name: systemd-resolved state: restarted + daemon_reload: true diff --git a/roles/baseline/tasks/disk_partitions.yml b/roles/baseline/tasks/disk_partitions.yml index 2e23a43..08c0a78 100644 --- a/roles/baseline/tasks/disk_partitions.yml +++ b/roles/baseline/tasks/disk_partitions.yml @@ -20,7 +20,7 @@ community.general.parted: device: "{{ baseline_second_disk_device }}" number: 1 - flags: [ lvm ] + flags: [lvm] state: present part_start: "0%" part_end: "100%" @@ -138,6 +138,8 @@ - name: Disk Partitions | PATCH | Enter emergency mode ansible.builtin.command: cmd: systemctl isolate emergency.target + tags: + - skip_ansible_lint # Not possible with ansible.builtin.systemd_service - name: Disk Partitions | PATCH | Unmount /var/lib/nfs/rpc_pipefs if mounted ansible.posix.mount: @@ -158,3 +160,5 @@ - name: Disk Partitions | PATCH | Restore default mode ansible.builtin.command: cmd: systemctl isolate default.target + tags: + - skip_ansible_lint # Not possible with ansible.builtin.systemd_service diff --git a/roles/baseline/tasks/disk_partitions_migrate.yml b/roles/baseline/tasks/disk_partitions_migrate.yml index 92cc746..fec3789 100644 --- a/roles/baseline/tasks/disk_partitions_migrate.yml +++ b/roles/baseline/tasks/disk_partitions_migrate.yml @@ -1,18 +1,18 @@ --- -- name: 'Disk Partitions | PATCH | Rename {{ baseline_second_disk_migrate_path }} to {{ baseline_second_disk_migrate_path }}.old' +- name: 'Disk Partitions | PATCH | Rename directory to directory.old | {{ baseline_second_disk_migrate_path }}' ansible.builtin.command: cmd: 'mv {{ baseline_second_disk_migrate_path }} {{ baseline_second_disk_migrate_path }}.old' - name: 'Disk Partitions | PATCH | Mount {{ baseline_second_disk_migrate_path }}' ansible.posix.mount: - src: "/dev/mapper/datavg-{{ baseline_second_disk_migrate_path | replace('/', '', 1) | replace('/', '_') }}" + src: "/dev/mapper/datavg-{{ baseline_second_disk_migrate_path | replace('/', '', 1) | replace('/', '_') }}" path: '{{ baseline_second_disk_migrate_path }}' fstype: 'xfs' opts: 'rw,{{ "noexec," if baseline_second_disk_migrate_path != "/var" else "" }}nosuid,nodev' state: mounted # TODO: systemctl daemon-reload after modifying /etc/fstab -- name: 'Disk Partitions | PATCH | Set {{ baseline_second_disk_migrate_path }} permissions' +- name: 'Disk Partitions | PATCH | Set permissions | {{ baseline_second_disk_migrate_path }}' ansible.builtin.file: path: '{{ baseline_second_disk_migrate_path }}' owner: root @@ -20,12 +20,12 @@ mode: '0755' state: directory -- name: 'Disk Partitions | PATCH | Move {{ baseline_second_disk_migrate_path }} content' +- name: 'Disk Partitions | PATCH | Move content | {{ baseline_second_disk_migrate_path }}' ansible.builtin.shell: cmd: 'cp -ax * {{ baseline_second_disk_migrate_path }}/' chdir: '{{ baseline_second_disk_migrate_path }}.old' -- name: 'Disk Partitions | PATCH | Delete {{ baseline_second_disk_migrate_path }}.old' +- name: 'Disk Partitions | PATCH | Delete directory.old | {{ baseline_second_disk_migrate_path }}' ansible.builtin.file: path: '{{ baseline_second_disk_migrate_path }}.old' state: absent diff --git a/roles/baseline/tasks/ipaclient.yml b/roles/baseline/tasks/ipaclient.yml index ec90515..2150f90 100644 --- a/roles/baseline/tasks/ipaclient.yml +++ b/roles/baseline/tasks/ipaclient.yml @@ -7,15 +7,15 @@ - name: FreeIPA Client | AUDIT | Check current authselect configuration ansible.builtin.command: authselect current - register: freeipa_authselect_status + register: _baseline_freeipa_authselect_status changed_when: false - name: FreeIPA Client | PATCH | Apply authselect profile with sssd, sudo, and mkhomedir if not set ansible.builtin.command: authselect select sssd with-sudo with-mkhomedir --force when: > - 'Profile ID: sssd' not in freeipa_authselect_status.stdout or - 'with-sudo' not in freeipa_authselect_status.stdout or - 'with-mkhomedir' not in freeipa_authselect_status.stdout + 'Profile ID: sssd' not in _baseline_freeipa_authselect_status.stdout or + 'with-sudo' not in _baseline_freeipa_authselect_status.stdout or + 'with-mkhomedir' not in _baseline_freeipa_authselect_status.stdout - name: FreeIPA Client | PATCH | Enable oddjobd.service (for with-mkhomedir feature) ansible.builtin.systemd_service: diff --git a/roles/baseline/tasks/lockdown.yml b/roles/baseline/tasks/lockdown.yml index fc142a8..9a11528 100644 --- a/roles/baseline/tasks/lockdown.yml +++ b/roles/baseline/tasks/lockdown.yml @@ -1,15 +1,10 @@ --- - name: Lockdown | AUDIT | Check current authselect configuration - command: authselect current + ansible.builtin.command: authselect current register: baseline_lockdown_authselect_status failed_when: false # Exit code is 2 when not configured changed_when: false -- name: Lockdown | AUDIT | Do not disable root login if no authselect profile configured - ansible.builtin.set_fact: - rhel9cis_rule_5_1_20: false - when: baseline_lockdown_authselect_status.rc == 2 - - name: Lockdown | PATCH | Run Ansible Lockdown (RHEL9-CIS) ansible.builtin.include_role: name: RHEL9-CIS @@ -19,11 +14,12 @@ rhel9cis_rule_1_7_4: false # Don't restrict user SSH access in sshd_config - this is managed by FreeIPA rhel9cis_rule_5_1_7: false + # Only disable root login once authselect is configured + rhel9cis_rule_5_1_20: "{{ baseline_lockdown_authselect_status.rc != 2 }}" # TODO: figure out boot password rhel9cis_set_boot_pass: false # TODO: We intend to later deploy a remote rsyslog sink rhel9cis_syslog: rsyslog rhel9cis_time_synchronization_servers: "{{ baseline_ntp_servers }}" rhel9cis_warning_banner: "{{ baseline_warning_banner }}" - rhel9cis_sshd_denyusers: "admin nobody" when: (ansible_distribution == "Rocky") and (ansible_distribution_major_version == "9") diff --git a/roles/baseline/tasks/main.yml b/roles/baseline/tasks/main.yml index 468941f..977f1a0 100644 --- a/roles/baseline/tasks/main.yml +++ b/roles/baseline/tasks/main.yml @@ -20,6 +20,7 @@ when: baseline_second_disk_device is defined - name: Baseline | PATCH | Enable EPEL repository + when: (baseline_epel_packages_allowed is defined) and (baseline_epel_packages_allowed | length > 0) block: - name: Baseline | PATCH | Install epel-release ansible.builtin.dnf: @@ -37,7 +38,6 @@ section: epel-cisco-openh264 option: enabled value: 0 - when: (baseline_epel_packages_allowed is defined) and (baseline_epel_packages_allowed | length > 0) - name: Baseline | PATCH | Remove EPEL repository ansible.builtin.dnf: diff --git a/roles/baseline/tasks/solusvm.yml b/roles/baseline/tasks/solusvm.yml index e8fd355..a43eb3b 100644 --- a/roles/baseline/tasks/solusvm.yml +++ b/roles/baseline/tasks/solusvm.yml @@ -19,14 +19,14 @@ - name: SolusVM Guest | AUDIT | Check for tuned profile ansible.builtin.command: tuned-adm active - register: vps_tuned_profile + register: _baseline_solusvm_tuned_profile become: true changed_when: false - name: SolusVM Guest | PATCH | Start tuned profile (virtual-guest) - ansible.builtin.shell: tuned-adm profile virtual-guest + ansible.builtin.command: tuned-adm profile virtual-guest become: true - when: "'virtual-guest' not in vps_tuned_profile.stdout" + when: "'virtual-guest' not in _baseline_solusvm_tuned_profile.stdout" - name: SolusVM Guest | PATCH | Remove console=ttyS0,115200n8 from bootloader configurations ansible.builtin.replace: diff --git a/roles/freeipa/tasks/certs.yml b/roles/freeipa/tasks/certs.yml index d233784..92b091c 100644 --- a/roles/freeipa/tasks/certs.yml +++ b/roles/freeipa/tasks/certs.yml @@ -17,7 +17,7 @@ when: freeipa_certs_existing_cert.not_after is defined - name: "FreeIPA Certificates | AUDIT | Print days until expiry" - debug: + ansible.builtin.debug: msg: "{{ freeipa_certs_days_until_expiry }}" when: freeipa_certs_existing_cert.not_after is defined diff --git a/roles/freeipa/tasks/main.yml b/roles/freeipa/tasks/main.yml index 5edbc8d..510aebb 100644 --- a/roles/freeipa/tasks/main.yml +++ b/roles/freeipa/tasks/main.yml @@ -11,11 +11,11 @@ - /root/isrgrootx1.pem - /root/isrg-root-x2.pem ipaserver_dirsrv_cert_name: "{{ ansible_inventory }}" - ipaserver_dirsrv_cert_files: [ "/root/server.p12" ] + ipaserver_dirsrv_cert_files: ["/root/server.p12"] ipaserver_dirsrv_pin: "" ipaserver_firewalld_zone: public ipaserver_http_cert_name: "{{ ansible_inventory }}" - ipaserver_http_cert_files: [ "/root/server.p12" ] + ipaserver_http_cert_files: ["/root/server.p12"] ipaserver_http_pin: "" ipaserver_no_hbac_allow: true ipaserver_no_pkinit: true @@ -30,22 +30,22 @@ - /root/isrgrootx1.pem - /root/isrg-root-x2.pem ipareplica_dirsrv_cert_name: "{{ ansible_inventory }}" - ipareplica_dirsrv_cert_files: [ "/root/server.p12" ] + ipareplica_dirsrv_cert_files: ["/root/server.p12"] ipareplica_dirsrv_pin: "" ipareplica_firewalld_zone: public ipareplica_http_cert_name: "{{ ansible_inventory }}" - ipareplica_http_cert_files: [ "/root/server.p12" ] + ipareplica_http_cert_files: ["/root/server.p12"] ipareplica_http_pin: "" ipareplica_no_pkinit: true ipareplica_setup_dns: false - name: FreeIPA | AUDIT | Check current authselect configuration - command: authselect current + ansible.builtin.command: authselect current register: freeipa_authselect_status changed_when: false - name: FreeIPA | PATCH | Apply authselect profile with sssd, sudo, and mkhomedir if not set - command: authselect select sssd with-sudo with-mkhomedir + ansible.builtin.command: authselect select sssd with-sudo with-mkhomedir when: > 'Profile ID: sssd' not in freeipa_authselect_status.stdout or 'with-sudo' not in freeipa_authselect_status.stdout or diff --git a/roles/podman_host/tasks/check_subid.yml b/roles/podman_host/tasks/check_subid.yml index 4d1516c..7ce0ddd 100644 --- a/roles/podman_host/tasks/check_subid.yml +++ b/roles/podman_host/tasks/check_subid.yml @@ -15,8 +15,8 @@ path: /etc/subuid regexp: '^{{ _podman_host_rootless_user }}:.*$' state: absent - register: uid_line_found - check_mode: yes + register: _podman_host_uid_line_found + check_mode: true failed_when: false changed_when: false @@ -25,17 +25,17 @@ path: /etc/subgid regexp: '^{{ _podman_host_rootless_user_group.ansible_facts.getent_group | first }}:.*$' state: absent - register: gid_line_found - check_mode: yes + register: _podman_host_gid_line_found + check_mode: true failed_when: false changed_when: false - name: Podman Host | AUDIT | Assert that user is in subuid file exactly once ansible.builtin.assert: that: - - uid_line_found.found == 1 + - _podman_host_uid_line_found.found == 1 - name: Podman Host | AUDIT | Assert that group is in subgid file exactly once ansible.builtin.assert: that: - - gid_line_found.found == 1 + - _podman_host_gid_line_found.found == 1 diff --git a/roles/podman_keycloak/defaults/main.yml b/roles/podman_keycloak/defaults/main.yml index cbaea96..5004c55 100644 --- a/roles/podman_keycloak/defaults/main.yml +++ b/roles/podman_keycloak/defaults/main.yml @@ -14,4 +14,4 @@ podman_keycloak_podman_rootless_user: keycloak podman_keycloak_postgres_keycloak_database: keycloak # podman_keycloak_postgres_keycloak_password: podman_keycloak_postgres_keycloak_username: keycloak -podman_keycloak_keycloak_additional_volumes: [] \ No newline at end of file +podman_keycloak_keycloak_additional_volumes: [] diff --git a/roles/podman_keycloak/tasks/ldap.yml b/roles/podman_keycloak/tasks/ldap.yml index 99bf0dd..ecba145 100644 --- a/roles/podman_keycloak/tasks/ldap.yml +++ b/roles/podman_keycloak/tasks/ldap.yml @@ -1,9 +1,9 @@ --- -- name: wait 30 seconds for ldap server to start +- name: Podman Keycloak | AUDIT | Wait 30 seconds for ldap server to start ansible.builtin.pause: seconds: 30 -- name: create ldap suffix +- name: Podman Keycloak | PATCH | Create ldap suffix containers.podman.podman_container_exec: name: ldap argv: @@ -25,13 +25,13 @@ tags: - ldap -- name: create suffix result (only when changed) - debug: +- name: Podman Keycloak | AUDIT | Create suffix result (only when changed) + ansible.builtin.debug: msg: "Suffix was created" when: not podman_keycloak_create_suffix.failed changed_when: not podman_keycloak_create_suffix.failed -- name: ldap organisational units +- name: Podman Keycloak | PATCH | Create OUs community.general.ldap_entry: dn: "ou={{ item }},{{ podman_keycloak_ldap_database_suffix_dn }}" objectClass: @@ -46,10 +46,10 @@ - People - Groups environment: - - LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" + LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" tags: ldap -- name: enable memberOf plugin +- name: Podman Keycloak | PATCH | Enable memberOf plugin containers.podman.podman_container_exec: name: ldap argv: @@ -65,7 +65,7 @@ tags: - ldap -- name: disable anonymous bind +- name: Podman Keycloak | PATCH | Disable anonymous bind containers.podman.podman_container_exec: name: ldap argv: @@ -81,7 +81,7 @@ tags: - ldap -- name: ldap read-only administrator +- name: Podman Keycloak | PATCH | Create a read-only administrator community.general.ldap_entry: dn: "uid=admin,ou=Administrators,{{ podman_keycloak_ldap_database_suffix_dn }}" objectClass: @@ -98,10 +98,10 @@ bind_pw: "{{ podman_keycloak_ldap_directory_manager_password }}" delegate_to: localhost environment: - - LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" + LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" tags: ldap -- name: ldap access control information +- name: Podman Keycloak | PATCH | Apply LDAP permissions community.general.ldap_attrs: dn: "{{ podman_keycloak_ldap_database_suffix_dn }}" attributes: @@ -111,5 +111,5 @@ bind_pw: "{{ podman_keycloak_ldap_directory_manager_password }}" delegate_to: localhost environment: - - LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" + LDAPTLS_REQCERT: "{% if podman_keycloak_certbot_testing %}never{% else %}always{% endif %}" tags: ldap diff --git a/roles/podman_keycloak/tasks/main.yml b/roles/podman_keycloak/tasks/main.yml index 4183e39..9068847 100644 --- a/roles/podman_keycloak/tasks/main.yml +++ b/roles/podman_keycloak/tasks/main.yml @@ -157,4 +157,4 @@ scope: user daemon_reload: true become: true - become_user: "{{ podman_keycloak_podman_rootless_user }}" \ No newline at end of file + become_user: "{{ podman_keycloak_podman_rootless_user }}"