The last lesson of this part is about the failure it cares about most,
and it is not a technical one.
An audit job nobody reads is worse than no audit, because it
manufactures the belief that someone is looking.
An estate with no compliance automation has a known gap. Somebody may
eventually address it. An estate with a nightly compliance run producing
a report that nobody has opened in seven months has a gap that is
invisible, is documented as covered, and appears in the control register
with a green tick.
The second one is worse, and everything in this lesson is about not
building it.
Running the audit on a timer
Mechanically simple. The audit playbook cannot enforce — that separation
is from lesson 1 — so scheduling it needs no change authorisation.
Read-only / Safethe scheduled invocation— --check --diff cannot change a target. The run ID ties the evidence files to this invocation.
A systemd timer is generally better than cron for this: it records
the last run in a way that is queryable, handles a missed run explicitly
through Persistent=true, and puts the job output in the journal
without a mail alias. cron works, and the ansible.builtin.cron
module manages it if that is the estate convention.
The scheduling detail that matters more than either: stagger the run
across the fleet or accept the load spike. 3,000 hosts audited at
03:00 is 3,000 SSH connections and 3,000 fact gatherings in a window,
which is a self-inflicted load event with a compliance justification.
Alert on the rate, not on the diffs
An estate of 300 hosts has drift. Some of it is being worked on, some is
excepted, and some is genuinely new. Alerting on every finding produces a
message per finding per run, which produces a filter rule, which produces
silence.
The useful signals are derived:
Signal
Why it is the right thing to alert on
Newly failing controls
A host that passed yesterday and fails today is an event with a cause
Drift rate
Findings per day, trending. A rise means a process changed
Hosts newly non-compliant
Points at a host, which points at a change
Controls with rising failure counts
Points at a control, which may be wrong
Hosts unmeasured across N runs
The most serious finding, and the quietest
Exceptions past their review date
The mechanism by which exceptions become permanent
None of those is “host web41 has PermitRootLogin yes”. That is a
finding, it belongs in the report, and it is not an alert — because it
was also true yesterday, and the day before, and alerting on it every
night is how the channel becomes noise.
Tuning out noise without hiding it
Some findings are expected. A build host that legitimately has compilers
installed, an appliance whose vendor requires a setting the baseline
forbids, a group mid-migration.
Two ways to make those stop generating alerts, and only one is safe.
The wrong one: remove the control for those hosts, or add a when
that skips them. The finding disappears from the report. Six months
later nobody knows the control is not being evaluated there, and the
report claims coverage it does not have.
The right one: the exception mechanism from lesson 1 — recorded in
inventory, with a reason, a compensating control, an approver and a
review date. The control still runs and still produces its finding; the
finding is classified as excepted rather than non-compliant.
Read-only / Safethree categories, not two— The excepted category is what makes a compliance percentage believable. A report showing 100% on an estate everyone knows has exceptions is a report nobody trusts.
- name: Classify this finding
ansible.builtin.set_fact:
finding_status: >-
{{
'compliant' if control_passed
else ('excepted' if control_name in (compliance_exceptions | default({}))
else 'non_compliant')
}}
- name: An exception past its review date is a finding in its own right
ansible.builtin.assert:
that:
- compliance_exceptions[control_name].review_date is version(
ansible_date_time.date, '>=')
fail_msg: >-
Exception for {{ control_name }} on {{ inventory_hostname }}
expired on {{ compliance_exceptions[control_name].review_date }}
quiet: true
when: finding_status == 'excepted'
The second task is what stops exceptions becoming permanent. An
exception with no expiry is a decision never to apply the control,
written in a way that avoids anyone having to say so.
Knowledge check
Knowledge check · 4 questions
Q1. A nightly compliance job is monitored by alerting on a non-zero exit code from ansible-playbook. Two months later the estate has drifted substantially and nothing has fired. Why?
Q2. Which of these are appropriate things to alert on from a scheduled compliance run? Select all that apply.
Q3. The right way to stop a legitimately non-compliant appliance from generating findings is an exception recorded in inventory with a reason, an approver and a review date - not a when condition that skips the control for that host.
Q4. A compliance report has been produced nightly for seven months and lists the same twelve findings each time. What is the design defect that will lead to it going unread?
Passing score: 75%. Answers are checked in this browser.