Categories
Misc

Self-Hosted Bitwarden On Raspberry Pi

Install Docker

# curl -sSL https://get.docker.com | sh

Add user(s) to the docker group. The default user would be pi. However, I highly recommend deactivating the default user.

# usermod -aG docker pi

Reboot and then test docker

$ docker run hello-world

Install more dependencies

# apt-get install -y libffi-dev libssl-dev
# apt-get install -y python3 python3-pip
# apt-get remove python-configparser
# pip3 -v install docker-compose

Fight With SSL

This is the most annoying part of the story. You can either choose to use letsencrypt or a self-signed openssl-cert. Letsencrypt will only work, if your service will be exposed publicly. Also, letsencrypt is fairly easy to setup, so I will focus on a self-signed openssl-solution.

First, we’ll need a “virtual” certificate authority (CA) that will actually sign our certificate later. If you already have a CA, you can skip this. The first command creates a private key, the second command creates the root certificate of our CA.

$ openssl genrsa -out myCA.key 2048
$ openssl req -x509 -new -nodes -sha256 -days 3650 -key myCA.key -out myCA.crt

Now, we’ll need to create a “client” key and a certificate signing request, which will then be “sent” to our CA.

$ openssl genpkey -algorithm RSA -out bitwarden.key -outform PEM -pkeyopt rsa_keygen_bits:2048
$ openssl req -new -key bitwarden.key -out bitwarden.csr

For the actual signing, we’ll also need an extension file. I ran into problems with OSX and iOS without adding the used extensions during signing. Neither OSX, iOS nor Google Chrome accepted the certificate without those extensions. Create a file openssl.cnf

[v3_ca]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = DNS:<hostname>, IP:127.0.0.1, IP:<ip>

Replace <hostname> and <ip> with your actual values.

Finally, the actual signing:

$ openssl x509 -req -in bitwarden.csr -CA myCA.crt -CAkey myCA.key -CAcreateserial -out bitwarden.crt -days 365 -sha256 -extfile openssl.cnf

The certificate you’ll need to deploy on your devices is the root certificate. Yes, this will also work on iOS.

Install/Configure Bitwarden

We’ll use the bitwarden_rs docker container. It uses sqlite instead of MSSQL, which is not available for ARM.

$ docker pull bitwardenrs/server:raspberry

If docker successfully downloaded the image, you can run it as follows. I simply created a small bash script.

$ docker run -d --name bitwarden \
-e ROCKET_TLS='{certs="/ssl/bitwarden.crt",key="/ssl/bitwarden.key"}' \
-v /path/to/certs/:/ssl/ \
-v /path/to/bw-data/:/data/ \
-v bitwarden:/config \
-p 443:80 \
--restart always \
bitwardenrs/server:raspberry

The ROCKET_TLS argument tells bitwarden, where it can find its key and certificate. The values describe paths within the docker container. For these paths to work, we’ll need to supply a volume mapping (-v). The additional volume mapping bw-data is a volume for bitwarden to store its actual sqlite “database” in. Internally, bitwarden will bind to port 80. Since we know/hope it’ll run SSL, we can map internal port 80 to 443.

If everything works, you can reach your bitwarden vaults on https://<hostname>

You’ll most likely run into SSL problems. Good luck.

Backup

Read this article.

Debugging/FAQ

Show running docker containers

$ docker ps

Logs and events

$ docker logs <container>
$ docker events

Run command within a docker container

$ docker exec -it <container> /bin/bash

Netstat (works w/o actual netstat binary in container. Cool, eh!?)

$ docker inspect -f '{{.State.Pid}}' <container>
# nsenter -t <pid> -n netstat

A word on IPv6. Initially, when bitwarden didn’t work during my first attempts, I was confused by the output of netstat. It showed, that the destination socket for https was only bound to tcp6. This shouldn’t be a problem, though, because bitwarden also sets up a couple of iptables rules (# iptables -L). However, if you think it might be a problem on your machine, try the following things in your /etc/sysctl.conf

net.ipv6.bindv6only = 0
net.ipv6.conf.all.forwarding = 1
net.ipv4.conf.all.forwarding = 1

At one point, I even completely disabled IPv6 via the kernel command line. However, that introduced even more problems.