parent
e163aab53c
commit
af79a00721
2 changed files with 304 additions and 1 deletions
|
@ -0,0 +1,303 @@
|
|||
+++
|
||||
title = "Workflows for Unix Password Store with Emacs and Shell"
|
||||
date = 2024-04-13
|
||||
[taxonomies]
|
||||
tags = ["programming"]
|
||||
categories = ["lisp" "shell"]
|
||||
+++
|
||||
|
||||
# Table of Contents
|
||||
|
||||
1. [Managing Secrets using Pass](#orgeea54c9)
|
||||
2. [Installation](#org44c3160)
|
||||
1. [GUIX](#org7920015)
|
||||
2. [Debian, Ubuntu et al](#orgbee77ce)
|
||||
3. [Emacs Integration](#orgad61fe8)
|
||||
1. [Enable the Unix Password Store.](#orgf739a63)
|
||||
2. [Helper Function](#org1c798ff)
|
||||
3. [Using in Emacs Configuration](#org25108ee)
|
||||
4. [Shell integration](#org849f72f)
|
||||
1. [DirEnv integration](#org299dd7c)
|
||||
5. [Tips](#org158dbf2)
|
||||
1. [Entering passwords on the terminal](#org00fffd0)
|
||||
2. [Getting fields from multi field secrets](#org6ab1e11)
|
||||
|
||||
|
||||
|
||||
<a id="orgeea54c9"></a>
|
||||
|
||||
# Managing Secrets using Pass
|
||||
|
||||
On UNIX there is a well known tool to manage secrets called
|
||||
**password-store** or **pass** for short.
|
||||
|
||||
It is a very minimal tool, more to facilitate workflows that to do
|
||||
real work, very much in the UNIX philosophy. It stores all secrets in
|
||||
plain files in a folder structure. It does not care about what is in
|
||||
the files and encrypts them using a GPG public key so only the owner
|
||||
of the private key can decrypt them. It does offer special access to
|
||||
the first line so a password can be quickly fetched and copied to the
|
||||
clipboard or stdout or wherever some **pass** aware integration needs it.
|
||||
|
||||
Certain folders can be configured to use a different public keys to
|
||||
allow pragmatic secret delegation to different systems without
|
||||
exposing all secrets.
|
||||
|
||||
It leverages the **gpg** infrastructure for key management, distribution,
|
||||
unlocking with **pinentry**, caching with **gpg-agent**.
|
||||
|
||||
|
||||
<a id="org44c3160"></a>
|
||||
|
||||
# Installation
|
||||
|
||||
|
||||
<a id="org7920015"></a>
|
||||
|
||||
## GUIX
|
||||
|
||||
Add **password-store**, **gpg** and **pinentry** to your package list.
|
||||
|
||||
The **pinentry** program provides a client to securely unlock your keys in
|
||||
a GUI and terminal environment. There are other options to make it
|
||||
better fit your environment, but this is fine for me.
|
||||
|
||||
|
||||
<a id="orgbee77ce"></a>
|
||||
|
||||
## Debian, Ubuntu et al
|
||||
|
||||
Add **pass**, **gpg**, **gpg-agent** and **pinentry-gnome** of **pinentry-qt** using
|
||||
|
||||
$ apt-get install pass gpg gpg-agent pinentry-qt
|
||||
|
||||
Both the gnome and qt versions at least fall back gracefully if no
|
||||
graphical environment are available.
|
||||
|
||||
The current selected pinentry program is provided as
|
||||
**/usr/bin/pinentry** and this link is managed by the usual alternatives
|
||||
machinery in debian based distros.
|
||||
|
||||
|
||||
<a id="orgad61fe8"></a>
|
||||
|
||||
# Emacs Integration
|
||||
|
||||
Emacs **auth-source** infrastructure supports **pass** out of the box.
|
||||
|
||||
|
||||
<a id="orgf739a63"></a>
|
||||
|
||||
## Enable the Unix Password Store.
|
||||
|
||||
We have to make sure the password-store is added to the
|
||||
auth-sources. There is a handy function for that:
|
||||
|
||||
;; enable unix password-store
|
||||
(auth-source-pass-enable)
|
||||
|
||||
We add that somewhere in the init.el before any secrets are needed.
|
||||
|
||||
|
||||
<a id="org1c798ff"></a>
|
||||
|
||||
## Helper Function
|
||||
|
||||
Auth-sources is a flexible system and works like a database, i.e. you
|
||||
can query, browse through results and have multiple fields per secret.
|
||||
|
||||
Example:
|
||||
|
||||
(defun snam-password (host user)
|
||||
"Get password from the unix password store.
|
||||
|
||||
Searches the password file for a secret in the folder corresponding to
|
||||
the HOST name given, which is the folder with the '/' replaced by a '.'.
|
||||
The filename in the folder corresponds to the USER argument with a
|
||||
'.pgp' extension."
|
||||
(auth-info-password
|
||||
(car (auth-source-search
|
||||
:max 1
|
||||
:host host
|
||||
:user user))))
|
||||
|
||||
\`auth-source-search\` returns a list of results, so we have to get the
|
||||
first entry with \`car\`. The secret is lightly obfuscated, hence the
|
||||
need to decode it with the \`auth-info-password\` function.
|
||||
|
||||
Luckily there is a function [auth-source-pass-get](help:auth-source-pass-get) to get a password
|
||||
from the password store which also follows the recommended conventions
|
||||
for multiple fields in a pass file.
|
||||
|
||||
(auth-source-pass-get 'secret "snamellit/znc")
|
||||
|
||||
The pseudo key \`'secret\` returns the first line of the password store
|
||||
entry which contains the password, per **pass** conventions.
|
||||
|
||||
|
||||
<a id="org25108ee"></a>
|
||||
|
||||
## Using in Emacs Configuration
|
||||
|
||||
For single secrets, like connecting to my **znc** IRC bouncer:
|
||||
|
||||
(erc-tls :id 'znc :server "********"
|
||||
:port "****" :user "xyz" :nick
|
||||
"foobar" :password (auth-source-pass-get 'secret "foobar/znc")))
|
||||
|
||||
For multifield secrets, like for google authentication, we can
|
||||
leverage the multiple fields in the password store. Here is my
|
||||
**org-gcal** configuration:
|
||||
|
||||
(setq org-gcal-client-id (auth-source-pass-get 'secret "snamellit/org-gcal-client")
|
||||
org-gcal-client-secret (auth-source-pass-get "id" "snamellit/org-gcal-client")
|
||||
org-gcal-fetch-file-alist '(("xyz@foobar.com" . "~/org/schedule.org")))
|
||||
|
||||
|
||||
<a id="org849f72f"></a>
|
||||
|
||||
# Shell integration
|
||||
|
||||
|
||||
<a id="org299dd7c"></a>
|
||||
|
||||
## DirEnv integration
|
||||
|
||||
When developping 12-factor or similar inspired apps, the configuration
|
||||
is passed using environment variables. Using **.envrc** files with
|
||||
**direnv** integration in the shell is a very smooth way to work in
|
||||
multiple projects.
|
||||
|
||||
It is of course less than ideal to have the secrets exposed in the
|
||||
**.envrc** files in your project tree even if it is in the **.gitignore**
|
||||
file, although that is infinitely better than having secrets end up in
|
||||
the git repository.
|
||||
|
||||
Secrets in the **.envrc** files can be easily moved to the password store
|
||||
by entering the folder. The following snippet prints the current value
|
||||
to the screen and then inserts it in the password store, and verifies
|
||||
it actually is entered correctly.
|
||||
|
||||
$ echo $FOO_BAR_PASSWORD
|
||||
$ echo $FOO_BAR_PASSWORD | pass add -e foo/bar
|
||||
$ pass foo/bar
|
||||
|
||||
and then editing the **.envrc** file from
|
||||
|
||||
...
|
||||
FOO_BAR_PASSWORD=<some secret>
|
||||
...
|
||||
|
||||
to
|
||||
|
||||
...
|
||||
FOO_BAR_PASSWORD=$(pass foo/bar)
|
||||
...
|
||||
|
||||
After modification you'll be asked to allow to read the new **.envrc**
|
||||
file content and you can check if it still works by comparing the
|
||||
password with the one printed previously.
|
||||
|
||||
Printing the passwords allows to fix any typos. If this are the only
|
||||
copies you have of them you might rug-pull yourself. Ideally it should
|
||||
be possible to quickly recreate secrets if you lose any, but reality
|
||||
is often far from ideal. Echoing all these passwords is also not
|
||||
ideal, but preferable over keeping a little black book of secrets. If
|
||||
these passwords are coming from another password manager it is not
|
||||
needed of course.
|
||||
|
||||
Do not forget to clear your terminal scroll back history with
|
||||
|
||||
$ clear
|
||||
|
||||
To help migration of **.envrc** files I created a bash script I stored in
|
||||
**~/.local/bin/envrc-to-pass**:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <variable_name> <namespace>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VAR_NAME=$1
|
||||
SLUG=$(echo $VAR_NAME | sed 's/_/-/g' | tr '[:upper:]' '[:lower:]')
|
||||
NAMESPACE=$2
|
||||
|
||||
|
||||
echo "Moving variable $VAR_NAME to $NAMESPACE/$SLUG in password store"
|
||||
|
||||
SECRET=$(grep "$VAR_NAME=" .envrc | cut -d'=' -f2)
|
||||
if (echo $SECRET | grep '^\$(pass.*)'); then
|
||||
echo "secret already migrated"
|
||||
else
|
||||
echo $SECRET | pass insert -e $NAMESPACE/$SLUG
|
||||
fi
|
||||
|
||||
sed -i "s#$VAR_NAME=.*#$VAR_NAME=\\\$(pass $NAMESPACE\/$SLUG)#" .envrc
|
||||
|
||||
This makes short work of migrating projects to use the password-store.
|
||||
|
||||
|
||||
<a id="org158dbf2"></a>
|
||||
|
||||
# Tips
|
||||
|
||||
|
||||
<a id="org00fffd0"></a>
|
||||
|
||||
## Entering passwords on the terminal
|
||||
|
||||
Usually invoking \`pass add foobar/baz\` will ask to enter the password
|
||||
and confirm it in the shell.
|
||||
|
||||
However when piping a secret into the password-store
|
||||
with
|
||||
|
||||
echo FOO_BAR_PASSWORD | pass add foo/bar
|
||||
|
||||
will silently fail although the man pages tell that \`pass add\` will
|
||||
read from **stdin**. It is not clear IMO that you have to specify the **-e**
|
||||
flag like :
|
||||
|
||||
echo FOO_BAR_PASSWORD | pass -e add foo/bar
|
||||
|
||||
to suppress the confirmation and make it work as expected.
|
||||
|
||||
|
||||
<a id="org6ab1e11"></a>
|
||||
|
||||
## Getting fields from multi field secrets
|
||||
|
||||
Often secrets come in multiple parts which are nice to be stored in a
|
||||
single entry in order not to complicate the tree. The **pass**
|
||||
documentation suggests:
|
||||
|
||||
<password or main secret>
|
||||
field1: <some data>
|
||||
field2: <more data>
|
||||
|
||||
the main secret is just the first line, and can have the same
|
||||
structure as the other lines, if that makes more sense.
|
||||
|
||||
e.g. for a google integration:
|
||||
|
||||
service-account: 1234567-abcdefghijklm@developer.gserviceadmin.com(some-project)
|
||||
email: xyz@foobar.com
|
||||
private-key: ...
|
||||
|
||||
In this case it is useful to <span class="underline">document</span> which kind of secret it is as it
|
||||
could also be an api-key, or a refresh-token, or whatever part of the
|
||||
authentication menagerie that Google offers.
|
||||
|
||||
To get these fields individually I use:
|
||||
|
||||
GOOGLE_EMAIL=$(pass foo/bar | awk '/^email:/ {print $2})
|
||||
|
||||
in emacs this syntax is supported directly and the same info can be
|
||||
fetched with
|
||||
|
||||
(let
|
||||
((google-email (auth-source-pass-get "email" "foo/bar")))
|
||||
... )
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit d987049cd2660ab019ebaaf108aa69fff7fb847d
|
||||
Subproject commit 1a510f1be436d04c36a3ff0596b3162673ff1298
|
Loading…
Reference in a new issue