TTechclick ⚡ XP 0% All lessons
Ansible · Security · Ansible VaultInteractive · L1 / L2 / L3

Ansible Vault: — Getting Plaintext Passwords Out of Your Playbooks

Your router enable password, an API token and SSH creds are sitting in clear text in group_vars — and that folder is pushed to Git. One clone, one leaked history, one breach. Ansible Vault encrypts those variables at rest with AES-256 so they ride safely in version control and are decrypted only at run time. This lesson gets the secrets out of your playbooks for good.

📅 2026-06-11 · ⏱ 12 min · 3 live demos · 4 infographics · 🏷 10-Q assessment + AI Tutor inline

⚡ Quick Answer

Ansible Vault for L1/L2 engineers and RHCE EX294: stop committing plaintext passwords. Encrypt vars with AES-256 (create/encrypt_string/rekey), supply the password safely, and integrate a secret store.

🎯 By the end you will be able to

Read as:

Pick where you want to start

1

The plaintext problem

Why secrets in committed group_vars are a live grenade.

2

Core workflow

create, encrypt_string, the seven sub-commands, vault.yml.

3

Supplying the password

Prompt, password file, vault IDs, a real secret store.

4

Doing it right

What to encrypt, key out of repo, rekey, the gotchas.

🧠 Warm-up — 3 questions, no score

Just notice which ones make you pause. We answer all three inside the lesson.

1. An engineer deletes the plaintext password line from group_vars and commits. Is the secret now safe?

Answered in The plaintext problem.

2. Where should the vault PASSWORD (the key that decrypts vault.yml) live?

Answered in Supplying the password.

3. You only need to hide ONE token inside an otherwise readable vars file. Which fits best?

Answered in Core workflow.

Most engineers think…

Most engineers think Ansible Vault "hides" their passwords — so once a var is vaulted they treat the repo as safe and stop worrying. They picture the secret being tucked away somewhere clever.

Wrong — and that mindset gets repos breached. Vault doesn't hide anything; it encrypts the value with AES-256 using a password you must keep outside the repo. The committed file is unreadable ciphertext, yes — but the security comes entirely from where the vault password lives. Commit that password (or pass -vvv on a vaulted var, or paste the key into CI logs) and the encryption is worthless. Vault moves the secret to one thing you must protect — the key — instead of scattering plaintext across forty files.

① The plaintext-secret problem — why a password in Git is a grenade

Meet Sneha, an L1 automation engineer at Infosys. She inherits a tidy-looking Ansible repo that pushes config to 200 Cisco and Fortinet boxes. In group_vars/all.yml she finds this: enable_password: Wipro@123, an api_token for the monitoring API, and an ssh_pass. All in clear text. All committed. The repo is mirrored to the company GitHub. That folder is a breach waiting to happen — anyone who clones the repo, sees a fork, or reads the commit history reads every device's enable password in plain sight.

It gets worse than "don't commit secrets." A junior on Sneha's team, Rahul, spotted the same passwords and 'fixed' it by deleting the lines and committing. He felt safe. He wasn't: Git keeps history, so git log -p group_vars/all.yml still shows every old version with the password intact. Deleting a line does not erase it from the repo's past — you'd have to rewrite history, and by then it may already be cloned. That is the single most common, most dangerous misunderstanding about secrets in version control.

👉 So far: plaintext device creds, API tokens and SSH passwords in committed group_vars are readable by anyone with the repo, and deleting them later doesn't help. Next: what Ansible Vault actually does about it.

Enter Ansible Vault. It encrypts the sensitive value — or a whole file — at rest using AES-256, keyed by a password you choose. The committed file is replaced by unreadable ciphertext that begins with the header $ANSIBLE_VAULT;1.1;AES256. Ansible decrypts it only at run time, in memory, when you supply the password. So the secret can sit right there in Git, fully version-controlled and diffed alongside everything else, and a thief who clones the repo gets nothing but encrypted noise.

