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.
Tony Finch – blog