Skip to main content
RunBook Academy

AnsibleXLVII · Controller SecurityController security

Controller compromise is fleet compromise

Intermediate⏱ ~18 minansible-core

What you'll learn

  • State the controller threat model in terms of what an attacker gains rather than what they exploit
  • Explain why there is no agent-side authorisation to stop a compromised controller
  • Enumerate what a read of the controller filesystem yields, separately from a write
  • Rank the assets on a controller by what their loss costs
  • Justify controller hardening to someone who thinks the controller is low-value

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Say it once, plainly, and let the rest of the part follow from it:

Compromise of the controller is compromise of every host it manages.

Not “increases risk to”. Not “could lead to”. The controller holds credentials that authenticate as a privileged user on every managed node, and the managed nodes have no way to tell a legitimate run from an illegitimate one. An attacker who has your controller does not need to find a vulnerability in your fleet. They have the mechanism you built for exactly this purpose, working exactly as designed.

That is an uncomfortable sentence and it is the honest one. Everything in this part is an attempt to make it less true, and none of it makes it false.

Why there is nothing to stop them

A managed node’s sshd receives a connection presenting a valid key for an account with sudo. It authenticates the key, grants the session, and escalates. From the host’s point of view, that is a correct and successful authentication.

There is no step at which the host asks whether this run was authorised, whether it came from a pipeline or a laptop, whether it is 03:00 on a Sunday, or whether the change matches an approved ticket. Those questions have no representation in the protocol. The host has one question — is this key permitted for this account — and the answer is yes, because you made it yes.

This is what “agentless” costs. An agent could hold policy: refuse certain operations, require a signed instruction, rate-limit. Ansible has no agent, so there is no policy on the far side. All of the authorisation lives on the controller, which means all of it is lost when the controller is.

What is actually on a controller

Rank the assets by what their loss costs, because that ranking drives every decision in the rest of this part.

AssetA read gives an attackerA write gives an attacker
SSH private keys for automationfleet access as the automation user, usually with sudoreplace with their own key
Vault password or its sourceevery secret in the repository, historic and currentre-encrypt secrets under their key
ansible.cfg and inventorythe map of the estate: every hostname, group and roleredirect runs, change the become user, disable host key checking
The playbook repositoryyour architecture, and often credentials that leaked into itarbitrary code that runs as root everywhere on next run
Fact cacheinventory of software and versions across the fleetpoisoned facts that change conditional behaviour
~/.ansible/tmp, logsmodule arguments, which contain passwords

Two things stand out from that table.

Read access is not a lesser compromise. Someone who can only read the controller filesystem walks away with the automation private key and the vault password. That is fleet access. The distinction between read and write matters much less here than it does almost anywhere else, which is why Part III’s material on filesystem layout and group permissions is a security control and not housekeeping.

The inventory is a valuable asset by itself. Estates protect credentials and leave the inventory readable, because it “contains no secrets”. It contains the complete topology of the environment, including which hosts are databases, which are in production, and which group has the weakest configuration. Reconnaissance that would otherwise take an attacker weeks is a file.

What you can still do

The framing is deliberately bleak, so it is worth stating what remains possible before the part continues. None of these prevent the outcome; they change its size, its speed, or the odds of noticing.

Reduce what one credential reaches. Separate keys and separate controllers per environment mean a compromise of staging is not a compromise of production. This is the highest-value control available and it is the subject of lesson 4.

Constrain the credential at the far end. authorized_keys options can limit where a key may be used from and what it may run. That is the only authorisation that lives on the managed node rather than on the controller, which makes it the only control that survives controller compromise at all.

Make the controller a poor place to hide. A dedicated host with a small package set and restricted egress is one where unexpected processes and unexpected outbound connections are visible. On a general-purpose workstation they are not.

Make runs accountable. Named accounts, logged runs, and a record of who ran what. This does not stop an attacker; it decides whether you can answer “what did they change” afterwards, which is the difference between a scoped recovery and rebuilding the estate.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Why does a compromised controller not need to exploit any vulnerability to change every managed node?

  2. Q2. Which control from this part actually survives compromise of the controller itself?

  3. Q3. An attacker gains read-only access to the controller filesystem. What have they obtained? Select all that apply.

  4. Q4. Because Ansible has no authentication of its own, the security controls that matter most are OpenSSH and sudo controls enforced on the managed node.

Passing score: 75%. Answers are checked in this browser.