Figure 1 — Plaintext in Git vs encrypted at rest
A plaintext password in group_vars committed to Git is one leaked clone away from a breach; Vault encrypts it at rest A before-and-after of the same Ansible repository. On the left, the danger path: a group_vars file holds an enable password and an API token in clear text, the repo is pushed to a Git host, anyone who clones or sees the history reads the secret instantly. On the right, the safe path: the same secrets live in a vault.yml encrypted with AES-256 so the committed file is unreadable ciphertext, and the password to decrypt it lives outside the repo, supplied only at run time. Red marks the leaked plaintext and the attacker; blue marks the encrypted, trusted store; amber marks the run-time decision to decrypt; lime marks the key insight; green marks the safe outcome. Same repo, two fates: plaintext in Git vs encrypted at rest Plaintext secrets in group_vars (danger) group_vars/all.yml (committed)enable_password: Wipro@123api_token: sk_live_9f2c...c41ssh_pass: P@ssw0rd! git push → GitHub / GitLab attacker clones / reads historysecret read in clear — game over deleting the line later does NOT erase Git history Encrypted with Ansible Vault (safe) group_vars/all/vault.yml (committed)$ANSIBLE_VAULT;1.1;AES25666386439653...38633166653937336466... (ciphertext) run time: --vault-password-file decrypted only in memoryattacker clones → reads only ciphertext the key never lives in the repo — that's the whole trick leaked / attackerencrypted / trustedrun-time decryptkey insightsafe / allowed
Read both columns for the same repo. Left (red) = plaintext secrets committed, pushed, and read by anyone who clones — and history keeps them. Right (green) = AES-256 ciphertext in Git, decrypted only at run time, with the key kept OUT of the repo.
Daily-life analogy — the society gate-pass register vs a sealed locker

Plaintext secrets in Git are like writing every flat's spare-key location in the open society gate-pass register at the gate — convenient, but every visitor, guard and courier reads it. Ansible Vault is putting those details in a sealed bank locker: the register (your repo) can still be public, but the locker is opened only when you bring the locker key — and you never leave that key in the register. Lose the locker key, and even you can't open it; leave the key in the register, and the lock was pointless.

One sober, recent reminder that the key is the real asset: in 2025, CVE-2025-4656 showed that even a dedicated secret store (HashiCorp Vault) had a flaw in its rekey path that an operator could abuse to cause a denial of service — fixed in Vault 1.20.0. The lesson for an Ansible engineer: encryption tooling is itself attack surface, so the discipline around who holds the key and how you rotate it matters as much as turning encryption on.

Quick check · Q1 of 10

Priya at TCS removed a plaintext API token from vars.yml, committed, and pushed. A teammate says the secret is still exposed. Why is the teammate right?

Correct: b. Git keeps every prior version, so git log -p still reveals the old token — you'd have to rewrite history (and rotate the token). AES-256 is not reversible without the key; vars files aren't inherently public; and Ansible doesn't permanently cache plaintext in /tmp. The real fix is rotate the secret AND encrypt going forward.

Pause & Predict

Predict: if the committed vault.yml is just AES-256 ciphertext, what is the ONE thing an attacker still needs to read your secrets — and where must you make sure it never lives? Type your guess.

Answer: They need the vault PASSWORD (the key). The ciphertext alone is useless. So the entire security model collapses to one rule: the vault password must NEVER live in the repo — not in vault.yml, not in the playbook, not in a 'gitignored' .env you forgot to actually gitignore, not pasted into CI logs. Protect the key and the encrypted file can be as public as you like.

② Core workflow — create, encrypt_string, and the vault.yml convention

Vault is one command, ansible-vault, with seven sub-commands you will use constantly. create opens your $EDITOR on a brand-new file and encrypts it when you save. edit decrypts an existing vault file into a temp editor session and re-encrypts on save (never decrypting to disk). view prints the decrypted content to the screen without editing. encrypt takes an existing plaintext file and encrypts it in place; decrypt reverses that. rekey changes the password. And encrypt_string encrypts a single value to paste inline into a YAML file. That last one is the one most teams reach for.

Terminal — create a fresh encrypted vars file and confirm it's ciphertext on disk
ram@netauto:~/playbooks$ ansible-vault create group_vars/all/vault.yml
New Vault password:
Confirm New Vault password:
# ($EDITOR opens — you type vault_enable_password: Cisco@123 — save & quit)
ram@netauto:~/playbooks$ head -1 group_vars/all/vault.yml
Expected output
$ANSIBLE_VAULT;1.1;AES256
3134663539663...  (ciphertext — the secret is never on disk in clear)

Now the pattern that keeps things readable. The convention is to keep a vault.yml beside your normal group_vars, holding only the encrypted secrets, and to name every encrypted variable with a vault_ prefix. Your plain, reviewable vars file then references them. So vars/main.yml says enable_password: "{{ vault_enable_password }}" (readable, diff-friendly), while vault.yml holds vault_enable_password as ciphertext. Anyone scanning the repo sees instantly which values are secret, and code review still works on the non-secret file.

