ansible/ansible
Vault encryption
Ansible Vault encrypts variables, files, or arbitrary strings with AES-256-CTR (or older 1.1-format with the same algorithm). Encrypted material can sit in a Git repository safely; the controller decrypts at parse time when it has the right secret. Implementation is at lib/ansible/parsing/vault/__init__.py (1,525 lines).
Header format
A vault-encrypted file (or inline string) begins with a header line:
$ANSIBLE_VAULT;1.2;AES256;<vault_id_label>| Component | Meaning |
|---|---|
$ANSIBLE_VAULT |
Magic prefix; identifies the file as vault-encrypted |
1.2 |
Format version. 1.0 (no vault id), 1.1 (no label), 1.2 (current — adds optional label) |
AES256 |
Cipher. The only currently-implemented cipher |
<vault_id_label> |
Optional label that lets multiple vaults coexist in one playbook |
After the header, a hex-encoded payload contains: salt, HMAC, ciphertext.
Files and classes
| File | Role |
|---|---|
lib/ansible/parsing/vault/__init__.py |
Everything: VaultLib, VaultEditor, VaultSecret, PromptVaultSecret, file-format helpers |
lib/ansible/cli/vault.py |
The ansible-vault CLI |
lib/ansible/parsing/dataloader.py |
Calls the vault on every file load |
lib/ansible/_internal/_datatag/_tags.py:VaultedValue |
The tag attached to decrypted-but-still-secret values |
lib/ansible/parsing/yaml/loader.py |
YAML loader that handles inline !vault tags |
Encryption flow
graph TD
PLAINTEXT[Plaintext] --> SALT[Generate 32-byte salt]
PASSWORD[Vault password] --> KDF[PBKDF2-HMAC-SHA256<br/>10000 rounds]
SALT --> KDF
KDF --> KEYS[Derive: AES key, HMAC key, IV]
PLAINTEXT --> PAD[PKCS7 pad]
PAD --> AES[AES-256-CTR encrypt]
KEYS --> AES
AES --> CIPHERTEXT[Ciphertext]
CIPHERTEXT --> HMAC[Compute HMAC-SHA256]
KEYS --> HMAC
HMAC --> ENVELOPE[Envelope: salt || HMAC || ciphertext]
ENVELOPE --> HEX[Hex-encode]
HEX --> HEADER[Prepend $ANSIBLE_VAULT;1.2;AES256;label]
HEADER --> OUT[Encrypted output]The KDF is PBKDF2 with HMAC-SHA256, 10,000 rounds. That's modest by modern standards but is the documented format; changing it requires a new format version. Decryption verifies the HMAC before attempting AES decryption (so wrong-password attempts fail at HMAC verification, not on garbled output).
Vault ids
Format 1.2 introduced labeled vault ids so playbooks can mix multiple secret sources:
ansible-playbook --vault-id dev@~/.vault-pass-dev --vault-id prod@prompt site.ymlThis sets up two secrets — dev (read from a file) and prod (prompted at runtime). When decrypting, the loader matches on the label embedded in each vault header. If a header's label is empty (1.1 format) or doesn't match, VaultLib tries every loaded secret in order.
The @source part is parsed by lib/ansible/parsing/vault/__init__.py:get_file_vault_secret, which dispatches:
@<path>— read the password from the file. If executable, run it as a script and read stdout.@prompt— prompt the user interactively (PromptVaultSecret).@<custom>— anything else is passed tolookup_vault_secrets()for plugin-style secret backends.
Where decryption happens
DataLoader (lib/ansible/parsing/dataloader.py) is the central read-from-disk surface. Every .load_from_file() runs through DataLoader._is_vaulted() and decrypts before parsing YAML. The flow is:
- Read raw bytes.
- If the first line is
$ANSIBLE_VAULT;..., hand toVaultLib.decrypt(). - Match the vault id label against loaded secrets; iterate until one decrypts successfully or all fail.
- Return the plaintext to the YAML loader.
For inline !vault scalars in a YAML file, the YAML loader decodes them on read and tags the result as a VaultedValue so the rest of the pipeline knows it came from an encrypted source.
api_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;prod
31613966...
...VaultedValue tag
After decryption, the value carries the VaultedValue datatag from lib/ansible/_internal/_datatag/_tags.py. This tag flows through the variable manager and the templating engine. Implementations can choose to handle vaulted values specially:
- The
no_log=Trueparameter system uses it to scrub secrets from displayed task results. - The
display.vvvpaths skip vaulted values when emitting verbose output. - Encoding to JSON for an AnsiBallZ archive marks the value as still-secret so the remote side can avoid logging it.
VaultEditor
VaultEditor is the high-level, file-oriented surface used by the ansible-vault CLI:
create()— encrypt a new file in$EDITOR.edit()— decrypt to a temporary file, edit, re-encrypt.view()— decrypt to stdout.encrypt(),decrypt()— file-level transforms.rekey()— change the password of an already-encrypted file.
encrypt_string (the inline-scalar form invoked by ansible-vault encrypt_string) wraps the result in YAML's tagged-scalar format so it can be pasted directly into a vars file.
VaultLib (the workhorse)
VaultLib is what DataLoader uses internally:
is_encrypted(data)— bytes/text predicate.is_encrypted_file(path)— same, but for a path.encrypt(plaintext, secret, vault_id=None)— encrypt with a specific secret.decrypt(ciphertext, filename=None)— decrypt; tries each loaded secret.decrypt_and_get_vault_id()— same asdecrypt, but returns which secret matched.
The cryptography package is the underlying crypto provider (from cryptography.hazmat.primitives.ciphers import Cipher). There's no PyCrypto fallback in the modern code.
Vault secret resolution
Secrets are loaded into a VaultSecretsContext ordered from most-specific to least-specific:
- CLI:
--vault-id,--vault-password-file,--ask-vault-pass. ansible.cfg:DEFAULT_VAULT_ID_MATCH,DEFAULT_VAULT_PASSWORD_FILE.- Environment:
ANSIBLE_VAULT_PASSWORD_FILE,ANSIBLE_VAULT_IDENTITY_LIST.
Match order during decryption is configurable: with DEFAULT_VAULT_ID_MATCH=true, only the secret whose label matches gets tried; with the default false, all loaded secrets are tried in order.
Integration points
- Imported by:
lib/ansible/parsing/dataloader.py(every YAML load),lib/ansible/cli/__init__.py(CLI startup),lib/ansible/cli/vault.py(the dedicated CLI). - Imports:
cryptography(crypto),lib/ansible/_internal/_templating/_jinja_common.py(for value handling),lib/ansible/_internal/_datatag/_tags.py(VaultedValue).
Entry points for modification
- Adding a new format version — extend the parser/writer in
parsing/vault/__init__.py. UpdateVAULT_VERSION_MIN/VAULT_VERSION_MAXconstants. Sanity should be updated to test round-trips. - A new secret source backend — add to the
get_file_vault_secretdispatch, or write a newVaultSecretsubclass. - Different cipher — would require a new
cipher_name(currently onlyAES256) and a newCipher*class. - Debugging a "decryption failed" error —
--vault-id label@prompt -vvvreveals which labels were tried.
Cross-links
- Apps → ansible-vault — the CLI surface.
- Templating — how
VaultedValueflows through templating. - Reference → Configuration —
DEFAULT_VAULT_ID_MATCHand friends.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.