Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

* **role:aide**: The AIDE check no longer fails on hosts with fwupd, where it reported `/etc/fwupd/fwupd.conf` as changed after the first start of the fwupd daemon.
* **role:aide**: The AIDE check no longer fails on hosts with an EFI system partition, where it reported the files below `/boot/efi` as changed a while after the database was created.
* **role:system_update**: The security lane installs hot-fixes that need a newer package from BaseOS or AppStream, such as the kernel on Rocky 8.3, instead of failing on every run.
* **role:system_update**: On Fedora 42 and later, where `/usr/local/sbin` is a link to `/usr/local/bin`, the role no longer deletes the `update-and-reboot` script right after deploying it, so the weekly update runs again.
* **role:monitoring_plugins**: A run against an unchanged host no longer reports a change for the legacy dependency list of the source install.
Expand Down
23 changes: 23 additions & 0 deletions extensions/molecule/aide/prepare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@
mode: 0o644


# A file on the EFI system partition that the database has to contain from the start, so that
# verify.yml can show that the default rule '/boot/efi DATAONLY' ignores the inode numbers vfat
# hands out anew, but still reports a changed content. No owner, group or mode: vfat has none
# per file, the mount options set them.
- name: 'Deploy /boot/efi/lfops-molecule'
hosts: 'systems_under_test'

tasks:

- name: 'findmnt --noheadings --output FSTYPE /boot/efi'
ansible.builtin.command: 'findmnt --noheadings --output FSTYPE /boot/efi'
register: '__molecule__efi_fstype_result'
changed_when: false
failed_when: false

- name: 'Deploy /boot/efi/lfops-molecule'
ansible.builtin.copy:
content: 'lfops-molecule'
dest: '/boot/efi/lfops-molecule'
when:
- '__molecule__efi_fstype_result["stdout"] == "vfat"'


