Audit: tells you that 47 of 312 hosts have PermitRootLogin yes.
Nothing changes. The output is a finding.
Enforce: changes PermitRootLogin on 47 hosts and restarts sshd.
The commands differ by one flag. The decisions differ by everything, and
this lesson is about not letting the small distance between the commands
disguise the large distance between the decisions.
Audit mode is --check --diff
For a role built out of modules that support check mode, the audit run is
the same role with two flags.
Read-only / Safethe audit run— Nothing is changed. changed=N is a count of hosts that would be corrected, which is the compliance finding.
ansible-playbook -i inventories/prod compliance.yml \
--limit all \
--check --diff
The recap becomes the report: changed is the count of non-compliant
hosts, and --diff shows what each one differs by. That is a genuinely
good property — the same code that fixes the estate measures it — and it
is why compliance automation is usually built this way.
It has two limits, one covered in the next lesson and one here.
The next lesson’s limit: check mode is not uniformly honest across
modules, so a green audit is not the same as a compliant estate.
This lesson’s limit: --diff prints file contents. An audit run over
managed configuration files prints those files to whoever reads the
output — and an audit run is precisely the kind of run whose output goes
into a log, a ticket, and a compliance evidence package. If any managed
file contains a credential, the audit has just published it. Part XXI
covers no_log and the mechanics; the compliance-specific point is that
the audit is the run most likely to be archived and shared.
Enforce is a separate decision
The reason to keep enforcement separate is not technical. Technically it
is one flag. It is organisational, and the argument is this: an audit is
a measurement and an enforce is a change, and changes to 300 production
hosts have a process.
The separation buys three things:
Different authorisation. Reading the estate needs read access.
Changing 300 hosts needs change approval. Wiring them to the same
trigger means the audit schedule holds change authority.
Different frequency. Audit hourly, enforce during a window.
A gap in which somebody looks. The finding exists for a period
before anything acts on it, and that period is where a control that
would break production gets caught.
Designing a role for both
The design that works keeps one set of task definitions and controls
whether they execute.
Configuration changeone role, two entry points— compliance_enforce defaults to false. The enforce path requires an explicit variable, so no invocation enforces by accident.
check_mode: false on the measurement task. Without it, the slurp
that gathers current state is skipped during an audit run, and every
subsequent condition evaluates against nothing. A read-only gathering
task inside a check run needs this. It was verified by execution for the
next lesson and the mechanism is the same.
The enforcing task is guarded by a variable, not by --check. This
is what makes “audit” a mode of the role rather than a mode of the
command line. Someone who forgets --check on an audit run gets an
audit, not an enforcement.
validate on lineinfile. The command is run against a temporary
copy before the real file is replaced, so a baseline that would produce
an unparseable sshd_config fails the task instead of the host. For SSH
specifically this is not optional, and it is the difference between a
bad baseline and an unreachable fleet.
Staged remediation
The sequence that keeps a control from becoming an incident. Each stage
answers a question the previous one raised.
Stage
What runs
The question it answers
1. Report
Audit across the fleet
How many hosts, and are they alike?
2. Enforce one
Enforce on a single representative host
Does applying this break anything?
3. Enforce a subset
One environment or one group
Does it break anything at scale or in combination?
4. Widen
Group by group, with validation between
—
5. Continuous
Audit on a schedule, enforce on exception
Is the estate staying compliant?
Stage 1 is not a formality. The distribution of the finding is the most
useful thing the audit produces:
47 of 312 hosts non-compliant, all in one group — probably one
build generation, and probably safe to enforce as a group.
47 of 312, scattered across every group — something is
un-converging them, and enforcing will fix the symptom while whatever
causes it continues.
311 of 312 non-compliant — the baseline is probably wrong, or the
check is. Enforcing would change the entire estate on the strength of
a rule nobody has validated.
That third row is worth pausing on. A control that reports almost
universal non-compliance is far more likely to be a bad control than an
estate that is uniformly wrong, and the instinct to fix 311 hosts is the
instinct to skip stage 2.
Knowledge check
Knowledge check · 4 questions
Q1. An audit run reports 311 of 312 hosts non-compliant with a newly written control. What is the most appropriate next step?
Q2. Which of these should be checked before a control is enforced anywhere? Select all that apply.
Q3. A read-only task that gathers current state for a compliance check needs check_mode: false, or it will be skipped during an audit run and every condition depending on it will evaluate against nothing.
Q4. A dashboard shows hourly compliance findings and has a remediate button that re-runs the same job without --check. What is the core problem?
Passing score: 75%. Answers are checked in this browser.