runtimeterror/content/posts/bitwarden-password-manager-self-hosted-on-free-google-cloud-instance/index.md

251 lines
9.8 KiB
Markdown
Raw Normal View History

---
series: Projects
date: "2018-09-26T08:34:30Z"
lastmod: "2022-03-06"
thumbnail: i0UKdXleC.png
usePageBundles: true
tags:
- docker
- linux
- cloud
- gcp
- security
2023-09-11 19:53:22 +00:00
- selfhosting
title: BitWarden password manager self-hosted on free Google Cloud instance
---
![Bitwarden login](i0UKdXleC.png)
A friend mentioned the [BitWarden](https://bitwarden.com/) password manager to me yesterday and I had to confess that I'd never heard of it. I started researching it and was impressed by what I found: it's free, [open-source](https://github.com/bitwarden), feature-packed, fully cross-platform (with Windows/Linux/MacOS desktop clients, Android/iOS mobile apps, and browser extensions for Chrome/Firefox/Opera/Safari/Edge/etc), and even offers a self-hosted option.
I wanted to try out the self-hosted setup, and I discovered that the [official distribution](https://help.bitwarden.com/article/install-on-premise/) works beautifully on an `n1-standard-1` 1-vCPU Google Compute Engine instance - but that would cost me an estimated $25/mo to run after my free Google Cloud Platform trial runs out. And I can't really scale that instance down further because the embedded database won't start with less than 2GB of RAM.
I then came across [this comment](https://www.reddit.com/r/Bitwarden/comments/8vmwwe/best_place_to_self_host_bitwarden/e1p2f71/) on Reddit which discussed in somewhat-vague terms the steps required to get BitWarden to run on the [free](https://cloud.google.com/free/docs/always-free-usage-limits#compute_name) `e2-micro` instance, and also introduced me to the community-built [vaultwarden](https://github.com/dani-garcia/vaultwarden) project which is specifically designed to run a BW-compatible server on resource-constrained hardware. So here are the steps I wound up taking to get this up and running.
{{% notice note "bitwarden_rs -> vaultwarden"%}}
When I originally wrote this post back in September 2018, the containerized BitWarden solution was called `bitwarden_rs`. The project [has since been renamed](https://github.com/dani-garcia/vaultwarden/discussions/1642) to `vaultwarden`, and I've since moved to the hosted version of BitWarden. I have attempted to update this article to account for the change but have not personally tested this lately. Good luck, dear reader!
{{% /notice %}}
### Spin up a VM
*Easier said than done, but head over to https://console.cloud.google.com/ and fumble through:*
1. Creating a new project (or just add an instance to an existing one).
2. Creating a new Compute Engine instance, selecting `e2-micro` for the Machine Type and ticking the *Allow HTTPS traffic* box.
3. *(Optional)* Editing the instance to add an ssh-key for easier remote access.
### Configure Dynamic DNS
*Because we're cheap and don't want to pay for a static IP.*
1. Log in to the [Google Domain admin portal](https://domains.google.com/registrar) and [create a new Dynamic DNS record](https://domains.google.com/registrar). This will provide a username and password specific for that record.
2. Log in to the GCE instance and run `sudo apt-get update` followed by `sudo apt-get install ddclient`. Part of the install process prompts you to configure things... just accept the defaults and move on.
3. Edit the `ddclient` config file to look like this, substituting the username, password, and FDQN from Google Domains:
2023-10-16 21:48:55 +00:00
```command
sudo vim /etc/ddclient.conf
```
```cfg {linenos=true,hl_lines=["10-12"]}
# Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf
protocol=googledomains,
ssl=yes,
syslog=yes,
use=web,
server=domains.google.com,
login='[USERNAME]',
password='[PASSWORD]',
[FQDN]
```
4. `sudo vi /etc/default/ddclient` and make sure that `run_daemon="true"`:
2023-10-16 21:48:55 +00:00
```cfg {linenos=true,hl_lines=16}
# Configuration for ddclient scripts
# generated from debconf on Sat Sep 8 21:58:02 UTC 2018
#
# /etc/default/ddclient
# Set to "true" if ddclient should be run every time DHCP client ('dhclient'
# from package isc-dhcp-client) updates the systems IP address.
run_dhclient="false"
# Set to "true" if ddclient should be run every time a new ppp connection is
# established. This might be useful, if you are using dial-on-demand.
run_ipup="false"
# Set to "true" if ddclient should run in daemon mode
# If this is changed to true, run_ipup and run_dhclient must be set to false.
run_daemon="true"
# Set the time interval between the updates of the dynamic DNS name in seconds.
# This option only takes effect if the ddclient runs in daemon mode.
daemon_interval="300"
```
5. Restart the `ddclient` service - twice for good measure (daemon mode only gets activated on the second go *because reasons*):
2023-10-16 21:48:55 +00:00
```command
sudo systemctl restart ddclient
sudo systemctl restart ddclient
```
6. After a few moments, refresh the Google Domains page to verify that your instance's external IP address is showing up on the new DDNS record.
### Install Docker
*Steps taken from [here](https://docs.docker.com/install/linux/docker-ce/debian/).*
1. Update `apt` package index:
2023-10-16 21:48:55 +00:00
```command
sudo apt-get update
```
2. Install package management prereqs:
2023-10-16 21:48:55 +00:00
```command-session
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg2 \
software-properties-common
```
3. Add Docker GPG key:
2023-10-16 21:48:55 +00:00
```command
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
```
4. Add the Docker repo:
2023-10-16 21:48:55 +00:00
```command-session
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/debian \
$(lsb_release -cs) \
stable"
```
5. Update apt index again:
2023-10-16 21:48:55 +00:00
```command
sudo apt-get update
```
6. Install Docker:
2023-10-16 21:48:55 +00:00
```command
sudo apt-get install docker-ce
```
### Install Certbot and generate SSL cert
*Steps taken from [here](https://certbot.eff.org/instructions?ws=other&os=debianbuster).*
1. Install Certbot:
2023-10-16 21:48:55 +00:00
```command
sudo apt-get install certbot
```
2. Generate certificate:
2023-10-16 21:48:55 +00:00
```command
sudo certbot certonly --standalone -d [FQDN]
```
3. Create a directory to store the new certificates and copy them there:
2023-10-16 21:48:55 +00:00
```command
sudo mkdir -p /ssl/keys/
sudo cp -p /etc/letsencrypt/live/[FQDN]/fullchain.pem /ssl/keys/
sudo cp -p /etc/letsencrypt/live/[FQDN]/privkey.pem /ssl/keys/
```
### Set up vaultwarden
*Using the container image available [here](https://github.com/dani-garcia/vaultwarden).*
1. Let's just get it up and running first:
2023-10-16 21:48:55 +00:00
```command-session
sudo docker run -d --name vaultwarden \
-e ROCKET_TLS={certs='"/ssl/fullchain.pem", key="/ssl/privkey.pem"}' \
-e ROCKET_PORT='8000' \
-v /ssl/keys/:/ssl/ \
-v /bw-data/:/data/ \
-v /icon_cache/ \
-p 0.0.0.0:443:8000 \
vaultwarden/server:latest
```
2. At this point you should be able to point your web browser at `https://[FQDN]` and see the BitWarden login screen. Click on the Create button and set up a new account. Log in, look around, add some passwords, etc. Everything should basically work just fine.
3. Unless you want to host passwords for all of the Internet you'll probably want to disable signups at some point by adding the `env` option `SIGNUPS_ALLOWED=false`. And you'll need to set `DOMAIN=https://[FQDN]` if you want to use U2F authentication:
```shell
2023-10-16 21:48:55 +00:00
sudo docker stop vaultwarden
sudo docker rm vaultwarden
sudo docker run -d --name vaultwarden \
-e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
-e ROCKET_PORT='8000' \
-e SIGNUPS_ALLOWED=false \
-e DOMAIN=https://[FQDN] \
-v /ssl/keys/:/ssl/ \
-v /bw-data/:/data/ \
-v /icon_cache/ \
-p 0.0.0.0:443:8000 \
vaultwarden/server:latest
```
### Install vaultwarden as a service
*So we don't have to keep manually firing this thing off.*
2023-10-16 21:48:55 +00:00
1. Create a script at `/usr/local/bin/start-vaultwarden.sh` to stop, remove, update, and (re)start the `vaultwarden` container:
```command
sudo vim /usr/local/bin/start-vaultwarden.sh
```
```shell
2023-10-16 21:48:55 +00:00
#!/bin/bash
docker stop vaultwarden
docker rm vaultwarden
docker pull vaultwarden/server
docker run -d --name vaultwarden \
-e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
-e ROCKET_PORT='8000' \
-e SIGNUPS_ALLOWED=false \
-e DOMAIN=https://[FQDN] \
-v /ssl/keys/:/ssl/ \
-v /bw-data/:/data/ \
-v /icon_cache/ \
-p 0.0.0.0:443:8000 \
vaultwarden/server:latest
```
```command
sudo chmod 744 /usr/local/bin/start-vaultwarden.sh
```
2. And add it as a `systemd` service:
2023-10-16 21:48:55 +00:00
```command
sudo vim /etc/systemd/system/vaultwarden.service
```
```cfg
[Unit]
Description=BitWarden container
Requires=docker.service
After=docker.service
[Service]
Restart=always
ExecStart=/usr/local/bin/vaultwarden-start.sh
ExecStop=/usr/bin/docker stop vaultwarden
[Install]
WantedBy=default.target
2023-10-16 21:48:55 +00:00
```
```command
sudo chmod 644 /etc/systemd/system/vaultwarden.service
```
3. Try it out:
2023-10-16 21:48:55 +00:00
```command
sudo systemctl start vaultwarden
```
```command-session
sudo systemctl status vaultwarden
● bitwarden.service - BitWarden container
Loaded: loaded (/etc/systemd/system/vaultwarden.service; enabled; vendor preset: enabled)
Active: deactivating (stop) since Sun 2018-09-09 03:43:20 UTC; 1s ago
Process: 13104 ExecStart=/usr/local/bin/bitwarden-start.sh (code=exited, status=0/SUCCESS)
Main PID: 13104 (code=exited, status=0/SUCCESS); Control PID: 13229 (docker)
Tasks: 5 (limit: 4915)
Memory: 9.7M
CPU: 375ms
CGroup: /system.slice/vaultwarden.service
└─control
└─13229 /usr/bin/docker stop vaultwarden
Sep 09 03:43:20 vaultwarden vaultwarden-start.sh[13104]: Status: Image is up to date for vaultwarden/server:latest
Sep 09 03:43:20 vaultwarden vaultwarden-start.sh[13104]: ace64ca5294eee7e21be764ea1af9e328e944658b4335ce8721b99a33061d645
```
### Conclusion
If all went according to plan, you've now got a highly-secure open-source full-featured cross-platform password manager running on an Always Free Google Compute Engine instance resolved by Google Domains dynamic DNS. Very slick!