Secrets Storage With Vault On Your Home Server
You may be looking for a self-hosted solution for storing passwords and other secrets that you may not feel comfortable having in the cloud. Vault by HashiCorp is an enterprise secrets storage solution with both web based and command line access. It's open source and you can run it for free at home.
I do not recommend exposing this to the clearnet. My Vault server requires I hop onto my home network's VPN. I don't doubt Vault's security, but leaving it out in the open is opening you up to the same worries that come with using a commercial password manager or your browser's built in sync functionality. The goal here is to stash all this away.
For this guide, I'm using Debian, but I'll try to keep this as distro agnostic as possible.
Download and Install
Let's start by creating a system user account for Vault.
# useradd -r vault
Go to https://www.vaultproject.io/downloads and find the link to your architecture, then download and extract the vault binary to /usr/local/bin. Example as of the time of writing:
# wget https://releases.hashicorp.com/vault/1.6.3/vault_1.6.3_linux_amd64.zip
# unzip vault_1.6.3_linux_amd64.zip
# mv vault /usr/local/bin
# mkdir /etc/vault
# chown vault:vault /etc/vault
Configure a Storage Backend
There are many options for this. Consult the documentation and follow the instructions for your preferred method. Chances are if you're running a homeserver, you already have Postgres, MySQL or MariaDB running for other apps. We can keep life simple and just use that. For my example, I'm using Postgres on Debian.
# apt install postgresql-all
# su -c psql postgres
In the Postgres shell, we'll create a user, password, and database for Vault.
postgres=# CREATE DATABASE vault;
CREATE DATABASE
postgres=# CREATE ROLE vault WITH LOGIN PASSWORD '3Mq685AS2Zjzt2zZ2hTQ0zI4ZjmHoJoP';
CREATE ROLE
postgres=# GRANT ALL ON DATABASE vault TO vault;
GRANT
postgres=# EXIT
Now login as the vault user.
# psql -U vault -d vault -h localhost
Password for user vault:
psql (13.2 (Debian 13.2-1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
vault=>
This SQL code will create the necessary schema. Note that this is copied verbatim from HashiCorp's documentation.
CREATE TABLE vault_kv_store (
parent_path TEXT COLLATE "C" NOT NULL,
path TEXT COLLATE "C",
key TEXT COLLATE "C",
value BYTEA,
CONSTRAINT pkey PRIMARY KEY (path, key)
);
CREATE INDEX parent_path_idx ON vault_kv_store (parent_path);
Configure the Service
Okay, now we want to create a config file for Vault.
# touch /etc/vault/config.hcl
# chown vault:vault /etc/vault/config.hcl
# chmod 600 /etc/vault/config.hcl
Edit and add the following.
storage "postgresql" {
connection_url = "postgres://vault:3Mq685AS2Zjzt2zZ2hTQ0zI4ZjmHoJoP@localhost:5432/vault?sslmode=disable"
}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = 1
}
api_addr = "http://127.0.0.1:8200"
ui = true
disable_mlock = true
A few things to note here, the listener
address being set to local means we'll need a reverse proxy to this to be able to access it from the outside. Setting ui
to true means we'll have a web portal we can access it from. We'll be limiting access to that later. disable_mlock
is required in order to run Vault as a non-root user. If you have unencrypted swap space, this is insecure and you'll need to run Vault as root.
Create the necessary systemd config in /etc/systemd/system/vault.service
[Unit]
Description=Vault
After=network.target postgresql.service
Requires=network.target
[Service]
User=vault
Group=vault
ExecStart=/usr/local/bin/vault server -config=/etc/vault/config.hcl
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=vault
Create directory /var/log/vault
and set Vault as the owner. Create /etc/rsyslog.d/vault.conf
and add the following:
if $programname == 'vault' then /var/log/vault/vault.log
& stop
And finally, create /etc/logrotate.d/vault.conf
to complete our service setup.
/var/log/vault/vault.log {
rotate 4
weekly
missingok
}
Now get everything started.
# systemctl restart rsyslog
# systemctl start vault
Initial Setup
Now we need to initialize Vault. You can do the following as an unprivileged user, but first let's unset the history file so sensitive data isn't stored there.
blake@vault-test:~$ unset HISTFILE
blake@vault-test:~$ export VAULT_ADDR="http://127.0.0.1:8200"
blake@vault-test:~$ vault operator init
Unseal Key 1: HemGy27C/An1PD/Rvmh+Scwv/h4rofatbBbEece/RKBG
Unseal Key 2: 0lZeThunWO1C/7hjrztIP6ayLfnLzeANZvgtRwM/u4Gs
Unseal Key 3: RqL+cLBcyLrBp2uC0+rOMb7umFEE4baHmta6VjdBVAIF
Unseal Key 4: tsadSAQzM7Lz6Ru5o7SyXeFwr1OblNca0G4NB5a1PqjT
Unseal Key 5: Ynss3Uocie6RJnVSkuIN+mB1FOzx0VOEKC45jhMUWoKB
Initial Root Token: s.aIajFemqnJ3f06vzGIjMi75P
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated master key. Without at least 3 key to
reconstruct the master key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
Your paranoia level will determine where you store your keys. You could keep them on separate encrypted devices if you wish. Also, be careful with the root token as it never expires.
Alright! Now to unseal, run vault operator unseal [key]
with 3 different keys, one at a time. You can now login with the initial root token.
blake@vault-test:~$ vault login s.aIajFemqnJ3f06vzGIjMi75P
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token s.aIajFemqnJ3f06vzGIjMi75P
token_accessor oz6lMguYyxuBmfdcDFOxEo3n
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
So we're going to want to create a store for our passwords and then create a limited user with access to it.
blake@vault-test:~$ vault secrets enable -path=passwords kv-v2
Success! Enabled the kv-v2 secrets engine at: passwords/
To create a policy with access to this resource, create and edit a file called password-policy.hcl
path "passwords/*"
{
capabilities = ["create", "read", "update", "delete", "list"]
}
Now let's create a user with access to this keystore.
blake@vault-test:~$ vault policy write password-policy password-policy.hcl
Success! Uploaded policy: password-policy
blake@vault-test:~$ vault auth enable userpass
blake@vault-test:~$ vault write auth/userpass/users/blake password=ThisIsATestPassword policies=password-policy
Success! Data written to: auth/userpass/users/blake
Managing Secrets with the CLI
Let's login as our new user and make sure we're able to store a secret.
blake@vault-test:~$ vault login -method=userpass username=blake
Password (will be hidden):
Success! You are now authenticated.
blake@vault-test:~$ vault kv put passwords/yahoo login="[email protected]" password="somepassword"
Key Value
--- -----
created_time 2021-02-28T13:55:32.900321369Z
deletion_time n/a
destroyed false
version 1
blake@vault-test:~$ vault kv get passwords/yahoo
====== Metadata ======
Key Value
--- -----
created_time 2021-02-28T13:55:32.900321369Z
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
login [email protected]
password somepassword
Using put
again will overwrite all key entries. Use patch
to add a value without deleting.
blake@vault-test:~$ vault kv patch passwords/yahoo "mother's maiden name"=99l4Jhp7
Key Value
--- -----
created_time 2021-02-28T15:08:20.132487093Z
deletion_time n/a
destroyed false
version 2
blake@vault-test:~$ vault kv get passwords/yahoo
====== Metadata ======
Key Value
--- -----
created_time 2021-02-28T15:08:20.132487093Z
deletion_time n/a
destroyed false
version 2
============ Data ============
Key Value
--- -----
login [email protected]
mother's maiden name 99l4Jhp7
password somepassword
Alright, so we're able to store and view passwords using the command line. This is practical if you have an ssh jump into your home network. The web interface is definitely much easier to work with though if you're running a VPN instead. So let's get that setup.
Configuring a Reverse Proxy
For the sake of this tutorial, I'm going to recommend Nginx as a reverse proxy. Much like with the database, your home server is probably already running this or Apache. I'll be using a LetsEncrypt certificate for good measure, but if your webserver isn't remote accessible at all, you might wind up using a self-signed cert instead.
# apt install nginx certbot python3-certbot-nginx
# certbot certonly --nginx -d vault.yourdomain.ext
Add an entry for Vault in /etc/nginx/sites-available/vault.conf
server {
listen 80;
server_name vault.yourdomain.ext;
return 301 https://vault.yourdomain.ext$request_uri;
access_log /var/log/nginx/vault.access.log;
error_log /var/log/nginx/vault.error.log;
}
server {
listen 443 ssl;
server_name vault.yourdomain.ext;
access_log /var/log/nginx/vault.access.log;
error_log /var/log/nginx/vault.error.log;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_certificate /etc/letsencrypt/live/vault.yourdomain.ext/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.yourdomain.ext/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8200/;
proxy_buffering off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 0;
# Only allow the internal network
allow 192.168.122.0/24;
allow 127.0.0.1/32;
deny all;
}
}
With this configuration, Vault will always use https and send a 403 to requests from the public internet.
# ln -s /etc/nginx/sites-available/vault.conf /etc/nginx/sites-enabled/vault.conf
# nginx -t
# systemctl restart nginx
Managing Secrets with the Web UI
So now you should be able to login through the web portal. Change the login method to Username and we'll sign in with our limited user.
We can see the passwords keystore we created before.
From here, we can either look at the secret we stored manually before, or create a new one. Let's add another password.
That's it! You now have a working Vault service serving an actual purpose on your home server. From here, you may as well spin up some new kv stores and users and start developing apps that use it.