Vault: securing passwords on my laptop
August 25, 2023•894 words
Oh no, passwords!
My laptop is my main computing device and I have lots of passwords that I keep in a KeePass store. This store I access interactively through the Kee Firefox Extension which works great. However, I have a bunch of applictions on my laptop that need access to my plain text groupware password and this didn't work so great with KeePass. For a long time I kept the passwords in various configuration files including netrc. This is not very secure plus it's a pain to change my password because of many places it's stored at.
For improving this situation I probably could've used the system password store. I decided against it because I didn't want to deal with the various wallet applications that come attached to it, e.g. Kwallet. Instead, I decided to give vault a try. It's less a single user application and more an enterprise grade password store with very detailed access control mechanisms that might come in handy if I decide to store many more things in there.
In this first exploration I set up a local vault installation and integrate it with msmtp, offlineimap and vdirsyncer for a comprehensive and secure groupware and email setup.
Installation
Make sure that the vault
executable on Linux has the appropriate capabilities to use mlock
without root privileges. This command sets the capabilities:
sudo setcap cap_ipc_lock=+ep $(readlink -f $(which vault))
Set up consul (not required!!!)
First I got the impression that consul is required in order to run vault. Only later on I realized that this is not the case because vault also supports storing everything on the local filesystem which is sufficient for me. I'll leave the configuration here in case I need to set up a distributed vault server.
Consul is the storage backend for Vault and should therefore be set up first.
/home/jceb/.config/vault/consul.json
:
{
"datacenter": "jc",
"data_dir": "/home/jceb/.config/vault/consul",
"log_level": "INFO", <-- has to be adjusted later on to WARN or so
"node_name": "torch",
"server": true,
"bootstrap": true, <-- Key to have it run as the first (only) server
"bind_addr": "127.0.0.1", <-- Run it only privately on this computer
"ports": { <-- disable unused services
"dns": -1,
"https": -1
}
}
Run server with consul agent -config-file /home/jceb/.config/vault/consul.json
Set up vault
Actually, vault doesn't need consul. It can also run with a file backend.
/home/jceb/.config/vault/vault.json
:
{
"storage": {
"file": {
"path": "~/.config/vault/vault"
}
},
"listener": {
"tcp": {
"address": "127.0.0.1:8200",
"tls_disable": 1
}
}
}
Run server with vault server -log-level=info -config /home/jceb/.config/vault/vault.json
Initialize vault with
export VAULT_ADDR=http://127.0.0.1:8200
vault operator init
- Store unseal keys - you need them every time vault is restarted and also right now.
- Unseal vault via
vault operator unseal
lvault
command
For convenience reasons I created the lvault
command that will access the local vault server without having to export VAULT_ADDRESS
globally:
#!/bin/bash
VAULT_ADDR=http://127.0.0.1:8200 exec vault "$@"
Create a policy and a role
Policies are there to restrict/grant access in one place for as many concrete tokens as you like. Policies can be combined into roles that make it even easier to control access.
First, create a policy that only grants access to the password that will be store in the vault: lvault policy write password -
path "secret/password" {
capabilities = ["read"]
}
The success of the policy creation can be checked by running the following commands:
lvault policy list
lvault policy read password
Second, create a role that integrates the just created policy: lvault write auth/token/roles/simpleapp @..
{
"allowed_policies": "password",
"name": "simpleapp",
"orphan": false,
"renewable": true
}
The success of the role creation can be checked by running the following commands:
lvault list auth/token/roles
lvault read auth/token/roles/simpleapp
Production
Create an acces token for an application
Create the new token: lvault token create -role=simpleapp -display-name=msmtp
Store token (not the accessor token which is only the identifier of the token) in an encrypted file gpg -eq > ~/.msmtp.key.gpg
.
Store secret in vault
Authenticate as root and store the secert:
lvault login
lvault write secert/password @..
{
"username": "<username>",
"value": "<password>"
}
Authenticate with token and access secret
Unfortunately, the vault
command will store the current token in ~/.vault-token
which will cause different programs to compete with one another for storing their token in there.
gpg -dq ~/.msmtp.key.gpg | lvault auth -
lvault read -field=value secret/password
Solution: don't store the token instead use curl. Store the following line in file lvault-read
gpg -dq "$1" | paste ~/.local/bin/lvault-read.header - | curl -s --header @- --request GET "http://127.0.0.1:8200/v1/${2:-secret/password}" | jq -r "${3:-.data.value}"
File ~/.local/bin/lvault-read.header
contains:
X-Vault-Token:
Configure msmtp and offlineimap
msmtp
Add the following line to your account in ~/.msmtprc
:
passwordeval lvault-read ~/.msmtp.key.gpg
offlineimap
Add the following line to your repository in ~/.offlineimaprc
:
remotepasseval = lvault_read()
And this line to the general section:
pythonfile = ~/.offlineimap/remotepasseval.py
And store these contents in ~/.offlineimap/remotepasseval.py
:
from subprocess import check_output
from os.path import expanduser
def lvault_read():
res = check_output(['lvault-read', expanduser('~/.offlineimap.key.gpg')])
return res.decode().strip()
vdirsyncer
Add the following lines to the storage configuration in ~/.config/vdirsyncer/config
:
username.fetch = ["command", "lvault-read", "~/.config/vdirsyncer/vdirsyncer.key.gpg", "", ".data.email"]
password.fetch = ["command", "lvault-read", "~/.config/vdirsyncer/vdirsyncer.key.gpg"]