Using Pass to Store Secrets for Ansible

Inevitably, when you use a configuration management system like Ansible, you’ll need to supply it with secrets (like passwords). It’s generally considered a bad idea to keep secrets in plain text and it’s an even worse idea to store them in a remotely hosted git repository. There are various options here, but they basically boil down to encrypting them and keeping them in the repository or storing them somewhere else entirely. If you want to keep secrets in the repository, solutions include Ansible Vault and git-crypt.

I much prefer the idea of storing them externally to the repository. I’m sure there are loads of ways to do this, but the way I like to do it with the Pass password manager.

What’s Pass?

Pass is a command line password manager. It stores the passwords in a GPG encrypted vault. Once unlocked, getting the password is as simple as running pass with the right arguments and reading the output.

Setting Up Pass

First you’ll need to install Pass. On Ubuntu and Debian, this is as simple as running sudo apt install pass. On you can install it with Homebrew by running brew install pass.

Before you can make a password vault with Pass, you’ll need a GPG key. If you don’t already have one, you can generate one with gpg --key-gen. Bear in mind that if you don’t set a passphrase, you’re not really gaining any protection.

Then you can run pass init "Michael Mulqueen", changing my name to match whatever name you’re using for your GPG key. If you’re not sure, you can run gpg -k to see what name you’ve used.

Storing Passwords in Pass

You can store passwords in pass by running pass insert servers/example.com/postgresql. You can use whatever convention you like for structuring your passwords in pass, I organise mine like this.

Passwords can be edited by running pass edit servers/example.com/postgresql

Retrieving a password is as simple as pass show servers/example.com/postgresql

Accessing Passwords from Ansible

Passwords stored in Pass can be accessed using Ansible’s pipe module.

For example like this from an inventory file:

example.com ansible_become=True ansible_become_pass="{{ lookup('pipe', 'pass show servers/example.com/ssh') }}"  ansible_user=ansible

Or like this as a task:

- postgresql_user:
    name: postgres
    password: "{{ lookup('pipe', 'pass show servers/example.com/postgresql') }}"

Pass will need to be unlocked before running the Ansible playbook because otherwise it may try (and fail) to interactively prompt for a password. This is as simple as showing one of the passwords before running Ansible - you could create a dummy password for this purpose if you like and then run pass show dummy. This shouldn’t be a problem if you’re using a GPG implementation that usually prompts with a graphical popup to ask for a passphrase (or PIN for a hardware key) - e.g. GPG Tools on macOS.