The time has come for me to consider another application for my TOTP data (think 6-digit codes produced by Google Authenticator or an RSA device. I’ve been using an app called 2STP – I have long liked it. Support for it ended about 7 years ago, yet it continued to slug along on my phone and on my watch.
Recently, it stopped working on my watch. That was the tipping point.
I decided to move to BitWarden. I’ll use BitWarden apps on my phone and watch, and use VaultWarden for the storage engine. VaultWarden is a “Bitwarden server API implementation written in Rust compatible with upstream Bitwarden clients”.
In this post:
- FreeBSD 14.1
- vaultwarden 1.32.0_2
- BitWarden 2024.7.1 (8086)
I also referred to a blog post by Ben Lavery-Griffiths.
Installing
I created a new jail dedicated to this task. Those steps are outside the scope of this post.
- The main docs: https://github.com/dani-garcia/vaultwarden/
- The main wiki: https://github.com/dani-garcia/vaultwarden/wiki
- The FreeBSD doc: https://github.com/dani-garcia/vaultwarden/wiki/Third-party-packages#freebsd
When installing, I did the following
pkg install vaultwarden cp /usr/local/etc/rc.conf.d/vaultwarden.sample /usr/local/etc/rc.conf.d/vaultwarden
/usr/local/etc/rc.conf.d/vaultwarden contains configuration settings for vaultwarden – I’m not sure how this works. It doesn’t seems to be the traditional rc.conf settings I expect to see. However, it does work. Really, all it’s doing is setting a bunch of environment variables. I suspect that’s what VaultWarden is expecting.
Things I changed in that file:
-ROCKET_ADDRESS=127.0.0.1 +ROCKET_ADDRESS=127.0.0.81
This was in a jail and I wanted something other than 127.0.0.1
Without the following, I could not create a new account.
-SIGNUPS_ALLOWED='false' +SIGNUPS_ALLOWED='true'
I changed it back later.
I don’t run an smtp service here (it’s dma only), so I supplied values for SMTP_HOST & SMTP_FROM
I also set DOMAIN to the hostname I’m using for my service.
Starting
Here is the start:
[root@bw:~] $ service vaultwarden enable vaultwarden enabled in /etc/rc.conf [root@bw:~] $ service vaultwarden start Starting vaultwarden.
From /var/log/daemon.log:
Sep 29 21:07:30 bw vaultwarden[84275]: [2024-09-29 21:07:30.121][rocket::server][WARN] Received SIGTERM. Requesting shutdown. Sep 29 21:07:30 bw vaultwarden[84275]: [2024-09-29 21:07:30.122][vaultwarden][INFO] Vaultwarden process exited! Sep 29 21:07:30 bw vaultwarden[3529]: /--------------------------------------------------------------------\ Sep 29 21:07:30 bw vaultwarden[3529]: | Starting Vaultwarden | Sep 29 21:07:30 bw vaultwarden[3529]: |--------------------------------------------------------------------| Sep 29 21:07:30 bw vaultwarden[3529]: | This is an *unofficial* Bitwarden implementation, DO NOT use the | Sep 29 21:07:30 bw vaultwarden[3529]: | official channels to report bugs/features, regardless of client. | Sep 29 21:07:30 bw vaultwarden[3529]: | Send usage/configuration questions or feature requests to: | Sep 29 21:07:30 bw vaultwarden[3529]: | https://github.com/dani-garcia/vaultwarden/discussions or | Sep 29 21:07:30 bw vaultwarden[3529]: | https://vaultwarden.discourse.group/ | Sep 29 21:07:30 bw vaultwarden[3529]: | Report suspected bugs/issues in the software itself at: | Sep 29 21:07:30 bw vaultwarden[3529]: | https://github.com/dani-garcia/vaultwarden/issues/new | Sep 29 21:07:30 bw vaultwarden[3529]: \--------------------------------------------------------------------/ Sep 29 21:07:30 bw vaultwarden[3529]: Sep 29 21:07:30 bw vaultwarden[3529]: [DEPRECATED]: `SMTP_SSL` or `SMTP_EXPLICIT_TLS` is set. Please use `SMTP_SECURITY` instead. Sep 29 21:07:30 bw vaultwarden[3529]: [2024-09-29 21:07:30.258][start][INFO] Rocket has launched from http://127.0.0.81:4567
Note the expect IP address there.
This is what is looks like:
[root@bw:~] $ ps auwwx USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND www 39535 66.8 0.0 364276 38096 - SJ 19:52 0:01.31 /usr/local/bin/vaultwarden root 26703 0.0 0.0 12876 2728 - SsJ 19:29 0:00.01 /usr/sbin/syslogd -s root 26746 0.0 0.0 12916 2500 - IsJ 19:29 0:00.01 /usr/sbin/cron -s www 39534 0.0 0.0 12828 2300 - SsJ 19:52 0:00.00 daemon: /usr/local/bin/vaultwarden[39535] (daemon) root 26759 0.0 0.0 13376 3232 2 IJ 19:29 0:00.02 /bin/sh -i root 38654 0.0 0.0 14840 4416 2 IJ 19:51 0:00.02 zsh root 38766 0.0 0.0 14356 4272 2 SJ 19:51 0:00.02 bash root 39543 0.0 0.0 13452 2976 2 R+J 19:52 0:00.00 ps auwwx [root@bw:~] $
TLS? Who wants TLS?
The docs recommend a reverse proxy to implement TLS. Here we go:
pkg install nginx
This is my nginx configuration:
server { listen 80; server_name mine.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl; http2 on; ssl_certificate /usr/local/etc/ssl/mine.example.com.cer; ssl_certificate_key /usr/local/etc/ssl/mine.example.com.key; server_name mine.example.com; location / { proxy_pass http://127.0.0.81:4567; } access_log /var/log/nginx/mine.example.com-access.log; error_log /var/log/nginx/mine.example.com-error.log; }
There’s that same IP address and port number again. Look for yours in the logs.
Once nginx was running, Here’s what was listening:
[root@bw:~] $ sockstat -4 USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS www nginx 84281 8 tcp4 *:80 *:* www nginx 84281 9 tcp4 *:443 *:* www nginx 84281 12 tcp4 203.0.113.231:443 203.0.113.82:60294 root nginx 84280 8 tcp4 *:80 *:* root nginx 84280 9 tcp4 *:443 *:* www vaultwarde 84276 34 tcp4 127.0.0.81:4567 *:* root syslogd 84242 5 udp4 *:514 *:*
Here’s some of the stuff I saw in the logs, nothing relevant here, just showing.
[root@bw:/var/log] $ tail -F /var/log/daemon.log Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.563][request][INFO] POST /identity/accounts/register Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.564][vaultwarden::api::core::accounts][ERROR] Registration not allowed or user already exists Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.564][response][INFO] (identity_register) POST /identity/accounts/register => 400 Bad Request
The rest of the signup
After browsing to https:///mine.example.com, I created myself an account, and it all just worked.
In my BitWarden app, I signed in by pointing to a self hosted install, using the same URL. It also just worked.
The data to backup
The data I think I need to backup:
[root@bw:/usr/local/www/vaultwarden/data] $ ls -l total 172 drwxr-xr-x 2 www www 2 Sep 29 19:52 attachments -rw-r--r-- 1 www www 335872 Oct 1 00:26 db.sqlite3 -rw-r--r-- 1 www www 32768 Oct 1 00:26 db.sqlite3-shm -rw-r--r-- 1 www www 0 Oct 1 00:26 db.sqlite3-wal drwxr-xr-x 2 www www 2 Sep 29 19:52 icon_cache -rw-r--r-- 1 www www 1675 Sep 29 19:52 rsa_key.pem drwxr-xr-x 2 www www 2 Sep 29 19:52 sends drwxr-xr-x 2 www www 2 Sep 29 19:52 tmp
I’m sure the main stuff to backup is the db.* files. Yet, I’m sure it should all be backed up. It’s about 88K – easily accommodated. See the backup docs.
I have yet to do this, and I’m relying on my jail-based backups. Oh. I see I have to implement those backups on that host.
Things to fix up
Everything logs to both /var/log/daemon.log and /var/log/messages – this is hardcoded in the rc.d script. I’d prefer that to be removed and configurable via /etc/rc.conf settings
Copying data
For copying data out of 2STP, I used https://github.com/ewdurbin/evacuate_2stp
Future use
I need to decide if I’m going to continue using this implementation or switch to letting BitWarden host my 88K of data.
Is it worth the effort required to maintain etc?
I still plan to sign up with BitWarden and give them money, regardless.
I am a long time happy user of OTP Auth [1] on iOS (also available for iPadOS and WatchOS). The features I like:
– Folders can be created to organize the entries
– Tabbing an entry will put the code into the clipboard (and then could be pasted on macOS)
– Selected entries can be added to the Widget
– In edit mode the secret and QR Code can be shown again
– With the Pro version (in-App purchase, $3.99) custom icons or emojis can be assigned to entries
– Password protected exports / backup
– Supports iCloud sync (I am not using that yet)
I just discovered that a desktop [2] version also is available. I guess I should consider that with iCloud Sync as well. I aware that this kind of is not the preferred idea of 2FA, but also the copy & paste between iPhone and Mac is probably not.
[1] https://apps.apple.com/app/otp-auth/id659877384
[2] https://apps.apple.com/app/otp-auth/id1471867429
Your comment seems more like an advertisement than something which can be self hosted.
Hello Dan
You are right, I also prefer to self host the things I am using. My comment was primarily to show you an alternative to 2STP you mention as introduction into this article. At my day job we also have BitWarden in use. But my personal opinion about this is, that such a setup is even more against the basic idea of having a proper 2FA setup with the one thing you should have. With any of the password safe services (even self hosted) at the end you have everything (username, password and 2FA) stored in a single location. Better not to put all eggs into one bucket.
When I have to activate 2FA for a service, I also save the image of the QR code (and if available also the ASCII secret) into a local encrypted .dmg / .sparseimage (do backups!) to be able to scan it again later e.g. with another device (in case the main one breaks down). This also works in case for some ugly online services where only a single credential is possible but access needs to be shared between multiple people (BitWarden and friends also solve this). Scanning the initial QR later one works just fine.
There are also command line options available to use for 2FA, a friend recommends zbarimg (FreeBSD Port: graphics/zbar) and oathtool (FreeBSD Port: security/oath-toolkit). Save the QR code and use ‘zbarimg’ do decode the secret and then use ‘oathtool -b –totp secret’ to get the 6 digits for 2FA. This could also be used with pass (https://www.passwordstore.org/) through pass-otp (https://github.com/tadfisher/pass-otp).
Best regards,
Fabian
Great post.
I’d recommend putting in a block to restrict access to /admin except from trusted IPs.
Just now, I tried that URL on my instance. I first had to remember the hostname for my instance. That’s how reliable it has been: I’ve use the hostname only once, to set up BitWarden, and now I’ve forgotten it. I had to ssh into the server to find out….
When I go to /admin, without being logged in, I get:
So yes, adding a restrict clause seems a good idea for those times when ADMIN_TOKEN is configured. Travis: can you share your restriction to get us started please?
You use nginx so I would assume it would be something like this:
location /admin {
allow 1.2.3.4;
deny all;
}
I use CloudFlare so my WAF rule looks like this:
(http.request.uri wildcard r"/admin" and http.host eq "my.domain.example")