fix: EL10 PAM and crypto readiness via authselect profile and DEFAULT policy

This commit is contained in:
2026-05-28 17:30:57 +02:00
parent 6fe843355e
commit 89e366d0f0
6 changed files with 84 additions and 42 deletions

View File

@@ -49,6 +49,15 @@
bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}" bootstrap_var_key: "{{ 'bootstrap_' + (os | replace('-lts', '') | replace('-', '_')) }}"
ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}" ansible.builtin.include_tasks: "{{ bootstrap_os_task_map[os] }}"
# dnf --installroot never runs anaconda, so no authselect profile is selected and
# /etc/pam.d/system-auth is missing, leaving the system unable to authenticate.
# local is the right profile: local-auth only, no pam_sss.so, still CIS-capable.
- name: Select default authselect profile for the PAM stack
when: is_authselect | bool
ansible.builtin.command: "{{ chroot_command }} authselect select local --force"
register: bootstrap_authselect_result
changed_when: bootstrap_authselect_result.rc == 0
- name: Install hardware-matched firmware/microcode/GPU/peripheral packages - name: Install hardware-matched firmware/microcode/GPU/peripheral packages
when: >- when: >-
(system_cfg.features.firmware.enabled | bool) (system_cfg.features.firmware.enabled | bool)

View File

