Add 5 wiki articles from 2026-04-17/18 work
- ghost-smtp-mailgun-setup: two-system email config (newsletter API + transactional SMTP) - firewalld-fleet-hardening: Fedora fleet firewall audit-and-harden pattern with Ansible - clamav-fleet-deployment: fleet deployment with nice/ionice throttling + quarantine - ansible-check-mode-false-positives: when: not ansible_check_mode guard for verify/assert tasks - ghost-emailanalytics-lag-warning: submitted status, lag counter, fetchMissing skip explained
This commit is contained in:
135
05-troubleshooting/ansible-check-mode-false-positives.md
Normal file
135
05-troubleshooting/ansible-check-mode-false-positives.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Ansible Check Mode False Positives in Verify/Assert Tasks
|
||||
domain: selfhosting
|
||||
category: troubleshooting
|
||||
tags:
|
||||
- ansible
|
||||
- check-mode
|
||||
- dry-run
|
||||
- assert
|
||||
- handlers
|
||||
- troubleshooting
|
||||
status: published
|
||||
created: 2026-04-18
|
||||
updated: 2026-04-18T11:13
|
||||
---
|
||||
# Ansible Check Mode False Positives in Verify/Assert Tasks
|
||||
|
||||
## The Problem
|
||||
|
||||
`ansible-playbook --check` (dry-run mode) reports failures on verify and assert tasks that depend on handler-triggered side effects — even when the playbook is correct and would succeed on a real run.
|
||||
|
||||
**Symptom:** Running `--check` produces errors like:
|
||||
|
||||
```
|
||||
TASK [Assert hardened settings are active] ***
|
||||
fatal: [host]: FAILED! => {
|
||||
"assertion": "'permitrootlogin without-password' in sshd_effective.stdout",
|
||||
"msg": "One or more SSH hardening settings not effective"
|
||||
}
|
||||
```
|
||||
|
||||
But a real run (`ansible-playbook` without `--check`) succeeds cleanly.
|
||||
|
||||
## Why It Happens
|
||||
|
||||
In check mode, Ansible simulates tasks but **does not execute handlers**. This means:
|
||||
|
||||
1. A config file task reports `changed` (it would deploy the file)
|
||||
2. The handler (`reload sshd`, `reload firewalld`, etc.) is **never fired**
|
||||
3. A subsequent verify task runs `sshd -T` or `ufw status verbose` against the **current live state** (pre-change)
|
||||
4. The assert compares the current state against the expected post-change state and fails
|
||||
|
||||
The verify task is reading reality accurately — the change hasn't happened yet — but the failure is misleading. It suggests the playbook is broken when it's actually correct.
|
||||
|
||||
## The Fix
|
||||
|
||||
Guard verify and assert tasks that depend on handler side effects with `when: not ansible_check_mode`:
|
||||
|
||||
```yaml
|
||||
- name: Verify effective SSH settings post-reload
|
||||
ansible.builtin.command:
|
||||
cmd: sshd -T
|
||||
register: sshd_effective
|
||||
changed_when: false
|
||||
when: not ansible_check_mode # sshd hasn't reloaded in check mode
|
||||
|
||||
- name: Assert hardened settings are active
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'permitrootlogin without-password' in sshd_effective.stdout"
|
||||
- "'x11forwarding no' in sshd_effective.stdout"
|
||||
fail_msg: "SSH hardening settings not effective — check for conflicting config"
|
||||
when: not ansible_check_mode # result would be pre-change state
|
||||
```
|
||||
|
||||
This skips the verify/assert during check mode (where they'd produce false failures) while keeping them active on real runs (where they catch actual misconfigurations).
|
||||
|
||||
## When to Apply This Guard
|
||||
|
||||
Apply `when: not ansible_check_mode` to any task that:
|
||||
|
||||
- Reads the **active/effective state** of a service after a config change (`sshd -T`, `ufw status verbose`, `firewall-cmd --list-all`, `nginx -T`)
|
||||
- **Asserts** that the post-change state matches expectations
|
||||
- Depends on a **handler** having fired first (service reload, daemon restart)
|
||||
|
||||
Don't apply it to tasks that check pre-existing state (e.g., verifying a file exists before modifying it) — those are valid in check mode.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### SSH daemon verify
|
||||
|
||||
```yaml
|
||||
- name: Verify effective sshd settings
|
||||
ansible.builtin.command: sshd -T
|
||||
register: sshd_out
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Assert sshd hardening active
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'maxauthtries 3' in sshd_out.stdout"
|
||||
when: not ansible_check_mode
|
||||
```
|
||||
|
||||
### UFW status verify
|
||||
|
||||
```yaml
|
||||
- name: Show UFW status
|
||||
ansible.builtin.command: ufw status verbose
|
||||
register: ufw_status
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Confirm default deny incoming
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'Default: deny (incoming)' in ufw_status.stdout"
|
||||
when: not ansible_check_mode
|
||||
```
|
||||
|
||||
### nginx config verify
|
||||
|
||||
```yaml
|
||||
- name: Test nginx config
|
||||
ansible.builtin.command: nginx -t
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
```
|
||||
|
||||
## Trade-off
|
||||
|
||||
Guarding with `when: not ansible_check_mode` means check mode won't validate these assertions. The benefit — no false failures — outweighs the gap because:
|
||||
|
||||
- Check mode is showing you what *would* change, not whether the result is valid
|
||||
- Real runs still assert and will catch actual misconfigurations
|
||||
- The alternative (failing check runs) erodes trust in `--check` output
|
||||
|
||||
If you need to verify the effective post-change state in check mode, consider splitting the playbook into a deploy pass and a separate verify-only playbook run without `--check`.
|
||||
|
||||
## See Also
|
||||
|
||||
- [ssh-hardening-ansible-fleet](../02-selfhosting/security/ssh-hardening-ansible-fleet.md)
|
||||
- [ufw-firewall-management](../02-selfhosting/security/ufw-firewall-management.md)
|
||||
- [ansible-getting-started](../01-linux/shell-scripting/ansible-getting-started.md)
|
||||
Reference in New Issue
Block a user