Skip to main content
RunBook Academy

AnsibleXXI · Secrets ManagementSecrets management

When the secret should not be in the repository at all

Advanced⏱ ~24 minansible-core

What you'll learn

  • Explain why a lookup executing on the controller is the property that makes external secret retrieval work
  • Retrieve a secret with the community.hashi_vault lookups and handle the returned structure
  • Compare a long-lived encrypted file with a short-lived dynamic credential on revocation and blast radius
  • Plan for the secret store being unavailable, since it is now in the critical path of every run

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.

Every lesson in this part so far has worked on the same premise: the secret is in your repository, encrypted, and the job is to keep it that way and to stop it escaping once decrypted.

This lesson questions the premise. A secret that is never in the repository cannot leak from the repository, cannot be committed by accident, cannot sit in a clone on a laptop that gets stolen, and does not need a password you have to distribute.

The property that makes this work

Lookups run on the controller, not on the managed node. That is the whole architecture in one sentence, and it is worth proving rather than asserting.

Read-only / Safea lookup resolves for a host that is never contacted
$ ansible-playbook -i inv.ini lk.yml
TASK [Show the lookup value on the controller before any connection] ****
  "msg": "lookup resolved to: ebrandi"

TASK [Now actually connect] ********************************************
fatal: [web1.example.com]: UNREACHABLE! => {"changed": false,
"msg": "Task failed: Failed to connect to the host via ssh:
ssh: connect to host 192.0.2.10 port 22: Connection timed out",
"unreachable": true}

The lookup produced a value; the host was never reachable. Whatever the lookup did, it did on the controller.

Three consequences follow, and they are the reason this pattern is practical at all:

Managed nodes need no access to the secret store. They do not need network reachability to it, credentials for it, or any client software. Four hundred hosts do not become four hundred secret store clients.

One credential fetches for the whole fleet. The controller authenticates once and distributes what it retrieves. That is one identity to manage, audit and revoke rather than one per host.

The controller becomes even more sensitive. It now holds the identity that can retrieve production credentials on demand. The previous lesson’s conclusion applies with more force: a controller that can decrypt or retrieve production secrets is a production system.

Retrieving a secret

The community.hashi_vault collection is the reference implementation. Every cloud provider ships an equivalent collection, and the shape is the same in each.

Read-only / Safethe KV v2 lookup, with the return structure the docs describe
- name: Deploy with a credential fetched at run time
hosts: appservers
vars:
  # Runs on the controller. Nothing is stored in this repository.
  db_creds: "{{ lookup('community.hashi_vault.vault_kv2_get',
                       'billing/database',
                       engine_mount_point='secret',
                       url='https://vault.example.com:8200',
                       auth_method='token',
                       token=lookup('env', 'VAULT_TOKEN')) }}"
tasks:
  - name: Write the application credentials
    ansible.builtin.template:
      src: credentials.j2
      dest: /etc/billing/credentials
      owner: billing
      mode: '0600'
    vars:
      db_password: "{{ db_creds.secret.password }}"
    no_log: true

Two details from the plugin documentation worth getting right first time.

The return is a dictionary, not a string. The documented accessible keys are secret“The data field within the data field. Equivalent to raw.data.data — plus data, metadata and raw. So the value you want is db_creds.secret.<key>, and a lookup returning something that looks nothing like a password is almost always this.

engine_mount_point defaults to secret. If your KV engine is mounted elsewhere, every path is wrong in a way that presents as “the secret does not exist”.

The collection provides a family of lookups — hashi_vault, vault_kv1_get, vault_kv2_get, vault_read, vault_list, vault_login, vault_write, vault_token_create and vault_ansible_settings — plus modules for managing the store itself, including dynamic database credentials. Read the one you need; do not extrapolate parameter names from a sibling.

Static secrets versus dynamic credentials

Fetching a stored password at run time is an improvement. It is not the main prize.

The larger change is a credential that did not exist before the run and expires after it. The store creates a database user on demand with a lease of, say, one hour, hands it over, and revokes it when the lease ends.

Encrypted file in the repositoryFetched static secretDynamic credential
In the repositoryyes, encryptednono
Lifetimeuntil someone rotates ituntil someone rotates itminutes to hours
Revocationrotate and redeploy everywhererotate at the storeexpires on its own
A leaked run logexposes a live credentialexposes a live credentialexposes an expired one
Attributionnonewhich identity fetched itwhich identity, and this exact lease
Works when the store is downyesnono

The fourth row is the one that reframes the whole part. Every leak route in this part’s first lesson — the log file, the CI artifact, the pasted terminal, the --diff output — leaks a credential that has already expired if the credential lived for an hour. The exposure does not disappear, but its value decays to nothing without anybody doing anything.

Note also that dynamic credentials do not remove the need for no_log, Vault or output discipline. They shorten the window during which a leak matters. That is a different kind of protection from the others in this part, and it composes with them rather than replacing them.

Knowledge check

Knowledge check · 4 questions

  1. Q1. Why does retrieving secrets with a lookup not require the managed nodes to reach the secret store?

  2. Q2. What does a dynamic credential with a one-hour lease change about the leak routes enumerated at the start of this part?

  3. Q3. Which are genuine costs of moving secrets to an external store? Select all that apply.

  4. Q4. Because the community.hashi_vault KV lookups return the secret value directly as a string, it can be used wherever a password is expected without further indexing.

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