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.
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: w6#SYO46w_\^WfBvjX4!n#~$g $ pass git push
Then we can print the password, or copy it to the clipboard:
$ pass foo w6#SYO46w_\^WfBvjX4!n#~$g $ 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 .gpg-id foo.gpg
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
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.
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.
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 ^D
Then before running any Terraform command, we can simply run
eval "$(pass env/terraform-test)" to load our variables into the environment.
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 #!/bin/bash set -eo pipefail echo -n "\$(pass show ansible-vault/\$2 | head -n 1)" EOF $ 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 email@example.com your-vault-file.yml
We can then automatically fetch the password from pass by using the
--vault-id firstname.lastname@example.org argument when using for example
ansible-vault commands in the future.
To get less verbose commands, you can add
vault_identity_list = email@example.com to your
ansible.cfg file to avoid having to add the argument each time.