# A host that already runs the daily check of aide-common, which the role has to remove, since
# it would overwrite the report of aidecheck.service. Without its recommended packages, which
# would pull in a mail transport agent.
Expand Down
115 changes: 106 additions & 9 deletions extensions/molecule/aide/verify.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Verify aide on the running system: the check timer is armed, aide-common (installed by
# prepare.yml on Debian and Ubuntu) is gone, and a check against the database the role created
# runs clean, which proves that aide reads the deployed config and that the database matches it,
# including the role's own files. A forced log rotation leaves that report in place. A config
# aide rejects aborts the role before it replaces
# /etc/aide.conf. Then a file below the scenario's regular rule makes the check fail and shows
# up in the report with all its attributes (report level added_removed_entries), while files
# below its negative rules (one in the rule list, one in aide__conf_raw) do not, and neither
# does a file below /opt, whose default rule the scenario removes. A content change of
# /root/.lfops-molecule is reported, which only its own rule does, not the default '/root/\..*
# PERMS' of the same directory. This only works if the rules and the raw part from the inventory
# are in effect, since /srv is not monitored otherwise and /opt is by default.
# including the role's own files. On a vfat /boot/efi that check runs after the inodes there have
# been dropped from the cache, which gives the files new inode numbers that the default rule
# ignores. It also ignores the mode fwupd sets on its config when its daemon starts. A forced log
# rotation leaves that report in place. A config aide rejects aborts the role before it replaces
# /etc/aide.conf. Then a file below the scenario's regular rule makes the check fail and shows up
# in the report with all its attributes (report level added_removed_entries), while files below
# its negative rules (one in the rule list, one in aide__conf_raw) do not, and neither does a file
# below /opt, whose default rule the scenario removes. A content change of /root/.lfops-molecule
# is reported, which only its own rule does, not the default '/root/\..* PERMS' of the same
# directory, and so is a content change of /boot/efi/lfops-molecule. This only works if the rules
# and the raw part from the inventory are in effect, since /srv is not monitored otherwise and
# /opt is by default.
# In between, the role is run again twice, each time after a change it has to revert, to
# prove that its handler leaves the database alone in both cases where re-baselining would
# accept unvetted changes: a database found without the role's check unit, and a last check
Expand Down Expand Up @@ -66,6 +69,78 @@
- 'not __molecule__aide_common_conf_result["stat"]["exists"]'
fail_msg: '{{ __molecule__aide_common_result["stdout"] | d("") }}'

# vfat stores no inode numbers, the kernel assigns a new one whenever it reads a file in
# again. dropping the cache forces that, so the check below sees new inode numbers for every
# file below /boot/efi, which it has to ignore. 3, not 2: the kernel keeps an inode that
# still has pages in the page cache (inode_lru_isolate() in fs/inode.c). Verified on Debian
# 13, Rocky 9 and Ubuntu 24.04, where 2 leaves the inode number as it is.
- name: 'findmnt --noheadings --output FSTYPE /boot/efi'
ansible.builtin.command: 'findmnt --noheadings --output FSTYPE /boot/efi'
register: '__molecule__efi_fstype_result'
changed_when: false
failed_when: false

- name: 'Drop the inodes of /boot/efi from the cache'
when:
- '__molecule__efi_fstype_result["stdout"] == "vfat"'
block:

- name: 'stat /boot/efi/lfops-molecule (before dropping the cache)'
ansible.builtin.stat:
path: '/boot/efi/lfops-molecule'
get_checksum: false
register: '__molecule__efi_probe_before_result'

- name: 'sync; echo 3 > /proc/sys/vm/drop_caches'
ansible.builtin.shell: 'sync; echo 3 > /proc/sys/vm/drop_caches'
changed_when: false

- name: 'stat /boot/efi/lfops-molecule (after dropping the cache)'
ansible.builtin.stat:
path: '/boot/efi/lfops-molecule'
get_checksum: false
register: '__molecule__efi_probe_after_result'

- name: 'Assert that the probe file on /boot/efi got a new inode number'
ansible.builtin.assert:
that:
- '__molecule__efi_probe_after_result["stat"]["inode"] != __molecule__efi_probe_before_result["stat"]["inode"]'

# fwupd sets its config to 0640 whenever its daemon starts, the package ships it with 0644.
# back to 0644 and a restart of the daemon trigger that, which the check below has to
# ignore.
- name: 'stat /etc/fwupd/fwupd.conf'
ansible.builtin.stat:
path: '/etc/fwupd/fwupd.conf'
get_checksum: false
register: '__molecule__fwupd_conf_result'

- name: 'Have fwupd set the mode of its config'
when:
- '__molecule__fwupd_conf_result["stat"]["exists"]'
block:

- name: 'chmod 0644 /etc/fwupd/fwupd.conf'
ansible.builtin.file:
path: '/etc/fwupd/fwupd.conf'
mode: 0o644

- name: 'systemctl restart fwupd.service'
ansible.builtin.service:
name: 'fwupd.service'
state: 'restarted'

- name: 'stat /etc/fwupd/fwupd.conf (after the restart of fwupd)'
ansible.builtin.stat:
path: '/etc/fwupd/fwupd.conf'
get_checksum: false
register: '__molecule__fwupd_conf_after_result'

- name: 'Assert that fwupd set its config to 0640'
ansible.builtin.assert:
that:
- '__molecule__fwupd_conf_after_result["stat"]["mode"] == "0640"'

- name: 'systemctl start --wait aidecheck.service (clean host)' # noqa command-instead-of-module (wait for the check to finish and read its Result)
ansible.builtin.command: 'systemctl start --wait aidecheck.service'
changed_when: false
Expand Down Expand Up @@ -236,6 +311,13 @@
group: 'root'
mode: 0o644

- name: 'Change the content of /boot/efi/lfops-molecule'
ansible.builtin.copy:
content: 'lfops-molecule-changed'
dest: '/boot/efi/lfops-molecule'
when:
- '__molecule__efi_fstype_result["stdout"] == "vfat"'

- name: 'systemctl start --wait aidecheck.service (changed host)' # noqa command-instead-of-module (wait for the check to finish and read its Result)
ansible.builtin.command: 'systemctl start --wait aidecheck.service'
changed_when: false
Expand Down Expand Up @@ -266,6 +348,14 @@
- '"/etc/cron.d/lfops-molecule" not in __molecule__aide_changed_log_result["stdout"]'
fail_msg: '{{ __molecule__aide_changed_log_result["stdout"] }}'

- name: 'Assert that the check reports the changed content of /boot/efi/lfops-molecule'
ansible.builtin.assert:
that:
- '"/boot/efi/lfops-molecule" in __molecule__aide_changed_log_result["stdout"]'
fail_msg: '{{ __molecule__aide_changed_log_result["stdout"] }}'
when:
- '__molecule__efi_fstype_result["stdout"] == "vfat"'

# the check is failing now, so a config change must not re-baseline the database either.
# the role run below reverts the hand edit, which notifies the handler.
- name: 'sha256sum /var/lib/aide/aide.db.gz (before the run after a failed check)'
Expand Down Expand Up @@ -325,6 +415,13 @@
group: 'root'
mode: 0o644

- name: 'Restore the content of /boot/efi/lfops-molecule'
ansible.builtin.copy:
content: 'lfops-molecule'
dest: '/boot/efi/lfops-molecule'
when:
- '__molecule__efi_fstype_result["stdout"] == "vfat"'

# the last check failed, so the tasks `--tags aide:update_db` runs must leave the database
# alone and say so
- name: 'sha256sum /var/lib/aide/aide.db.gz (before update-db-if-clean after a failed check)'
Expand Down
2 changes: 2 additions & 0 deletions roles/aide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ This role is compatible with the following aide versions:
* `!/usr/lib/sysimage/rpm` is added. The RPM database lives there on RHEL 10, and every rpm or dnf query rewrites it.
* The audit tool rules of the CIS benchmarks are added.
* `/etc/ld.so.cache` and `/etc/udev/hwdb.bin` are checked without their inode (`DATAONLY`), since systemd rebuilds both on the first boot after an update.
* `/boot/efi` is checked without the inode (`DATAONLY`), since the kernel assigns new inode numbers on the vfat EFI system partition at runtime.
* `/etc/aliases.db` is checked for permissions only (`PERMS`), since postfix on the Red Hat family rebuilds it on its start.
* `/etc/resolv.conf` is checked for permissions only (`PERMS`), since DHCP clients and NetworkManager rewrite it at runtime.
* `/etc/fwupd/fwupd.conf` is checked without its mode, since fwupd sets the file to 0640 whenever its daemon starts.

* Subkeys:

Expand Down
15 changes: 15 additions & 0 deletions roles/aide/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ aide__rules__host_var: []
aide__rules__role_var:
- path: '/boot'
attributes: 'NORMAL'
# Not in the shipped list. The EFI system partition is vfat, which stores no inode numbers:
# the kernel assigns a new one whenever it reads a file in again (iunique() in
# fat_build_inode(), fs/fat/inode.c), so NORMAL reports every file below it as changed once
# its inode has been dropped from the cache. DATAONLY still reports a changed content.
# Verified on Debian 13, Rocky 8, Rocky 9 and Ubuntu 24.04.
- path: '/boot/efi'
attributes: 'DATAONLY'
- path: '/bin'
attributes: 'NORMAL'
- path: '/sbin'
Expand Down Expand Up @@ -469,6 +476,14 @@ aide__rules__role_var:
- path: '/etc/usbguard'
attributes: 'NORMAL'

# Not in the shipped list. PERMS without the mode and its ACL: fwupd 1.9.1 and later sets the
# file to 0640 whenever its daemon starts (fu_config_ensure_permissions() in
# libfwupdplugin/fu-config.c), while the package ships it with 0644, so the first start after
# `aide --init` or after an update of fwupd was reported. Verified on Rocky 9, Ubuntu 24.04
# and Ubuntu 26.04.
- path: '/etc/fwupd/fwupd\.conf$'
attributes: 'ftype+u+g+selinux+xattrs'

# Now everything else
- path: '/etc'
attributes: 'PERMS'
Expand Down
Loading