Krikri::Vault
Vault - Ansible Vault (AES256) encrypt/decrypt.
File format (verified against real ansible-vault, not assumed from
memory):
$ANSIBLE_VAULT;1.1;AES256
<hex, wrapped at 80 columns>
Where the wrapped hex, once unwrapped and hex-decoded, is itself ASCII text: "<hex(salt)>\n<hex(hmac)>\n<hex(ciphertext)>". That's a deliberate double hex-encoding, not a bug - Ansible's own format does this. salt is 32 random bytes; a single PBKDF2-HMAC-SHA256 call over (password, salt, 10_000 iterations, 80-byte output) is split into a 32-byte AES key, a 32-byte HMAC key, and a 16-byte CTR IV; the plaintext is PKCS7-padded to a 16-byte boundary before AES-256-CTR encryption; the HMAC is computed over the ciphertext (encrypt-then-MAC) using the derived HMAC key.
Constants
Class methods
Every password worth trying for content, best candidate first.
True if content looks like an Ansible Vault-armored file/string
(starts with the "$ANSIBLE_VAULT;" header), independent of whether
its cipher/version are ones we actually support.
Decrypts content if it's vault-encrypted (using the configured
password), otherwise returns it unchanged. This is what every
file-loading call site should call instead of using the raw file
content directly.
Like maybe_decrypt, but for a parsed variable value rather than a raw
file - covers Ansible's other vault use case: a single value inline in
an otherwise-plaintext vars file, tagged !vault (what ansible-vault encrypt_string produces, e.g. db_password: !vault |). Crystal's
YAML parser drops unknown tags and hands back the tagged scalar as a
plain string, so an inline-vault value already looks exactly like a
vault-encrypted file's content by the time it reaches here - the same
header check and decrypt() do the job. Recurses into arrays/hashes so
a vault-encrypted value nested inside a list or mapping var is also
caught.
Converts a YAML::Any value to JSON::Any, recursively stringifying
every hash key at every nesting level - unlike the JSON.parse( value.to_json) round-trip used in several places in this codebase,
which crashes ("Can't convert Bool to a JSON object key") on a real,
supported Ansible/Jinja2 idiom: a dict keyed by a bare YAML boolean
(true:/false:/yes:/no:), which Python's own YAML loader
happily parses as a bool-keyed dict and Jinja2 happily indexes with
dict[some_bool_expression]. JSON has no non-string-key concept at
all, so krikri-playbook's own JSON::Any-based variable
representation stringifies the key ("true"/"false") instead -
meaning a role that then indexes such a dict with a boolean
expression needs that same "true"/"false" stringification applied
at lookup time too (see VariableLookup's own dotted/bracket-index
handling). Found benchmarking robertdebock.tailscale's own
tailscale_sysctl_file/tailscale_command vars, both keyed by a
bare boolean - the crash happened at PARSE time, before any task
ran, taking down the entire playbook.