Skip to content
Open
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
1 change: 1 addition & 0 deletions .ansible-lint-ignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
roles/acme_sh/defaults/main.yml jinja[spacing]
roles/aide/defaults/main.yml jinja[spacing]
roles/ansible_init/defaults/main.yml jinja[spacing]
roles/apache_httpd/defaults/main.yml jinja[spacing]
roles/apache_solr/defaults/main.yml jinja[spacing]
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* **role:aide, playbook:aide**: Add a role and playbook to install AIDE on RHEL 8, 9 and 10 and run a daily file integrity check, which leaves `aide-check.service` failed on any finding.
* **role:wordpress**: Entries in `wordpress__plugins` accept `enabled: false`, which keeps a plugin installed but deactivated.
* **role:system_update**: The role's inventory variables are type-checked when it starts, so a mistyped value fails the run right away instead of surfacing further in as a confusing error.
* **role:wordpress**: Several WordPress instances can share a host as pseudo hosts in the inventory, under different host names as well as under different paths of one host name, such as `https://example.com/blog`.
Expand Down Expand Up @@ -57,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

* **role:system_update**: The daily security lane on Rocky refreshes the AIDE database after installing hot-fixes, like the weekly lane, so the next AIDE check no longer fails on every file a hot-fix touched.
* **module:bitwarden_item**: The module works with the Mitogen strategy, where it aborted with `MODULE FAILURE` on every run, for example when the `grafana` role stores its service account tokens.
* **plugin:bitwarden_item, module:bitwarden_item**: Running against several hosts in parallel no longer creates duplicates of a Bitwarden item, whether the item is new or has existed for a long time, so the next run no longer aborts with "Found multiple Bitwarden items".
* **role:wordpress**: The installation no longer aborts at `wp core download` when Ansible connects as `root` without privilege escalation.
Expand Down
1 change: 1 addition & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Which Ansible role is proven to run on which OS?
| Role | Deb 12 | Deb 13 | RHEL 8 | RHEL 9 | RHEL 10 | Ubu 22.04 | Ubu 24.04 | Ubu 26.04 | Other |
|---------------------------------------|:------:|:------:|:------:|:------:|:-------:|:---------:|:---------:|:---------:|----------------------------------------------|
| acme_sh | x | x | x | x | x | (x) | x | (x) | |
| aide | | | x | x | x | | | | |
| alternatives | x | x | x | x | (x) | x | x | (x) | |
| ansible_init | | | | | | | | | Fedora 35+ |
| apache_httpd | x | x | x | x | x | (x) | x | (x) | |
Expand Down
2 changes: 2 additions & 0 deletions extensions/molecule/aide/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- name: 'Converge aide playbook'
ansible.builtin.import_playbook: 'linuxfabrik.lfops.aide'
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# /srv is not in the default rule list, so a finding below it can only come from the two
# rules added here. /opt is in the default list, and removing its rule has to take it out
# of the check. verify.yml relies on all three.
aide__rules__group_var:
- path: '/opt'
state: 'absent'
- path: '/srv/lfops-molecule'
attributes: 'NORMAL'
- path: '/srv/lfops-molecule/ignored'
type: 'negative'
13 changes: 13 additions & 0 deletions extensions/molecule/aide/inventory/hosts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# yamllint disable rule:empty-values

# Map the playbook's target group (playbooks/aide.yml: hosts: lfops_aide) onto the
# shared systems_under_test host set. The role is Red Hat-family only.
lfops_aide:
children:
systems_under_test:

systems_under_test:
hosts:
rocky8-vm:
rocky9-vm:
rocky10-vm:
2 changes: 2 additions & 0 deletions extensions/molecule/aide/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Molecule scenario marker. Inherits the VM backend and the full test_sequence
# from extensions/molecule/config.yml.
213 changes: 213 additions & 0 deletions extensions/molecule/aide/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Verify aide on the running system: the check timer is armed, 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. Then a file below the
# scenario's regular rule makes the check fail and shows up in the report, while a file
# below its negative rule does not, and neither does a file below /opt, whose default rule
# the scenario removes. This only works if the rules 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
# that reported changes. Finally the probe files are removed and the update-db tasks of the
# role accept the current state, after which the failed state is cleared and the check runs
# clean again, so the second verify after the idempotence step starts from the same state.
- name: 'Verify aide'
hosts: 'systems_under_test'
gather_facts: false

tasks:

- name: 'systemctl is-enabled aide-check.timer' # noqa command-instead-of-module (read-only state query)
ansible.builtin.command: 'systemctl is-enabled aide-check.timer'
register: '__molecule__aide_timer_enabled_result'
changed_when: false
failed_when: false

