Securely cache ansible-vault and "become" password
If you need to run ansible multiple times, it’s cumbersome to enter both the become (formely known as sudo) and the ansible-vault-password with each invocation.
tl;dr use a script for vault_password_file in your ansible.cfg that executes a password-manager (e.g. pass) to echo the vault-password to stdout. Then put your become-password into ansible-vaults.
The goal
The goal is to somehow “cache” both the vault password and the become password, so you can quickly run ansible without re-entering credentials for small playbook changes.
I start with a naive approach that might be sufficient for playing around with ansible and throw-away hosts (e.g. vagrant-boxes, digitalocean-droplets) and will develop it into my current solution, so you can relate to it.
Non-interactive “become” password
You can set the variable ansible_become_pass to provide the password required for ansible to gain administrative privileges. Utilising the command-line switch –extra-vars it’s possible to replace the -K flag with a a non-interactive option:
$ ansible-playbook … -K
SUDO password: …
# becomes (no pun intended)
ansible-playbook --extra-vars="ansible_become_pass=secret"
Now the sudo password is scattered around in your shell’s history file, and is visible in the process-list ps aux
, not very good.
Non-interactive vault-password
Ansible’s vault feature already has an option to use a passwords from a file if you add the flag –vault-password-file or set the correpsonding configuration variable. Again, password on disk: very bad solution.
But: vault-password-file may also be an executable (script). Without any arguments, but we can use a simple wrapper script. Now the solution becomes very obvious: Write a wrapper script to query the password from the user and cache it for subsequent runs.
Password manager to store and provide the vault-password
We now need a tool that returns the ansible vault-password on stdin. Luckily pass provides just that feature. It’s a password-manager implemented in shell-script organising passwords in a directory-structure and encrypting passwords (put into plaintext-files) with GPG.
Looking up a password might look like: pass show ansible-vault-password
. Then GPG asks for your password, decrpyts the password-file and prints the password to stdout.
Until your GPG-Agent removes your personal private key from memory, subsequent calls to pass show
print the password without prompting the private-key password again.
(You may of course replace pass with any other password-manager, the only requirement is a cli interface.)
Wrapping this call to pass into an argument-less script (which is required to set it as vault_password_file is trivial:
#!/bin/sh
pass show ansible-vault-password
Make that script executable, and configure your ansible.cfg:
[defaults]
vault_password_file = ~/bin/ansible-vault-pass.sh
Now invocations of ansible
or ansible-playbook
“ask” the password-manager for your private key (even if you don’t have any ansible-vaults yet) which might trigger a password-prompt, but only once during the password-managers session (which is totally up to the GPG agent’s configuration if you use pass).
Putting the bits together
Depending how familiar you are with ansible you should already have an idea how to proceed.
Basically you create (or re-use) a vault where you set ansible_become_pass:
ansible-vault edit host_vars/all.yml # opens the decrypted file in your $EDITOR
# in the editor:
ansible_become_pass: s3cr3t
Now host_vars/all.yml will be read by ansible, decrypted (since ansible uses the script in vault_password_file to retrieve the vault-password) and the become-password is available. Wanna try it?
$ ansible -b -m command -a whoami my-host
The result is of course “root”.
More elaborate solution
To gain a little bit of flexibility I organized my host variables inside the host_vars directory in smaller files. Ansible allows you to use directories instead of files inside host vars:
host_vars/
├── a.examble.com.yml
├── b.examble.com
│ ├── become.yml
│ └── main.yml
All those files under b.example.com will be treated as if they were concatenated and then used like a.example.com.yml — except that ansible decrypts vault files before “concatenating”. Thus become.yml is encrypted (and only contains the variable ansible_become_pass
), and main.yml contains all non-sensitive host_vars — unencrypted and easily editable without invocation of ansible-vault edit main.yml
.