@@ -1,6 +1,6 @@
--- ---
# Feature-gated packages shared across all distros. # Feature-gated packages shared across all distros. Arch strips nftables from
# Arch has special nftables handling and composes this differently. # this and composes it differently.
bootstrap_common_conditional: >- bootstrap_common_conditional: >-
{{ {{
( (
@@ -15,12 +15,29 @@ bootstrap_common_conditional: >-
) )
}} }}
# --------------------------------------------------------------------------- # Native-installer parity backfill: anaconda and the d-i "standard" task leave
# Per-OS package definitions: base (rootfs/group install), extra (post-base), # these, but install_weak_deps=False / Recommends-off minimal installs drop them.
# conditional (feature/version-gated, appended by task files). bootstrap_el_runtime:
# DNF-based distros also carry repos (dnf --repo) and use base as group names. - NetworkManager
# --------------------------------------------------------------------------- - authselect
- authselect-libs
- chrony
- crypto-policies
- crypto-policies-scripts
- dbus
- polkit
bootstrap_deb_runtime:
- apparmor-utils
- chrony
- libpam-pwquality
- needrestart
- network-manager
- sudo
# Per-OS package definitions: base (rootfs/group install), extra (post-base),
# conditional (feature/version-gated, appended by task files). DNF distros also
# carry repos and use base as group names.
bootstrap_rhel: bootstrap_rhel:
repos: repos:
- "rhel{{ os_version_major }}-baseos" - "rhel{{ os_version_major }}-baseos"
@@ -53,6 +70,7 @@ bootstrap_rhel:
+ (['python39'] if os_version_major | default('') == '8' else ['python']) + (['python39'] if os_version_major | default('') == '8' else ['python'])
+ (['kernel'] if os_version_major | default('') == '10' else []) + (['kernel'] if os_version_major | default('') == '10' else [])
+ (['zram-generator'] if os_version_major | default('') in ['9', '10'] else []) + (['zram-generator'] if os_version_major | default('') in ['9', '10'] else [])
+ bootstrap_el_runtime
+ bootstrap_common_conditional + bootstrap_common_conditional
}} }}
@@ -87,8 +105,8 @@ bootstrap_almalinux:
- zstd - zstd
conditional: >- conditional: >-
{{ {{
(['dbus-daemon'] if (os_version_major | default('10') | int) >= 9 else []) (['dhcp-client'] if (os_version_major | default('10') | int) < 10 else [])
+ (['dhcp-client'] if (os_version_major | default('10') | int) < 10 else []) + bootstrap_el_runtime
+ bootstrap_common_conditional + bootstrap_common_conditional
}} }}
@@ -127,6 +145,7 @@ bootstrap_rocky:
conditional: >- conditional: >-
{{ {{
(['dhcp-client'] if (os_version_major | default('9') | int) < 10 else []) (['dhcp-client'] if (os_version_major | default('9') | int) < 10 else [])
+ bootstrap_el_runtime
+ bootstrap_common_conditional + bootstrap_common_conditional
}} }}
@@ -160,7 +179,6 @@ bootstrap_fedora:
- nc - nc
- nfs-utils - nfs-utils
- nfsv4-client-utils - nfsv4-client-utils
- polkit
- ppp - ppp
- python3 - python3
- ripgrep - ripgrep
@@ -171,7 +189,7 @@ bootstrap_fedora:
- zoxide - zoxide
- zram-generator - zram-generator
- zstd - zstd
conditional: "{{ bootstrap_common_conditional }}" conditional: "{{ bootstrap_el_runtime + bootstrap_common_conditional }}"
bootstrap_debian: bootstrap_debian:
base: base:
@@ -189,28 +207,22 @@ bootstrap_debian:
- python3 - python3
- xfsprogs - xfsprogs
extra: extra:
- apparmor-utils
- bat - bat
- chrony
- curl - curl
- entr - entr
- fish - fish
- fzf - fzf
- htop - htop
- jq - jq
- libpam-pwquality
- linux-image-amd64 - linux-image-amd64
- lrzsz - lrzsz
- mtr - mtr
- ncdu - ncdu
- needrestart
- net-tools - net-tools
- network-manager
- python-is-python3 - python-is-python3
- ripgrep - ripgrep
- rsync - rsync
- screen - screen
- sudo
- syslog-ng - syslog-ng
- tcpd - tcpd
- vim - vim
@@ -225,6 +237,7 @@ bootstrap_debian:
+ (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else []) + (['systemd-zram-generator'] if (os_version | string) not in ['10', '11'] else [])
+ (['tldr'] if (os_version | string) not in ['13', 'unstable'] else []) + (['tldr'] if (os_version | string) not in ['13', 'unstable'] else [])
+ (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else []) + (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
+ bootstrap_deb_runtime
+ bootstrap_common_conditional + bootstrap_common_conditional
}} }}
@@ -246,10 +259,8 @@ bootstrap_ubuntu:
- python3 - python3
- xfsprogs - xfsprogs
extra: extra:
- apparmor-utils
- bash-completion - bash-completion
- bat - bat
- chrony
- curl - curl
- dnsutils - dnsutils
- duf - duf
@@ -261,20 +272,16 @@ bootstrap_ubuntu:
- fzf - fzf
- htop - htop
- jq - jq
- libpam-pwquality
- lrzsz - lrzsz
- mtr - mtr
- ncdu - ncdu
- ncurses-term - ncurses-term
- needrestart
- net-tools - net-tools
- network-manager
- python-is-python3 - python-is-python3
- ripgrep - ripgrep
- rsync - rsync
- screen - screen
- software-properties-common - software-properties-common
- sudo
- syslog-ng - syslog-ng
- systemd-zram-generator - systemd-zram-generator
- tcpd - tcpd
@@ -288,6 +295,7 @@ bootstrap_ubuntu:
conditional: >- conditional: >-
{{ {{
(['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else []) (['shim-signed'] if system_cfg.features.secure_boot.enabled | bool else [])
+ bootstrap_deb_runtime
+ bootstrap_common_conditional + bootstrap_common_conditional
}} }}

View File

@@ -27,7 +27,9 @@
# Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip) # Non-RHEL/non-Debian distros: loop evaluates to [] (intentional skip)
- name: Prevent Login to Accounts With Empty Password - name: Prevent Login to Accounts With Empty Password
when: cis_effective_rules.empty_password_login | default(false) when:
- cis_effective_rules.empty_password_login | default(false)
- not is_authselect | bool
ansible.builtin.replace: ansible.builtin.replace:
dest: "{{ item }}" dest: "{{ item }}"
regexp: "\\s*nullok" regexp: "\\s*nullok"

View File

@@ -1,11 +1,15 @@
--- ---
# Fedora ships its own crypto-policies preset and update-crypto-policies # Fedora ships its own crypto-policies preset and update-crypto-policies
# behaves differently; applying DEFAULT:NO-SHA1 can break package signing. # behaves differently; applying DEFAULT:NO-SHA1 can break package signing.
# EL10 dropped the NO-SHA1 subpolicy module (DEFAULT already disables SHA-1
# signatures), so the modifier is set only on EL9 and below.
- name: Configure System Cryptography Policy - name: Configure System Cryptography Policy
vars:
_cis_crypto_policy: "{{ 'DEFAULT' if (os_version_major | int >= 10) else 'DEFAULT:NO-SHA1' }}"
when: when:
- cis_effective_rules.crypto_policy | default(false) - cis_effective_rules.crypto_policy | default(false)
- os in (os_family_rhel | difference(['fedora'])) - os in (os_family_rhel | difference(['fedora']))
ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set DEFAULT:NO-SHA1" ansible.builtin.command: "{{ chroot_command }} /usr/bin/update-crypto-policies --set {{ _cis_crypto_policy }}"
register: cis_crypto_policy_result register: cis_crypto_policy_result
changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout" changed_when: "'Setting system-wide crypto-policies to' in cis_crypto_policy_result.stdout"

View File

@@ -126,32 +126,45 @@
regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so' regexp: '^\s*#?\s*auth\s+required\s+pam_wheel\.so'
line: auth required pam_wheel.so line: auth required pam_wheel.so
# authselect wires the pam_faillock stack via the feature; deny/unlock_time live
# in faillock.conf, the supported place (pam_faillock(8) deprecates module args).
- name: Configure account lockout (authselect)
when:
- cis_effective_rules.faillock | default(false)
- is_authselect | bool
block:
- name: Enable the authselect faillock feature
ansible.builtin.command: "{{ chroot_command }} authselect enable-feature with-faillock"
register: cis_faillock_result
changed_when: cis_faillock_result.rc == 0
- name: Set faillock thresholds
ansible.builtin.lineinfile:
path: /mnt/etc/security/faillock.conf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
create: true
mode: "0644"
loop:
- {regexp: '^\s*#?\s*deny\s*=', line: "deny = {{ cis_cfg.faillock_deny }}"}
- {regexp: '^\s*#?\s*unlock_time\s*=', line: "unlock_time = {{ cis_cfg.faillock_unlock_time }}"}
loop_control:
label: "{{ item.line }}"
- name: Configure account lockout - name: Configure account lockout
when: cis_effective_rules.faillock | default(false) when:
- cis_effective_rules.faillock | default(false)
- not is_authselect | bool
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: "{{ item.path }}" path: "{{ item.path }}"
regexp: "{{ item.regexp }}" regexp: "{{ item.regexp }}"
line: "{{ item.line }}" line: "{{ item.line }}"
loop: loop:
- path: >- - path: '/mnt/etc/{{ "pam.d/common-auth" if is_debian | bool else "pam.d/system-auth" }}'
/mnt/etc/{{
"pam.d/common-auth"
if is_debian | bool
else "authselect/system-auth"
if os == "fedora"
else "pam.d/system-auth"
}}
regexp: '^\s*auth\s+required\s+pam_faillock\.so' regexp: '^\s*auth\s+required\s+pam_faillock\.so'
line: >- line: >-
auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }} auth required pam_faillock.so onerr=fail audit silent deny={{ cis_cfg.faillock_deny }} unlock_time={{ cis_cfg.faillock_unlock_time }}
- path: >- - path: '/mnt/etc/{{ "pam.d/common-account" if is_debian | bool else "pam.d/system-auth" }}'
/mnt/etc/{{
"pam.d/common-account"
if is_debian | bool
else "authselect/system-auth"
if os == "fedora"
else "pam.d/system-auth"
}}
regexp: '^\s*account\s+required\s+pam_faillock\.so' regexp: '^\s*account\s+required\s+pam_faillock\.so'
line: account required pam_faillock.so line: account required pam_faillock.so
loop_control: loop_control:

View File

@@ -61,6 +61,12 @@
ansible.builtin.set_fact: ansible.builtin.set_fact:
os_version_major: "{{ (os_version | string).split('.')[0] }}" os_version_major: "{{ (os_version | string).split('.')[0] }}"
# EL>=10 and Fedora dropped the static /etc/pam.d/system-auth shipped by pam;
# the PAM stack is generated by authselect and absent until a profile is selected.
- name: Flag authselect-managed PAM stacks
ansible.builtin.set_fact:
is_authselect: "{{ is_rhel | bool and (os_version_major | default('0') | int) >= 10 }}"
- name: Set chroot command wrapper - name: Set chroot command wrapper
ansible.builtin.set_fact: ansible.builtin.set_fact:
chroot_command: >- chroot_command: >-