Using pass to share secrets in a team

Why you might want to use a "CLI-only" password manager, and how to do it.

4 min read


By Jonas Bakken


December 10, 2021

pass, or "the standard unix password manager" as it is described on its website, is an open source, simple, easy to use password manager written in bash that uses GPG and git under the hood. Here we'll take a look at how pass can be used to share secrets within a team, and why it may be preferable to other options.

gpg + git + bash = pass

First I'd like to show the basics of how pass works. This assumes that you have a GPG key, and have pass installed. pass uses a "password store", just a directory (usually ~/.password-store), to store your passwords. To get started you must first initialize your password store:

$ pass init your-gpg-key
Password store initialized for your-gpg-key
$ pass git init
Add current contents of password store.
Configure git repository for gpg file diff.
$ pass git remote add origin your-git-remote:repo

From here we can generate a password and push it (it will automatically be committed when created):

$ pass generate foo
The generated password for foo is:
$ pass git push

Then we can print the password, or copy it to the clipboard:

$ pass foo
$ pass -c foo
Copied foo to clipboard. Will clear in 45 seconds.

And finally list the existing passwords and created files:

$ pass
Password Store
└── foo
$ pass git ls-files

Here we see that pass has created two files:

  • .gpg-id - A file that tells pass which gpg keys should be used to encrypt the passwords in this directory (and child directories)
  • foo.gpg - The password we just generated, as a gpg encrypted file, encrypted with the gpg keys found in .gpg-id

And that's it! These are just normal files in a local directory, and if we wanted to we could just decrypt our password with gpg --decrypt ~/.password-store/foo.gpg. pass has more features, but these are the basics you'll need to start using it.

Why use pass?

pass is in my opinion a great option for managing passwords and secrets, because it is so simple and transparent. While pass provides a clean command line interface for users to interact with, the fact that the password store itself is a git repo containing GPG encrypted files makes it easy to be used on machines without the pass program itself installed, such as a CI/CD system, using just normal tools most machines already have. Using git under the hood also comes with the benefits developers are already used to, such as version control, branching, and the ability to make pull requests to add changes in a structured way. To add a new team member, they can create a pull request adding their GPG key id to the .gpg-id files where they want access and adding a link to their public key, then anyone who already have access can add them by checking out their branch, importing their GPG key, and pushing a commit after running xargs pass init < .gpg-id.

The fact that pass is so simple and "CLI-first" makes it flexible and easily integratable into other applications, which is a major reason why why I find it so useful for managing secrets in general, not just passwords. These properties are useful for avoiding putting *.env files with decrypted secrets all over your computer, or accidentally leaving some sensitive data in your bash history.

To demonstrate this, I would like to share two use cases where I "daily drive" pass in combination with other tools.

Example 1: Terraform variables

Terraform will often need sensitive input variables, for example API-keys. As the Terraform documentation describes, one way to provide these sensitive variables is through environment variables prefixed with TF_VAR_. To avoid leaving keys in our bash history or in local files which might be accidentally added in git, we can put our environment variables in pass like this:

$ pass insert -m env/terraform-test
Enter contents of env/terraform-test and press Ctrl+D when finished:

export TF_VAR_cloudflare_api_key=0123456789

Then before running any Terraform command, we can simply run eval "$(pass env/terraform-test)" to load our variables into the environment.

Example 2: Ansible vault password

Ansible has a concept called Ansible Vault which is a way of keeping sensitive data used in your ansible scripts encrypted, and decrypting them only when running your scripts. Ansible supports fetching the encryption passwords through custom scripts called clients, and here we'll look at how easy it is to set up an Ansible Vault that fetches the encryption password directly from pass. First we need to create a simple "client":

$ cat << EOF > pass-client.sh
set -eo pipefail
echo -n "\$(pass show ansible-vault/\$2 | head -n 1)"
$ chmod +x pass-client.sh

We can then generate our encryption password and create our vault file:

$ pass generate ansible-vault/your-vault-id
$ ansible-vault create --vault-id your-vault-id@pass-client.sh your-vault-file.yml

We can then automatically fetch the password from pass by using the --vault-id your-vault-id@pass-client.sh argument when using for example ansible-playbook or ansible-vault commands in the future.
To get less verbose commands, you can add vault_identity_list = your-vault-id@pass-client.sh to your ansible.cfg file to avoid having to add the argument each time.