Self-Hosted Passwords

How to run Bitwarden for more than 3 months.


Like many others, I had difficult decisions to make when LastPass restricted free accounts in February 2021. Password management is essential for my mental stability, and I've always been uncomfortable handing secrets to a third party, so I've migrated to vaultwarden on a (firewalled) raspberry pi. Vaultwarden is an open-source Bitwarden implementation, compatible with all the first-party apps.

Here are a few issues I came across and how I solved them, read on if you're interested. If this post reminds me what I've done in a year's time, it will have been worthwhile regardless.


VPN

The vaultwarden server is at home, somewhere under 10.100.1.1/24, and I often need to store/fill passwords while I'm out. Having access all the time isn't essential, as the mobile app will happily cache passwords until the next time it can connect, but I thought it best to sync as I go in case of sudden unexpected defenestration.

Wireguard is my over-the-top solution, there's an interface on the pi:

    [Interface]
    Address = 10.100.3.1/24
    SaveConfig = true
    PostUp = <boring>
    PostDown = <boring>
    ListenPort = <high port>
    FwMark = <???>
    PrivateKey = <super secret>
    
    [Peer]
    PublicKey = <not so secret>
    AllowedIPs = 10.100.3.2/32
    Endpoint = <auto-updated>

The defined peer corresponds to my phone, and we have the following designated ranges at home:

10.100.1.1/24
Networking hardware and default DHCP pool
10.100.2.1/24
Devices visible externally to Wireguard peers 1
10.100.3.1/24
Virtual IPs routed to Wireguard peers

1 Currently this isn't implemented, one thing at a time. Maybe a topic for another post.

Hopefully you can see how this will be scalable as my requirements inevitably grow. Wireguard and vaultwarden are hosted on the same pi, which makes things easy. Remember to configure your firewall, kids.


DNS

The lovely ladies and gents at BT only offer us a dynamic IP from one of their prole pools, so writing wireguard peer config that survives a router reboot will require some DDNS. Fine, I have domains I can use, and Cloudflare have a decent API for it.

I'm surprised there's no off-the-shelf script, but it was simple enough to cobble together: Gitlab snippet. The piguard.$domain hostname should point to my home IP, with the relevant port forwarded to the raspberry pi. You'll have to mentally replace $domain with an example of your choice.

The Bitwarden app requires a hostname for the self-hosted backend. That's fine too, we'll just point vault.$domain at 10.100.3.1 and Bob's your uncle. Wireguard auto-connects on my phone when I leave Wi-Fi range, so there should be no downtime. Neat.


PKI

Bitwarden also requires a trusted certificate for the HTTP API. What's more, Certbot won't issue me a Let's Encrypt certificate via the pi because it isn't internet accessible in a useful way. Nothing's ever easy is it?

Thankfully we can get around this restriction by issuing a wildcard certificate on a different server that does host web applications for that domain, and copy across the certificates after every renewal. Simple in theory, harder if you care about defence in depth.

The certificates generated by Certbot are (typically) stored in /etc/letsencrypt. These are, quite rightly, only root-accessible. The best solution I have come up with is to forward a random high port to piguard:22 at home, set up a Let's Encrypt renewal hook to tar up and scp the certificates to an unprivileged account 2 on the pi, and use a cron job to extract the certs and restart nginx.

2Allowing root SSH, even passwordless, gives me the heebie jeebies. We all need boundaries.


I did have a more elegant solution nearly working, which used systemd timers to tar, transfer and extract the cert bundle at renewal time, but it didn't quite work. It would have been beautiful, but I prefer working solutions to pretty things, so I scrapped it.

Huge thanks to Wireguard, Let's Encrypt, Bitwarden (especially for supporting self-hosted installations), and vaultwarden (please don't change your name again) which together make these shenanigans possible.

I do realise that I can use DNS challenges to authenticate with Let's Encrypt from an inaccessible server; in fact this is the method I use for the wildcard cert. If I were to renew the cert on the pi, however, I'd still have to do the transfer, but in reverse. I'd rather have temporary password manager outages when my cron breaks instead of downtime on the sites covered by the certificate, so I'm leaving it how it is.

I hope you enjoyed this ramble, see you next time.

—James
09 May 2021