Terminal — encrypt ONE value inline with encrypt_string (the !vault tag block)
ram@netauto:~/playbooks$ ansible-vault encrypt_string 'Cisco@123' --name 'vault_enable_password'
New Vault password:
Confirm New Vault password:
Expected output
vault_enable_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386439653761...3863316665
          3937336466...c41
Encryption successful

That !vault block you paste straight into a YAML file — for example into group_vars/all.yml or a host_vars file — and the rest of that file stays human-readable. This is the difference from whole-file create/encrypt: with encrypt_string only the one value is opaque, so reviewers can still read and diff the structure around it. Use whole-file when a file is all secrets; use encrypt_string when one or two values hide inside otherwise-public config.

🖥️ This is the screen you'll use — a terminal running ansible-vault encrypt_string from ~/playbooks. (Recreated for clarity — your terminal matches this.)
ram@netauto:~/playbooks — ansible-vault encrypt_string
1
Command
ansible-vault encrypt_string 'Cisco@123' --name 'vault_enable_password'
2
New Vault password
•••••••••••• (typed, never echoed)
Confirm New Vault password
•••••••••••• (must match)
3
Output: tag
vault_enable_password: !vault |
Output: header
$ANSIBLE_VAULT;1.1;AES256
4
Result
Encryption successful
▶ run

The Vault command map, in one tap each

Tap each card — these are the four moves you'll repeat every week, and the exact verb for each.

🆕
create / edit / view
tap to flip

Whole-file: create opens $EDITOR new, edit re-opens it decrypted, view just prints. So: manage a vault.yml that is all secrets.

🔒
encrypt / decrypt
tap to flip

Take an existing plaintext file to ciphertext in place, or back. So: retrofit a file you already wrote, or hand it off in clear.

✂️
encrypt_string
tap to flip

Encrypt ONE value into a !vault block you paste inline. So: hide one token in an otherwise reviewable YAML file.

🔁
rekey
tap to flip

Swap the password old→new on a vault file. So: rotate the key after a leak or when someone leaves the team.

Common mistake — "ansible-playbook errors: Attempting to decrypt but no vault secrets found"

Symptom: your play references a vaulted var and Ansible stops with ERROR! Attempting to decrypt but no vault secrets found. Cause: you ran the playbook but never supplied the vault password — Vault encrypts at rest, so every run that touches a vaulted var needs the key. Fix: add --ask-vault-pass (or --vault-password-file). A close cousin: Decryption failed means you supplied the wrong password — the file is fine, your key isn't.

Quick check · Q2 of 10

Aditya at HCL has a vars file that is 90% plain config and just ONE secret API token inside it. He wants reviewers to keep reading and diffing the file. Which command fits best?

Correct: c. encrypt_string encrypts just the one value into a !vault block, so the rest of the YAML stays readable and reviewable. Encrypting the whole file would make the 90% non-secret config opaque too; create is for a brand-new all-secrets file; Base64 is encoding, not encryption — anyone can decode it instantly.

Pause & Predict

Predict: you run 'ansible-vault edit vault.yml', change a value, and save. Was the secret ever written to disk in plaintext during that edit — and why does the answer matter for a shared dev box? Type your guess.

Answer: No. ansible-vault edit decrypts the content into a temporary in-memory/secured editor session and re-encrypts on save; the cleartext is not committed to the file on disk. That matters on a shared box because another user can't grab the plaintext from the working file mid-edit — but they could still read your $EDITOR's swap/undo files in some setups, so a hardened editor config and not editing secrets on shared hosts is still wise.

③ Supplying the password — prompt, file, vault IDs, and a real secret store

Encryption is half the job; the other half is getting the password to Ansible at run time without leaking it. The simplest is the interactive prompt: ansible-playbook site.yml --ask-vault-pass makes Ansible ask for the password before it runs. Great on a laptop, useless in automation — a cron job or CI pipeline can't type. So the next step is a vault password file: a one-line file you reference with --vault-password-file.

Terminal — a gitignored password file, locked down, then used to run a play
ram@netauto:~/playbooks$ printf '%s' 'mySuperSecretVaultPass' > .vault_pass
ram@netauto:~/playbooks$ chmod 600 .vault_pass
ram@netauto:~/playbooks$ echo '.vault_pass' >> .gitignore
ram@netauto:~/playbooks$ git check-ignore .vault_pass
ram@netauto:~/playbooks$ ansible-playbook site.yml --vault-password-file .vault_pass
Expected output
.vault_pass
PLAY [Push base config to branch routers] **********************
TASK [set enable secret from vault] ****************************
changed: [BR-Mumbai-rtr01]
changed: [BR-Pune-rtr01]
PLAY RECAP *****************************************************
BR-Mumbai-rtr01 : ok=4  changed=1  unreachable=0  failed=0

