Skip to main content
RunBook Academy

AnsibleXLVII · Controller SecurityController security

Keys that are only good for automation

Advanced⏱ ~24 minopenssh-clientansible-core

What you'll learn

  • Separate automation keys from human keys, and state what each separation buys
  • Apply authorized_keys restrictions and explain why they are the only control that survives controller compromise
  • Choose between a passphrase-protected key with an agent and an unattended key, on evidence
  • Recognise the single shared root key as the anti-pattern it is, and cost its removal
  • Explain how SSH certificates change key distribution and revocation at fleet scale

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.

Part XX covered how Ansible uses SSH keys: ansible_ssh_private_key_file per group, the agent settings that make an unattended run possible, and why separating keys by what they may reach is better than separating them by who is using them.

This lesson is about the key as a credential to be governed: where it comes from, what it may do once it exists, and how you take it away. Those questions are answered on the managed node, not on the controller, which is the entire reason they matter after lesson 1.

One key, one purpose

An automation key should have exactly one job and no human should ever use it.

That sounds like a hygiene rule. It is actually an accountability one. The managed node’s sshd log records the fingerprint of the key that authenticated, not the name of a person. If one key is used both by the scheduler and by three engineers logging in to look at something, that fingerprint means “somebody, doing something”. If it is used only by the scheduled run, the same log line is a fact.

So the estate wants at least these separations:

Automation from human. Engineers authenticate with their own keys or the organisation’s identity system; automation uses keys no person holds.

Environment from environment. A staging key that cannot open a production host is the control that makes a staging compromise survivable. Part XX establishes the mechanism, lesson 3 the organisational case; this is where it becomes a property of the key material itself.

Purpose from purpose, where the purposes differ in blast radius. A key used by a read-only compliance scan is not the key used by the patch run. Constraining the first (see below) is easy and worth doing; the second cannot be meaningfully constrained, which is itself informative.

Constraining the key at the far end

authorized_keys supports options that prefix a key entry and restrict what a session authenticated by it may do. These are enforced by sshd on the managed node.

That sentence is the important one. Everything else in this part is configuration the controller applies to itself, and an attacker holding the controller changes it. authorized_keys options are read by software on the target that does not trust the controller, so they are the only constraint in this course that survives the compromise lesson 1 describes.

The options worth knowing:

OptionEffect
from="pattern-list"The key is only accepted from matching source addresses or hostnames
command="..."Any session using this key runs this command, ignoring what the client asked for
restrictDisables all forwarding, agent forwarding, port forwarding, X11 and PTY allocation
no-agent-forwardingIndividually disables agent forwarding
no-port-forwardingIndividually disables port forwarding
expiry-time="YYYYMMDD"The key stops being accepted after this time

A restricted entry for a read-only monitoring key looks like this:

restrict,from="192.0.2.10",command="/usr/local/bin/collect-facts" ssh-ed25519 AAAA...REPLACE_ME monitoring@controller

restrict is the right default because it is a deny-by-default: it turns off everything and you re-enable what you need with pty, port-forwarding and similar. Listing individual no-* options instead means every future OpenSSH feature is enabled on your key until somebody adds another no-.

Read-only / Safe

Passphrase, agent, or unattended

The trade is genuine and the honest framing is that both options are defensible for different runs.

Passphrase plus agent. The key on disk is useless without the passphrase. An engineer unlocks it once per session into ssh-agent, and Ansible uses the agent. This is correct for interactive work and costs nothing operationally.

It does not solve scheduled runs, because there is no human present to type a passphrase at 03:00. Part XX covers what ansible-core 2.21 offers here — the ssh_agent, private_key and private_key_passphrase settings on the ssh connection plugin — which give a real answer to running unattended without a passphrase-free key sitting on disk.

Unattended key with no passphrase. The key on disk is sufficient by itself. This is what most scheduled automation actually uses, and pretending otherwise produces advice nobody follows.

If you are here, be clear about what protects the key: it is filesystem permissions and the security of the controller, and nothing else. That is precisely why the previous three lessons exist, and it is the strongest argument for restricting the key with from= — because the one thing you can add is that a stolen copy does not work elsewhere.

Certificates, and why they are the scalable answer

Everything above manages access by distributing public keys to every host. That approach has a hard ceiling, and the ceiling is revocation.

To remove a key you must edit authorized_keys on every host that has it. Hosts that are down during that run keep the key. There is no central record of which hosts currently trust which keys, other than by asking all of them.

SSH certificates invert this. A certificate authority signs a user certificate; hosts are configured once to trust the CA (TrustedUserCAKeys in sshd_config); and the certificate carries its own constraints — a validity period, a list of principals, and force-command or source-address restrictions embedded in it by the CA rather than by an entry on each host.

The consequences at fleet scale:

Revocation becomes expiry. An automation certificate valid for twenty-four hours does not need to be revoked; it needs to not be renewed. That converts the hardest operation in key management into the absence of an operation.

Adding a host is one configuration line, not a distribution run. The host trusts the CA and therefore trusts every current certificate.

The constraints travel with the credential. Principals and validity are signed into the certificate, so they cannot be edited on one host and forgotten on another.

The cost is a CA to run, protect and make highly available, because a CA outage means nobody can obtain a certificate. That cost is real and it is why certificates are a fleet-scale answer rather than a starting point. On thirty hosts, authorized_keys and a rotation runbook are proportionate. On three thousand, the rotation runbook is the problem and certificates are the answer.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Why are authorized_keys options the only control in this part that survives compromise of the controller?

  2. Q2. Which authorized_keys restriction applies usefully to a general Ansible automation key?

  3. Q3. What changes when a fleet moves from distributed authorized_keys to SSH certificates? Select all that apply.

  4. Q4. Adding restrict to the first entry of an authorized_keys file constrains every key in that file.

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