- name: 'systemctl is-active aide-check.timer' # noqa command-instead-of-module (read-only state query)
ansible.builtin.command: 'systemctl is-active aide-check.timer'
register: '__molecule__aide_timer_active_result'
changed_when: false
failed_when: false

- name: 'Assert that aide-check.timer is enabled and active'
ansible.builtin.assert:
that:
- '__molecule__aide_timer_enabled_result["stdout"] == "enabled"'
- '__molecule__aide_timer_active_result["stdout"] == "active"'

- name: 'systemctl start aide-check.service (clean host)' # noqa command-instead-of-module (need to run the oneshot and read its Result)
ansible.builtin.command: 'systemctl start aide-check.service'
changed_when: false
failed_when: false

- name: 'systemctl show --property=Result aide-check.service (clean host)' # noqa command-instead-of-module (no module reads a oneshot Result)
ansible.builtin.command: 'systemctl show --property=Result --value aide-check.service'
register: '__molecule__aide_clean_result'
changed_when: false

- name: 'cat /var/log/aide/aide.log (clean host)'
ansible.builtin.command: 'cat /var/log/aide/aide.log'
register: '__molecule__aide_clean_log_result'
changed_when: false

- name: 'Assert that the check on the unchanged host runs clean'
ansible.builtin.assert:
that:
- '__molecule__aide_clean_result["stdout"] == "success"'
fail_msg: '{{ __molecule__aide_clean_log_result["stdout"] }}'

# a database the role finds without its own check unit must not be re-baselined. removing
# the unit makes the host look like one with a database from a previous AIDE setup, and
# the role run below deploys the unit again, which notifies the handler.
- name: 'sha256sum /var/lib/aide/aide.db.gz (before the run without a check unit)'
ansible.builtin.stat:
path: '/var/lib/aide/aide.db.gz'
checksum_algorithm: 'sha256'
register: '__molecule__aide_db_before_no_unit_result'

- name: 'rm -f /etc/systemd/system/aide-check.service'
ansible.builtin.file:
path: '/etc/systemd/system/aide-check.service'
state: 'absent'

- name: 'systemctl daemon-reload'
ansible.builtin.systemd:
daemon_reload: true

- name: 'Reset the end-of-play messages (run without a check unit)'
ansible.builtin.set_fact:
__shared__end_of_play_messages: []

- name: 'Run the aide role (run without a check unit)'
ansible.builtin.include_role:
name: 'linuxfabrik.lfops.aide'

- name: 'sha256sum /var/lib/aide/aide.db.gz (after the run without a check unit)'
ansible.builtin.stat:
path: '/var/lib/aide/aide.db.gz'
checksum_algorithm: 'sha256'
register: '__molecule__aide_db_after_no_unit_result'

- name: 'Assert that the database was left alone and the operator was told'
ansible.builtin.assert:
that:
- '__molecule__aide_db_after_no_unit_result["stat"]["checksum"] == __molecule__aide_db_before_no_unit_result["stat"]["checksum"]'
- '__shared__end_of_play_messages | select("match", "aide: ") | list | length == 1'

- name: 'mkdir -p /srv/lfops-molecule/ignored'
ansible.builtin.file:
path: '/srv/lfops-molecule/ignored'
state: 'directory'
owner: 'root'
group: 'root'
mode: 0o755

- name: 'Create a probe file below the regular rule, the negative rule and /opt'
ansible.builtin.copy:
content: 'lfops-molecule'
dest: '{{ item }}'
owner: 'root'
group: 'root'
mode: 0o644
loop:
- '/opt/lfops-molecule-probe'
- '/srv/lfops-molecule/ignored/probe'
- '/srv/lfops-molecule/probe'

- name: 'systemctl start aide-check.service (changed host)' # noqa command-instead-of-module (need to run the oneshot and read its Result)
ansible.builtin.command: 'systemctl start aide-check.service'
changed_when: false
failed_when: false

- name: 'systemctl show --property=Result aide-check.service (changed host)' # noqa command-instead-of-module (no module reads a oneshot Result)
ansible.builtin.command: 'systemctl show --property=Result --value aide-check.service'
register: '__molecule__aide_changed_result'
changed_when: false

- name: 'cat /var/log/aide/aide.log (changed host)'
ansible.builtin.command: 'cat /var/log/aide/aide.log'
register: '__molecule__aide_changed_log_result'
changed_when: false