Notice git check-ignore .vault_pass printed the filename — that's your proof Git will skip it. Two non-negotiables here: chmod 600 (only you can read the password file) and .gitignore (it never enters the repo). On a real CI/CD runner you don't even keep a file: you store the password as a masked pipeline secret, write it to a temp file at job start, run with --vault-password-file, then delete it — and you never echo it into the logs.

Now the grown-up version: multiple vault IDs. You almost never want the same key for dev and prod — if the dev key leaks, prod should stay safe. With --vault-id you label each password: --vault-id dev@dev.vp --vault-id prod@prompt means "use the dev key from a file, and ask me interactively for the prod key." Ansible tries each supplied ID against each encrypted block. That prod@prompt form keeps the production key in a human's head, never on the runner.

Terminal — encrypt with a labelled ID, then run with dev from file + prod by prompt
ram@netauto:~/playbooks$ ansible-vault encrypt_string --vault-id prod@prompt 'Airtel#Prod99' --name 'vault_enable_password'
New vault password (prod):
Confirm new vault password (prod):
ram@netauto:~/playbooks$ ansible-playbook site.yml --vault-id dev@dev.vp --vault-id prod@prompt
Expected output
vault_enable_password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          39383166...0a64
Vault password (prod):
PLAY RECAP ***************************************************
BR-Mumbai-rtr01 : ok=4  changed=1  unreachable=0  failed=0
Spot the version bump

Look at the header in that last output: $ANSIBLE_VAULT;1.2;AES256;prod. A plain vault file is format 1.1; the moment you attach a vault ID label, the format becomes 1.2 and the label is stored right in the header. That's how Ansible knows which key to try first — and how you can tell at a glance whether a block belongs to dev or prod just by reading head -1.

▶ Watch the password reach a vaulted variable, three different ways

Karthik runs the same play three times — once on his laptop, once in CI, once with a secret store. Follow how the password gets in each time, and what's exposed. Press Play for the healthy path, then Break it to see the failure.

① Prompt--ask-vault-pass → Ansible asks; Karthik types it; key lives only in his head + RAM
② Password file--vault-password-file .vault_pass → reads chmod 600, gitignored file; key on disk, not in repo
③ Client script--vault-id prod@vault-keyring-client.py → script fetches key from secret store
④ Decrypt + useVault decrypts vault_enable_password in memory; task pushes enable secret; plaintext gone at exit
Press Play to step through the healthy path. Then press Break it.

The best long-term pattern moves past a static file entirely: a client script (its filename ends in -client.py) that fetches the password from a real secret store. Better still, skip the static-ciphertext model for high-value secrets and pull them at run time with the community.hashi_vault lookup — so nothing sensitive is committed at all, and rotation, audit and RBAC live in the store. Vault-the-file is your floor; a secret store is your ceiling.

Neha at Flipkart faces this

Neha's nightly AWX job that pushes firewall rules suddenly fails for every host with: 'ERROR! Attempting to decrypt but no vault secrets found'. It worked yesterday from her laptop.

Likely cause

On her laptop she runs with --ask-vault-pass and types the key. The scheduled AWX job has no human to type it, and no vault credential was attached to the job template — so Ansible reaches a vaulted var with no key and aborts before touching a single device.

Diagnosis

She confirms the play references vault_ vars, then checks how the password is being supplied in automation versus interactively — the laptop has a key source, the job template does not.

