Ansible is the configuration
management tool we use at work. It has built-in support for encrypted
secrets, called
ansible-vault
,
so you can safely store secrets in version control.
I thought I should review the ansible-vault
code.
Summary
It's a bit shoddy but probably OK, provided you have a really strong vault password.
HAZMAT
The code starts off with a bad sign:
from cryptography.hazmat.primitives.hashes import SHA256 as c_SHA256
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
I like the way the Python cryptography library calls this stuff HAZMAT but I don't like the fact that Ansible is getting its hands dirty with HAZMAT. It's likely to lead to embarrassing cockups, and in fact Ansible had an embarrassing cockup - there are two vault ciphers, "AES" (the cockup, now disabled except that for compatibility you can still decrypt) and "AES256" (fixed replacement).
As a consequence of basing ansible-vault
on relatively low-level
primitives, it has its own Python implementations of constant-time
comparison and PKCS#7 padding. Ugh.
Good
b_salt = os.urandom(32)
Poor
b_derivedkey = PBKDF2(b_password, b_salt,
dkLen=(2 * keylength) + ivlength,
count=10000, prf=pbkdf2_prf)
PBKDF2 HMAC SHA256 takes about 24ms for 10k iterations on my machine, which is not bad but also not great - e.g. 1Password uses 100k iterations of the same algorithm, and gpg tunes its non-PBKDF2 password hash to take (by default) at least 100ms.
The deeper problem here is that Ansible has hard-coded the PBKDF2 iteration count, so it can't be changed without breaking compatibility. In gpg an encrypted blob includes the variable iteration count as a parameter.
Ugly
b_vaulttext = b'\n'.join([hexlify(b_salt),
to_bytes(hmac.hexdigest()),
hexlify(b_ciphertext)])
b_vaulttext = hexlify(b_vaulttext)
The ASCII-armoring of the ciphertext is as dumb as a brick, with hex-encoding inside hex-encoding.
File handling
I also (more briefly) looked through ansible-vault
's higher-level
code for managing vault files.
It is based on handing decrypted YAML files to $EDITOR
, so it's a
bit awkward if you don't want to wrap secrets in YAML or if you don't
want to manipulate them in your editor.
It uses
mkstemp()
,
so the decrypted file can be placed on a ram disk, though you might
have to set TMPDIR
to make sure.
It
shred
(1)s
the file after finishing with it.