- name: 'Assert that the check fails and reports only the file below the regular rule'
ansible.builtin.assert:
that:
- '__molecule__aide_changed_result["stdout"] == "exit-code"'
- '"/srv/lfops-molecule/probe" in __molecule__aide_changed_log_result["stdout"]'
- '"/srv/lfops-molecule/ignored" not in __molecule__aide_changed_log_result["stdout"]'
- '"/opt/lfops-molecule-probe" not in __molecule__aide_changed_log_result["stdout"]'
fail_msg: '{{ __molecule__aide_changed_log_result["stdout"] }}'

# 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)'
ansible.builtin.stat:
path: '/var/lib/aide/aide.db.gz'
checksum_algorithm: 'sha256'
register: '__molecule__aide_db_before_failed_result'

- name: 'Edit /etc/aide.conf by hand'
ansible.builtin.lineinfile:
path: '/etc/aide.conf'
line: '# lfops-molecule'

- name: 'Reset the end-of-play messages (run after a failed check)'
ansible.builtin.set_fact:
__shared__end_of_play_messages: []

- name: 'Run the aide role (run after a failed check)'
ansible.builtin.include_role:
name: 'linuxfabrik.lfops.aide'

- name: 'sha256sum /var/lib/aide/aide.db.gz (after the run after a failed check)'
ansible.builtin.stat:
path: '/var/lib/aide/aide.db.gz'
checksum_algorithm: 'sha256'
register: '__molecule__aide_db_after_failed_result'

- name: 'Assert that the database was left alone and the operator was told'
ansible.builtin.assert:
that:
- '__molecule__aide_db_after_failed_result["stat"]["checksum"] == __molecule__aide_db_before_failed_result["stat"]["checksum"]'
- '__shared__end_of_play_messages | select("match", "aide: ") | list | length == 1'

- name: 'Remove the probe files'
ansible.builtin.file:
path: '{{ item }}'
state: 'absent'
loop:
- '/opt/lfops-molecule-probe'
- '/srv/lfops-molecule'

# the same tasks `--tags aide:update_db` runs. they have to accept the unit and the config
# that the two role runs above redeployed, and clear the failed state of the last check.
- name: 'Run the update-db tasks of the aide role'
ansible.builtin.include_role:
name: 'linuxfabrik.lfops.aide'
tasks_from: 'update-db.yml'

- name: 'systemctl is-failed aide-check.service (after update-db)' # noqa command-instead-of-module (read-only state query)
ansible.builtin.command: 'systemctl is-failed aide-check.service'
register: '__molecule__aide_failed_after_update_db_result'
changed_when: false
failed_when: false

- name: 'Assert that update-db cleared the failed state'
ansible.builtin.assert:
that:
- '__molecule__aide_failed_after_update_db_result["rc"] != 0'

- name: 'systemctl start aide-check.service (restored host)' # noqa command-instead-of-module (need to run the oneshot and read its Result)
ansible.builtin.command: 'systemctl start aide-check.service'
changed_when: false
failed_when: false

- name: 'systemctl show --property=Result aide-check.service (restored host)' # noqa command-instead-of-module (no module reads a oneshot Result)
ansible.builtin.command: 'systemctl show --property=Result --value aide-check.service'
register: '__molecule__aide_restored_result'
changed_when: false

- name: 'Assert that the check runs clean again once the current state was accepted'
ansible.builtin.assert:
that:
- '__molecule__aide_restored_result["stdout"] == "success"'
7 changes: 7 additions & 0 deletions playbooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ Calls the following roles (in order):
* [acme_sh](https://github.com/Linuxfabrik/lfops/tree/main/roles/acme_sh)


## aide.yml

Calls the following roles (in order):

* [aide](https://github.com/Linuxfabrik/lfops/tree/main/roles/aide)


## alternatives.yml

Calls the following roles (in order):
Expand Down
35 changes: 35 additions & 0 deletions playbooks/aide.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
- name: 'Playbook linuxfabrik.lfops.aide'
hosts:
- 'lfops_aide'

pre_tasks:
- ansible.builtin.import_role:
name: 'shared'
tasks_from: 'log-start.yml'
tags:
- 'always'

- ansible.builtin.import_role:
name: 'shared'
tasks_from: 'global-variables.yml'
tags:
- 'always'


roles:

- role: 'linuxfabrik.lfops.aide'


post_tasks:
- ansible.builtin.import_role:
name: 'shared'
tasks_from: 'log-end.yml'
tags:
- 'always'

- ansible.builtin.import_role:
name: 'shared'
tasks_from: 'print-messages.yml'
tags:
- 'always'
1 change: 1 addition & 0 deletions playbooks/all.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
- import_playbook: 'acme_sh.yml'
- import_playbook: 'aide.yml'
- import_playbook: 'alternatives.yml'
- import_playbook: 'ansible_init.yml'
- import_playbook: 'apache_httpd.yml'
Expand Down
Loading