AWX → Templates → nightly-firewall-push → Edit → Credentials → add a 'Vault' credential (or set --vault-password-file via the job's extra args)
Fix

Attach a Vault credential to the AWX job template (AWX injects it as a vault-password-file at run time), or for a raw cron use --vault-password-file pointing at a 600/root-owned file written from the secret store. Never bake the password into the playbook.

Verify

Re-run the job: the PLAY RECAP shows changed/ok with failed=0, and the AWX job output never prints the password (no_log on the sensitive task keeps it out of the log).

Quick check · Q3 of 10

Meera wants dev secrets readable by the whole team from a file, but the PROD key to stay only in a senior engineer's head, never on any runner. Which invocation matches?

Correct: a. dev@dev.vp reads the dev key from a file (team-shareable on a controlled runner) while prod@prompt forces an interactive prompt, so the prod key is never stored anywhere. --ask-vault-pass for both can't run unattended for dev; committing prod.vp puts the key in the repo; and one shared key means a dev leak compromises prod — the exact thing vault IDs prevent.

Pause & Predict

Predict: your CI pipeline stores the vault password as a masked variable, but a task runs a shell command that echoes a vaulted value for 'debugging'. What leaks, and what one Ansible keyword would have stopped it? Type your guess.

Answer: The decrypted secret leaks into the CI job log in clear text — masking the password variable doesn't help, because what's printed is the DECRYPTED value, not the password. The keyword is no_log: true on the task (or play). With no_log set, Ansible replaces that task's output with 'the output has been hidden', so even a careless debug or a failed task can't spill the secret into logs.

④ Doing it right — what to encrypt, key hygiene, rekey, and a worked play

Three habits separate engineers who use Vault from engineers who use it safely. First, encrypt only what's secret — passwords, tokens, private keys, license strings. Encrypting the whole repo makes every diff opaque, kills code review, and tempts people to decrypt-everything-to-debug. Second, keep the key out of the repo, always — the password file is gitignored and chmod 600, and the prod key ideally never touches disk. Third, name secrets with the vault_ prefix so anyone can see at a glance which variables are sensitive.

Now the rotation discipline. When someone leaves the team, when a key is suspected leaked, or just on a schedule, you rotate the vault password with ansible-vault rekey. It asks for the old password, then the new one, and re-encrypts the file under the new key — the plaintext never appears. Rekey changes the vault password; if an actual device password leaked, you must also change that on the device and re-encrypt the new value. Two different secrets, two different rotations.

Terminal — rotate the vault key with rekey (old → new), then confirm it still decrypts
ram@netauto:~/playbooks$ ansible-vault rekey group_vars/all/vault.yml
Vault password:
New Vault password:
Confirm New Vault password:
ram@netauto:~/playbooks$ ansible-vault view group_vars/all/vault.yml --vault-password-file .vault_pass_new
Expected output
Rekey successful
vault_enable_password: Cisco@123
vault_api_token: sk_live_9f2c...c41

Two gotchas bite teams constantly. Diffing encrypted files is useless — a one-character change scrambles the whole ciphertext, so git diff on a vault file is noise. Fix: a per-repo Git diff driver that decrypts on the fly for review, or just keep secrets in a small dedicated vault.yml so the noisy diffs are contained. Accidentally committing the password is the other — a stray .vault_pass that wasn't gitignored, or a key pasted into CI logs. A pre-commit hook that rejects any staged vars file not starting with $ANSIBLE_VAULT stops both classes of accident.

Prove a file is actually encrypted before you commit

Cold, in five seconds: head -1 group_vars/all/vault.yml must print $ANSIBLE_VAULT;1.1;AES256 (or 1.2 with a label) — if you see your secret in clear text, STOP. Then git check-ignore .vault_pass must echo the filename, proving the key is excluded. A pre-commit hook that greps for the $ANSIBLE_VAULT header on every staged *vault* file turns this from a habit into a guarantee.

Worked example — playbook YAML: encrypt the network enable password and use it in a play
# group_vars/all/vars.yml  (plain, committed, reviewable)
enable_password: "{{ vault_enable_password }}"

# group_vars/all/vault.yml  (ciphertext, committed)
#   built with: ansible-vault encrypt_string 'Cisco@123' --name 'vault_enable_password'
vault_enable_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          66386439653761...3863316665

# push_enable.yml  (the play)
- name: Push enable secret to branch routers
  hosts: branch_routers
  gather_facts: false
  tasks:
    - name: Configure the enable secret from Vault
      cisco.ios.ios_config:
        lines:
          - "enable secret {{ enable_password }}"
      no_log: true        # never print the secret, even on failure
Expected output
$ ansible-playbook push_enable.yml -i inventory --vault-password-file .vault_pass
TASK [Configure the enable secret from Vault] ****************
changed: [BR-Mumbai-rtr01]
changed: [BR-Pune-rtr01]
PLAY RECAP **************************************************
BR-Mumbai-rtr01 : ok=1  changed=1  unreachable=0  failed=0
👉 So far: encrypt only secrets, key out of repo, vault_ naming, rekey to rotate, and a worked enable-password play with no_log. Next: the one-card cheat-sheet and the exam angle.
Figure 2 — Ansible Vault — the cheat-sheet
Ansible Vault on one card — the command map, the do/don't list and the first checks every engineer types A nine-tile cheat sheet. Tiles cover the seven vault sub-commands, the encrypt_string one-liner, supplying the password three ways, vault-id for dev versus prod, the vault prefix naming convention, the do list, the don't list, rotation with rekey, and the first verification checks. Each tile carries the exact CLI verbs. Ansible Vault — your one-glance card 7 sub-commandscreate · edit · viewencrypt · decryptrekey · encrypt_stringansible-vault <cmd> file.yml encrypt_stringansible-vault encrypt_string \ 'Cisco@123' \ --name 'vault_enable'one secret, inline, !vault tag Supply the password--ask-vault-pass (prompt)--vault-password-file .vp--vault-id prod@promptfile → chmod 600 + .gitignore Multiple vault IDs--vault-id dev@dev.vp \--vault-id prod@promptseparate keys per env =smaller blast radius Naming conventionvault_enable_passwordenable_password: "{{ vault_… }}"vault_ prefix → secret isobvious at a glance DO✓ encrypt only secrets✓ key OUT of repo✓ per-env passwords✓ rotate with rekey DON'T✗ commit the password file✗ same key for dev + prod✗ -vvv a vaulted var (leaks)deleting a line ≠ erasing Git history Rotate (rekey)ansible-vault rekey vault.ymlold pass → new passdo after any leak / offboard First checkshead -1 vault.yml → $ANSIBLE_VAULTansible-vault view vault.ymlgit check-ignore .vault_pass
Your one-card map of this whole lesson — the seven sub-commands, encrypt_string, the three password sources, vault IDs, the vault_ naming rule, the do/don't list, rekey, and the first checks. Keep it open in your first week and before any RHCE lab.
Figure 3 — Pick the right secret-holding method
Whole-file vault vs encrypt_string vs an external secret store: pick by who shares the repo and how dynamic the secret is A three-column decision card comparing the ways to hold a secret. Column one, encrypt a whole vars file with ansible-vault create or encrypt: best when a file is all secrets, but the file is opaque so diffs are useless. Column two, encrypt_string to embed one ciphertext value inside an otherwise readable YAML file with a vault prefixed name: best when only one or two values are secret and you want the rest reviewable. Column three, an external secret store such as HashiCorp Vault read through the community.hashi_vault lookup at run time: best for rotation, audit and dynamic secrets, with no static ciphertext in the repo at all. Amber marks the decision points, blue marks the trusted encrypted options, green marks the recommended long-term path. Three ways to hold a secret — and when to reach for each Whole-file vault ansible-vault create vault.yml ✓ whole file is secrets ✓ simplest to start ✗ opaque — diff useless ✗ all-or-nothing edit Use when:a vault.yml that is 100% secrets edit: ansible-vault edit vault.yml view: ansible-vault view vault.yml encrypt_string (inline) ansible-vault encrypt_string ✓ only 1–2 values secret ✓ rest of YAML reviewable ✓ vault_ prefix = obvious ✗ many values = clutter Use when:one token inside a mostly-plain file --name 'vault_api_token' paste the !vault block into YAML External secret store community.hashi_vault lookup ✓ no ciphertext in repo ✓ rotation + audit + RBAC ✓ dynamic / short-lived ✗ needs a store + auth Best long-term:rotate without re-editing repo Vault file still secures thestore's auth token at the edge risky / plaintextencrypted / trusteddecision pointkey insightrecommended
Decision card: whole-file vault when a file is all secrets, encrypt_string when one value hides in readable YAML, an external secret store (community.hashi_vault) as the long-term path for rotation and audit. Green = recommended ceiling.

For your certification path this lesson lands squarely on the RHCE EX294 objective "use Ansible Vault in playbooks to protect sensitive data." In the lab you'll typically be asked to create an encrypted vault.yml, reference its variables from a play, and run that play non-interactively using a vault password file — exactly the workflow above. Practice doing it without notes: ansible-vault create, reference with the vault_ prefix, run with --vault-password-file, and add no_log: true on the sensitive task. That muscle memory is worth easy marks.

Figure 4 — How a play consumes an encrypted variable at run time
How a play uses an encrypted variable: Ansible reads ciphertext, the password source decrypts it in memory, the value is used, then it is gone A left-to-right flow of how an encrypted variable is consumed at run time. Step 1 ansible-playbook loads the encrypted vault.yml from the repo as ciphertext. Step 2 a password source provides the vault password — either an interactive prompt with --ask-vault-pass, a gitignored password file with --vault-password-file, or a client script that fetches it from a secret store. Step 3 Vault uses AES-256 to decrypt the variable into memory only. Step 4 the task substitutes the value into the device config and the plaintext is discarded when the run ends; with no_log set, it is never printed. A break note shows what happens when the wrong password or no password is supplied. Run time: ciphertext in → secret used → plaintext gone 1 · vault.yml$ANSIBLE_VAULT AES256ciphertext from repo 2 · password sourceprompt · file · client script --ask-vault-pass(prompt) --vault-password-file -client.py →secret store 3 · AES-256 decryptin MEMORY onlynever written to disk password unlocks 4 · task uses the valueios_config: lines: "enable secret {{ vault_enable }}" run ends → plaintext discarded · no_log hides it from outputdisk only ever held ciphertext leaked / attackerencrypted / trustedrun-time decryptkey insightsafe / allowed
Follow the flow: ciphertext loads from the repo (1), a password source unlocks it (2), AES-256 decrypts in memory only (3), the task uses the value and the plaintext is discarded at exit (4). Disk only ever held ciphertext.
Related: Ansible for Firewall AutomationNext: Ansible Roles & Best Practices
Quick check · Q4 of 10

An interviewer asks Arjun: "Your repo is public, vault.yml is encrypted, but you committed .vault_pass last month. How bad is it, and what's the FULL fix?"

Correct: d. Committing the password means the encryption is effectively void — anyone with the repo history can decrypt. A new commit deleting the file doesn't remove it from history. The full fix is to treat the key as leaked: rekey to a new vault password, rotate the actual secrets the vault held (device/API), and rewrite history to purge the file. Re-encrypting alone changes nothing if the key is known.

🤖 Ask the AI Tutor

Tap any question — instant, scoped to this lesson. No login, no waiting.

Pre-curated from Ansible docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.

📝 Wrap-up assessment — six more

You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.

Q5 · Remember

What is the first line of any file encrypted by Ansible Vault, and what does it tell you?

Correct: c. Every vaulted file starts with $ANSIBLE_VAULT;1.1;AES256 (or 1.2 with a vault-id label), your quick proof the content is encrypted with AES-256. It never stores the password — only ciphertext follows. The other headers are invented; head -1 on the file is the real test.
Q6 · Apply

Sneha at Infosys must run a playbook from a nightly cron job that references vaulted variables, with no human present. Which is the correct way to supply the password?

Correct: a. A cron job can't type, so --vault-password-file at a locked-down (600), gitignored file is the unattended-safe choice. --ask-vault-pass needs a human; pasting the password into the playbook or committing the file puts the key in the repo — defeating the whole point.
Q7 · Apply

Rahul has a mostly-readable vars file with exactly one secret API token inside it, and reviewers must keep diffing the file. Which command produces the right result?

Correct: b. encrypt_string encrypts only the token into an inline !vault block, leaving the rest of the YAML readable and diffable. encrypt would lock the whole (mostly non-secret) file; create makes a new all-secret file; rekey only rotates an existing vault's password and doesn't encrypt a plaintext value.
Q8 · Analyze

A team's vault.yml is encrypted and committed, but git diff on it is pure noise after every tiny change, slowing reviews. What is the root cause, and the best mitigation?

Correct: d. Because a one-character change rescrambles the whole ciphertext, diffs on a vault file are inherently noisy — that's expected, not corruption. The fix is to contain secrets in a small dedicated vault.yml (so non-secret diffs stay clean) and optionally a git diff driver that decrypts for review. The file IS encrypted; AES-256 isn't the problem; Git isn't corrupt.
Q9 · Analyze

An engineer accidentally committed .vault_pass (the password file) to a shared repo last week. The vault.yml is still AES-256 encrypted. How serious is this, and why?

Correct: b. Committing the password hands an attacker both halves: the ciphertext (vault.yml) and the key (.vault_pass), even from history. They can decrypt everything, so it's critical. AES-256 is irrelevant once the key leaks; the risk is retroactive, not future-only; and a deletion commit doesn't remove the file from history. Treat the key as compromised and rekey + rotate.
Q10 · Evaluate

Two approaches to dev/prod secrets: (A) one shared vault password for both, stored in a committed file 'so it's simple'; (B) --vault-id dev@dev.vp for dev and prod@prompt for prod, with no key committed. Which is stronger and why?

Correct: a. B is far stronger: distinct vault IDs mean a dev leak can't decrypt prod, and prod@prompt keeps the production key off every disk and out of the repo. A commits the key (private repos still get cloned, forked and breached) and shares one key across environments, so a single leak compromises everything. 'Simpler' isn't a security argument; both are NOT identical because the keys and exposure differ.
Lesson complete — saved to your profile.
Almost! You need 70% (7 of 10) — re-read the path that tripped you up and tap "Try again".

🧠 In your own words

Type one line: In one line, why does committing the vault password file to Git make AES-256 encryption of your vault.yml pointless? Then compare to the expert version.

Expert version: Because the security of Vault rests entirely on the password staying out of the repo — once the key is committed alongside the ciphertext, anyone with the repo (including its history) has both halves and can decrypt every secret, so the encryption protects nothing.

🗣 Teach a friend

Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.

📖 Glossary

Ansible Vault
Ansible's built-in feature that encrypts variables or whole files at rest with AES-256, so secrets can live in version control safely.
AES-256
The symmetric cipher Vault uses (Advanced Encryption Standard, 256-bit key). The committed file is ciphertext, not readable text.
$ANSIBLE_VAULT header
The first line of any vaulted file — e.g. $ANSIBLE_VAULT;1.1;AES256 — your quick proof the content is encrypted, not plaintext.
encrypt_string
Sub-command that encrypts a single value into an inline !vault block you paste into otherwise-readable YAML.
!vault tag
A YAML tag marking the following block as a Vault-encrypted value, letting a secret sit inline inside a plaintext file.
vault_ prefix
Naming convention (e.g. vault_enable_password) so encrypted variables are obvious at a glance; plain vars reference them.
--ask-vault-pass
Flag that prompts interactively for the vault password — good on a laptop, can't run unattended.
Vault password file
A one-line file holding the password, used with --vault-password-file. Must be chmod 600 and gitignored.
--vault-id label@source
Attach a labelled password (dev/prod) from a prompt, file or client script, so one repo can use different keys per environment.
rekey
Sub-command that rotates a vault file from the old password to a new one, re-encrypting without exposing plaintext.
no_log
Task/play keyword that hides a task's output so a decrypted secret is never printed to the console or CI logs.
community.hashi_vault
An Ansible collection whose lookup pulls secrets from HashiCorp Vault at run time — the dynamic, audited alternative to a static vault file.

📚 Sources

  1. Ansible Community Documentation — "Encrypting content with Ansible Vault" (ansible-vault create/edit/view/encrypt/decrypt/rekey; encrypt_string with --name; the !vault tag; AES-256; $ANSIBLE_VAULT;1.1;AES256 header). docs.ansible.com/projects/ansible/latest/vault_guide/vault_encrypting_content.html · docs.ansible.com/projects/ansible/latest/cli/ansible-vault.html
  2. Ansible Community Documentation — "Managing vault passwords" (--ask-vault-pass; --vault-password-file; --vault-id label@source with prompt/file/script; client scripts ending in -client; DEFAULT_VAULT_ID_MATCH). docs.ansible.com/projects/ansible/latest/vault_guide/vault_managing_passwords.html
  3. Ansible community / OneUptime guides — "Ansible Vault with Git for team collaboration" + selivan.github.io pre-commit hook (committing encrypted files is fine; diffs are useless on ciphertext; pre-commit hook checks files start with $ANSIBLE_VAULT; never commit the password; chmod 600 + .gitignore). oneuptime.com/blog/post/2026-02-21-how-to-use-ansible-vault-with-git-for-team-collaboration/view · selivan.github.io/2017/04/08/ansible-check-on-commit-vault-files-are-encrypted.html
  4. Recent item (2025–26) — CVE-2025-4656 (HashiCorp Vault rekey/recovery-key DoS, fixed in Vault 1.20.0) + community.hashi_vault collection v7.1.0 lookup for run-time secret retrieval as the dynamic alternative to a static vault file. cvedetails.com/cve/CVE-2025-4656/ · docs.ansible.com/projects/ansible/latest/collections/community/hashi_vault/hashi_vault_lookup.html
  5. Red Hat — EX294 (RHCE) exam objectives: "use Ansible Vault in playbooks to protect sensitive data" (create vault.yml, reference encrypted vars, run non-interactively with a vault password file). redhat.com/en/services/training/ex294-red-hat-certified-engineer-rhce-exam-red-hat-enterprise-linux
  6. Ansible Community Documentation — "Using encrypted variables and files" (referencing vaulted vars from plays; no_log to keep secrets out of output; whole-file vs single-variable encryption trade-offs). docs.ansible.com/projects/ansible/latest/vault_guide/vault_using_encrypted_content.html

What's next?

You've got the secrets locked down — now stop copy-pasting tasks between playbooks. Roles turn your scattered plays into clean, reusable building blocks with a standard layout (and a tidy place to keep that vault.yml).