From 55b21eae5d5fed851ec415f65f713fa76d4c4538 Mon Sep 17 00:00:00 2001 From: Sandwich Date: Sat, 30 May 2026 18:05:14 +0200 Subject: [PATCH] fix: encryption, partitioning, cis and virtualization hardening --- roles/cis/defaults/main.yml | 2 +- roles/cis/tasks/modules.yml | 2 +- roles/configuration/defaults/main.yml | 6 ++--- .../tasks/encryption/initramfs.yml | 6 ++--- .../tasks/encryption/initramfs_detect.yml | 2 +- .../tasks/encryption/keyfile.yml | 2 +- roles/configuration/tasks/encryption/tpm2.yml | 2 +- roles/configuration/tasks/extras.yml | 1 - roles/configuration/tasks/sudo.yml | 3 ++- roles/partitioning/defaults/main.yml | 2 +- roles/partitioning/tasks/_create_lvm.yml | 6 ++--- roles/virtualization/defaults/main.yml | 22 ++++++++++++++----- roles/virtualization/tasks/libvirt.yml | 13 +++++++++++ roles/virtualization/tasks/vmware.yml | 2 +- 14 files changed, 46 insertions(+), 25 deletions(-) diff --git a/roles/cis/defaults/main.yml b/roles/cis/defaults/main.yml index a742e62..9adcbb0 100644 --- a/roles/cis/defaults/main.yml +++ b/roles/cis/defaults/main.yml @@ -1,6 +1,6 @@ --- # User-facing API: override via top-level `cis` dict in inventory. -# Merged with these defaults in _normalize.yml → cis_cfg. +# Merged with these defaults in _normalize.yml -> cis_cfg. cis_defaults: modules_blacklist: - freevxfs diff --git a/roles/cis/tasks/modules.yml b/roles/cis/tasks/modules.yml index b2801a2..7b444a5 100644 --- a/roles/cis/tasks/modules.yml +++ b/roles/cis/tasks/modules.yml @@ -1,7 +1,7 @@ --- - name: Disable Kernel Modules vars: - # Ubuntu uses squashfs for snap packages — blacklisting it breaks snap entirely + # Ubuntu uses squashfs for snap packages - blacklisting it breaks snap entirely cis_modules_squashfs: "{{ [] if os in ['ubuntu', 'ubuntu-lts'] else ['squashfs'] }}" cis_modules_all: "{{ cis_cfg.modules_blacklist + cis_modules_squashfs }}" ansible.builtin.copy: diff --git a/roles/configuration/defaults/main.yml b/roles/configuration/defaults/main.yml index f8ab1b6..2b0d3cc 100644 --- a/roles/configuration/defaults/main.yml +++ b/roles/configuration/defaults/main.yml @@ -1,7 +1,5 @@ --- -# Network configuration dispatch — maps OS name to the task file +# Network configuration dispatch - maps OS name to the task file # that writes network config. Default (NetworkManager) applies to # all OSes not explicitly listed. -configuration_network_task_map: - alpine: network_alpine.yml - void: network_void.yml +configuration_network_task_map: {} diff --git a/roles/configuration/tasks/encryption/initramfs.yml b/roles/configuration/tasks/encryption/initramfs.yml index 4b49d1e..7538202 100644 --- a/roles/configuration/tasks/encryption/initramfs.yml +++ b/roles/configuration/tasks/encryption/initramfs.yml @@ -16,9 +16,9 @@ RedHat: >- {{ chroot_command }} dnf install -y clevis clevis-luks clevis-systemd tpm2-tools - Suse: >- - {{ chroot_command }} zypper install -y - clevis clevis-systemd tpm2.0-tools + Archlinux: >- + {{ chroot_command }} pacman -S --noconfirm --needed + clevis tpm2-tools ansible.builtin.command: "{{ _clevis_install_cmd[os_family] }}" register: _clevis_install_result changed_when: _clevis_install_result.rc == 0 diff --git a/roles/configuration/tasks/encryption/initramfs_detect.yml b/roles/configuration/tasks/encryption/initramfs_detect.yml index c289c4b..eb2e852 100644 --- a/roles/configuration/tasks/encryption/initramfs_detect.yml +++ b/roles/configuration/tasks/encryption/initramfs_detect.yml @@ -3,7 +3,7 @@ # Sets _initramfs_generator and _tpm2_method facts. # # Generator detection: derived from the platform's initramfs_cmd -# (dracut → dracut, mkinitcpio → mkinitcpio, else → initramfs-tools) +# (dracut -> dracut, mkinitcpio -> mkinitcpio, else -> initramfs-tools) # TPM2 method: systemd-cryptenroll when generator supports tpm2-device, # clevis fallback otherwise. Non-native dracut installed automatically. diff --git a/roles/configuration/tasks/encryption/keyfile.yml b/roles/configuration/tasks/encryption/keyfile.yml index dbe5ef0..3ebaa41 100644 --- a/roles/configuration/tasks/encryption/keyfile.yml +++ b/roles/configuration/tasks/encryption/keyfile.yml @@ -107,7 +107,7 @@ when: (configuration_luks_keyfile_unlock_test_after.rc | default(1)) != 0 ansible.builtin.debug: msg: >- - LUKS keyfile enrollment failed — falling back to manual unlock at boot. + LUKS keyfile enrollment failed - falling back to manual unlock at boot. The system will prompt for the LUKS passphrase during startup. - name: Fallback to manual LUKS unlock if keyfile enrollment failed diff --git a/roles/configuration/tasks/encryption/tpm2.yml b/roles/configuration/tasks/encryption/tpm2.yml index 66320a7..8d418e4 100644 --- a/roles/configuration/tasks/encryption/tpm2.yml +++ b/roles/configuration/tasks/encryption/tpm2.yml @@ -1,7 +1,7 @@ --- # TPM2 enrollment via systemd-cryptenroll. # Works with dracut and mkinitcpio (sd-encrypt). The user-set passphrase -# remains as a backup unlock method — no auto-generated keyfiles. +# remains as a backup unlock method - no auto-generated keyfiles. - name: Enroll TPM2 for LUKS block: - name: Create temporary passphrase file for TPM2 enrollment diff --git a/roles/configuration/tasks/extras.yml b/roles/configuration/tasks/extras.yml index 37324a7..ad98b0e 100644 --- a/roles/configuration/tasks/extras.yml +++ b/roles/configuration/tasks/extras.yml @@ -30,7 +30,6 @@ - name: Create zram config when: - (os != "debian" or (os_version | string) != "11") and os != "rhel" - - os not in ["alpine", "void"] - system_cfg.features.swap.enabled | bool ansible.builtin.copy: dest: /mnt/etc/systemd/zram-generator.conf diff --git a/roles/configuration/tasks/sudo.yml b/roles/configuration/tasks/sudo.yml index 9410fc1..dff8c93 100644 --- a/roles/configuration/tasks/sudo.yml +++ b/roles/configuration/tasks/sudo.yml @@ -15,7 +15,8 @@ validate: /usr/sbin/visudo --check --file=%s - name: Deploy per-user sudoers rules - when: item.value.sudo is defined and (item.value.sudo | string | length > 0) + # Jinja truthiness: bool true / a rule string => deploy; false / '' / unset => skip. + when: item.value.sudo | default(false) vars: configuration_sudoers_rule: >- {{ item.value.sudo if item.value.sudo is string else 'ALL=(ALL) NOPASSWD: ALL' }} diff --git a/roles/partitioning/defaults/main.yml b/roles/partitioning/defaults/main.yml index d6d0bc0..b963742 100644 --- a/roles/partitioning/defaults/main.yml +++ b/roles/partitioning/defaults/main.yml @@ -1,7 +1,7 @@ --- partitioning_btrfs_compress_opt: "{{ 'compress=zstd:15' if system_cfg.features.zstd.enabled | bool else '' }}" # Partition separator: 'p' for NVMe/mmcblk (device path ends in digit), empty for SCSI/virtio. -# Examples: /dev/sda → /dev/sda1, /dev/nvme0n1 → /dev/nvme0n1p1 +# Examples: /dev/sda -> /dev/sda1, /dev/nvme0n1 -> /dev/nvme0n1p1 partitioning_part_sep: "{{ 'p' if (install_drive | default('') | regex_search('\\d$')) else '' }}" partitioning_boot_partition_suffix: 1 partitioning_main_partition_suffix: 2 diff --git a/roles/partitioning/tasks/_create_lvm.yml b/roles/partitioning/tasks/_create_lvm.yml index 0c9d96f..6d7a610 100644 --- a/roles/partitioning/tasks/_create_lvm.yml +++ b/roles/partitioning/tasks/_create_lvm.yml @@ -4,14 +4,14 @@ # Sizes are computed from disk_size_gb, memory_mb, and feature flags. # # Swap sizing: -# - RAM >= 16 GB → swap = RAM/2 (in GB) -# - RAM < 16 GB → swap = max(RAM_GB, 2) +# - RAM >= 16 GB -> swap = RAM/2 (in GB) +# - RAM < 16 GB -> swap = max(RAM_GB, 2) # - Capped to: min(target, 4 + max(disk - overhead, 0)) # - Further capped to: max available after subtracting reserved + CIS + extent reserve + 4 GB buffer # # Root sizing: # - Full-disk mode (default): disk - reserved - swap - extent_reserve - (CIS volumes if enabled) -# - Partial mode: tiered — <4 GB available → 4 GB, 4-12 GB → all available, >12 GB → 40% of disk +# - Partial mode: tiered - <4 GB available -> 4 GB, 4-12 GB -> all available, >12 GB -> 40% of disk # # CIS volumes (only when CIS enabled): # - /home: max(min(home_raw, home_max), home_min) where home_raw = (disk - overhead) * 10% diff --git a/roles/virtualization/defaults/main.yml b/roles/virtualization/defaults/main.yml index 7c6c6f8..92cdbff 100644 --- a/roles/virtualization/defaults/main.yml +++ b/roles/virtualization/defaults/main.yml @@ -1,9 +1,9 @@ --- # Cloud-init support matrix: -# libvirt — cloud-init ISO attached as CDROM (user-data + network-config) -# proxmox — cloud-init via Proxmox API (cicustom, ciuser, cipassword, etc.) -# vmware — no cloud-init; configuration is applied post-install via chroot -# xen — no cloud-init; configuration is applied post-install via chroot +# libvirt - cloud-init ISO attached as CDROM (user-data + network-config) +# proxmox - cloud-init via Proxmox API (cicustom, ciuser, cipassword, etc.) +# vmware - no cloud-init; configuration is applied post-install via chroot +# xen - no cloud-init; configuration is applied post-install via chroot virtualization_libvirt_image_dir: >- {{ system_cfg.path @@ -17,8 +17,18 @@ virtualization_libvirt_cloudinit_path: >- virtualization_xen_disk_path: /var/lib/xen/images virtualization_libvirt_machine_type: q35 -virtualization_libvirt_ovmf_code: /usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd -virtualization_libvirt_ovmf_vars: /usr/share/edk2/x64/OVMF_VARS.4m.fd +# Secboot OVMF firmware candidates, ordered Arch, Debian/Ubuntu, Fedora/RHEL. +# libvirt.yml resolves these to the first file present on the controller. +virtualization_libvirt_ovmf_code_candidates: + - /usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd + - /usr/share/OVMF/OVMF_CODE_4M.secboot.fd + - /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd + - /usr/share/OVMF/OVMF_CODE.secboot.fd +virtualization_libvirt_ovmf_vars_candidates: + - /usr/share/edk2/x64/OVMF_VARS.4m.fd + - /usr/share/OVMF/OVMF_VARS_4M.fd + - /usr/share/edk2/ovmf/OVMF_VARS.fd + - /usr/share/OVMF/OVMF_VARS.fd virtualization_tpm2_enabled: >- {{ diff --git a/roles/virtualization/tasks/libvirt.yml b/roles/virtualization/tasks/libvirt.yml index fb1d247..8ba1d7c 100644 --- a/roles/virtualization/tasks/libvirt.yml +++ b/roles/virtualization/tasks/libvirt.yml @@ -70,6 +70,19 @@ - /tmp/cloud-user-data-{{ hostname }}.yml - /tmp/cloud-network-config-{{ hostname }}.yml +# Resolve OVMF firmware to the first candidate present on the controller +# unless the user pinned an explicit path. first_found needs the localhost +# delegation since the candidates live on the libvirt host, not the target. +- name: Resolve OVMF firmware paths + delegate_to: localhost + ansible.builtin.set_fact: + virtualization_libvirt_ovmf_code: >- + {{ virtualization_libvirt_ovmf_code if virtualization_libvirt_ovmf_code | default('', true) | length > 0 + else lookup('ansible.builtin.first_found', virtualization_libvirt_ovmf_code_candidates) }} + virtualization_libvirt_ovmf_vars: >- + {{ virtualization_libvirt_ovmf_vars if virtualization_libvirt_ovmf_vars | default('', true) | length > 0 + else lookup('ansible.builtin.first_found', virtualization_libvirt_ovmf_vars_candidates) }} + # uri defaults to qemu:///system (local libvirtd) - name: Create VM using libvirt delegate_to: localhost diff --git a/roles/virtualization/tasks/vmware.yml b/roles/virtualization/tasks/vmware.yml index 88d6d8f..945c150 100644 --- a/roles/virtualization/tasks/vmware.yml +++ b/roles/virtualization/tasks/vmware.yml @@ -36,7 +36,7 @@ esxi_hostname: "{{ hypervisor_cfg.node if (hypervisor_cfg.node | default('') | length > 0) else omit }}" folder: "{{ system_cfg.path if system_cfg.path | string | length > 0 else omit }}" name: "{{ hostname }}" - # Generic guest ID — VMware auto-detects OS post-install + # Generic guest ID - VMware auto-detects OS post-install guest_id: otherLinux64Guest annotation: | {{ note if note is defined else '' }}