Run Your Own LastPass on Hardened OpenBSD

With LastPass having suffered yet another data breachimportant update here – it’s probably time to take matters into your own hands and run your own cloud password manager. Let’s do exactly that, on a hardened OpenBSD system!

Run Your Own LastPass on Hardened OpenBSD

Update 2022-12-22: Turns out attackers were in fact able to gain access to customer’s encrypted vaults on LastPass. Check the comments on Hacker News.

As humans our brains are naturally programmed to be very selective of the information they absorb. With the millions of messages the brain receives at any given time, it is obvious that substantial filtering is required in order for us to be able to function. Even when we pay close attention to something it might well be that details won’t stick for long. We can all agree that things that we can relate to or that make sense to us are easier to remember than those that do not, without going into great detail about how “working memory” functions or how synapses are generated and strengthened. For example we use vanity phone numbers – that is a phone number like 1-800-GoFedEx – because it’s easier for us to remember them over a random sequence of numbers that we can’t relate to. Ask yourself in two hours What’s FedEx’ phone number? and you’ll likely be able to tell.

Relatability and thereby rememberability is also the reason why 123456, password, monkey and trustno1 are amongst the most common passwords, even when something like VMemL9U7Vx5dBtrSsNfyn3Sq would be a more sensible choice. For the average Joe it would be pretty much impossible to come up with a password like that and instantly remember it. Heck, I can barely remember the Punycode of my own domain that I’ve been using for several years now.

To help with this, password managers have been on the rise over the past decade or so. Password managers intend to replace the PostIt notes on people’s computer monitors and allow for complex passwords without the hassle of remembering them or writing them down in various places. It’s basically an encrypted database that uses a single master password to encrypt all other passwords with. Ideally the master password is the only complex password a user has to remember, in order to unlock the password manager and allow it to automatically fill login forms with previously saved account info.

To make the passwords database available across every device, password managers usually offer a paid cloud service for syncing. Like every cloud service, password managers have to work tirelessly to protect their users’ data from malicious actors. Unfortunately snafus still happen from time to time, which in some cases could lead to actual user data being accessed by others. Of course, password databases are encrypted, and with a strong enough password there’s little chance for individual actors to actually crack open a database. Yet even just the theoretical possibility of this happening begs the question: Are the features that we’re getting from services like 1Password, LastPass and others really worth the risk? Wouldn’t we be better off hosting our own password manager cloud, that is somewhat less in the spotlight of attackers or might not even be accessible to them in first place?

“But that’s awfully complicated and probably requires developing all the nifty clients that 1Password, LastPass and others already have!” you might say. Well, it turns out, everything we need is already there, as beautiful open source software.

Let’s have a look at how we can set up all the bits and pieces to run our own password manager cloud on OpenBSD, using the open source password manager Bitwarden – but with a twist!

Options

I’m going to go through two different options here:

  • A bare metal setup, in this case on a Raspberry Pi 4B
  • A cloud instance, in this case on Vultr

The installation differs depending on where you choose to set this up. For the bare metal option installation of the base OpenBSD system might be different for your hardware. In case you should be going for a cloud instance, I’d recommend making it solely available through a VPN for enhanced protection.

Option A: Bare metal (Raspberry Pi)

First things first: Make sure your Raspberry runs the latest firmware version. If it doesn’t, flash Raspberry Pi OS on a microSD card, run it and use the rpi-update and rpi-eeprom-update commands to update the device.

Next, download the latest OpenBSD arm64 images (7.2 in this case), flash the miniroot72.img onto a microSD card, and install72.img onto an USB stick and plug both into the Raspberry Pi. Connect the device to a display, attach a keyboard and connect power to start it. As soon as you can see the boot> prompt, type in set tty fb0 and hit enter. The installation should start.

Hint: Sometimes it might be needed to press random keys on the keyboard for procedures to continue.

Option B: Cloud instance

If you’re going to use a cloud instance for this, there’s not much you need to do apart from launching a new compute instance with the latest OpenBSD image available. However, if you’re looking forward to have the instance drive encrypted, you will need to install a new compute instance using the official OpenBSD install image. For installation details, please continue below.

(Optional) Hard Drive Encryption

Our passwords database in itself is already going to be encrypted, meaning that you don’t necessarily need an extra layer of at-rest encryption. However, if it lets you sleep better to know that even in case an adversary would rip the hard drive out of your device it wouldn’t be much benefit to them (for the foreseeable future), then this part is for you.

Remember though that with hard drive encryption you will have to enter the passphrase every time the device/server boots. This means that for something like a Raspberry Pi you’ll need to have a keyboard and ideally a display attached – or a KMV – and for cloud instances you’ll have to connect to it via VNC on every reboot to enter the passphrase. The latter option might not be ideal as you probably can’t tell for sure whether the connection is sufficiently secure for you to send your encryption passphrase through it. For physical hardware you could always switch to a certificate on an USB stick or a YubiKey, to avoid attaching a keyboard/display every time it requires a reboot.

Let’s quickly go over how to install OpenBSD with hard drive encryption turned on:

(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? s

First, find out block device(s):

# dmesg | grep -i block

It’s usually something like sd0. Next, add the device for it under /dev:

# cd /dev
# sh MAKEDEV sd0

Continue by writing the MBR boot code, then create a partition layout and add an encrypted device:

# fdisk -iy sd0
# disklabel -E sd0
sd0> a a
offset: 64
size: *
FS type: RAID
sd0*> w
sd0> q
# bioctl -c C -l sd0a softraid0
# cd /dev
# sh MAKEDEV sd1
# dd if=/dev/zero of=/dev/rsd1c bs=1m count=1
# exit

Next we’re going to install OpenBSD as we usually would. Adjust the values (hostname, username, network interfaces, …) to your needs. Values in square brackets are default values. If there’s no other value behind them it means we can press the enter key to accept the default:

(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? i

Choose your keyboard layout ('?' or 'L' for list) [default] 

System hostname? (short form, e.g. 'foo') v4u1t

(for bare metal could be) Available network interfaces are bse0 bwfm0.
(for cloud instances usually) Available network interfaces are vio0 vlan0.

Which network interface do you wish to configure? (or 'done') [bse0]

IPv4 address for bse0? (or 'autoconf' or 'none') [autoconf]

IPv6 address for bse0? (or 'autoconf' or 'none') [none] autoconf

(for bare metal could be) Available network interfaces are bse0 bwfm0.
(for cloud instances usually) Available network interfaces are vio0 vlan0.

Which network interface do you wish to configure? (or 'done') [done]

Password for root account? (will not echo)

Password for root account? (again)

Start sshd(8) by default? [yes]

Do you expect to run the X Window System? [yes] no

Setup a user? (enter a lower-case loginname, or 'no') [no] mrus

Full name for user mrus [mrus]

Password for user mrus? (will not echo)

Password for user mrus? (again)

Allow root ssh login? (yes, no, prohibit-password) [no]

What timezone are you in? ('?' for list) [US/East]

Available disks are: sd0 sd1.

Which disk is the root disk? ('?' for details) [sd0] sd1

No valid MBR or GPT.

Use (W)hole disk MBR, whole disk (G)PT or (E)dit? [whole]

Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout? [a]

Available disks are sd0.

Which disk do you wish to initialize? (or 'done') [done]

Location of sets? (cd0 disk http nfs or 'done') [cd0]

Pathname to the sets? (or 'done') [7.2/amd64]

Set name(s)? (or 'abort' or 'done') [done] -game* -x*

Directory does not contain SHA256.sig. Continue without verification? [no] yes

Location of sets? (cd0 disk http nfs or 'done') [done]

Exit to (S)hell, (H)alt or (R)eboot? [reboot]

Voilà! Your device/cloud instance now runs from an encrypted drive.

First Boot

As usual when booting OpenBSD for the first time, let’s take care of a few basics:

v4u1t# mail
v4u1t# man afterboot

On actual hardware you might want to check date and see what time it is. In case it seems off we can correct it right away:

v4u1t# ntpctl -sa
v4u1t# rdate -ncv time.google.com

Additionally I’ve changed the mirror that OpenBSD was using to a different one, since I was having issues with the default one lately:

v4u1t# cat /etc/installurl
https://ftp.eu.openbsd.org/pub/OpenBSD

Next, perform a system upgrade to -current. We want this because -stable releases especially for the password manager are outdated.

v4u1t# sysupgrade -s

The system will reboot – don’t forget to enter your passphrase in case you enabled drive encryption! – and on the next login you should be greeted with a version that says -current:

OpenBSD 7.2-current (GENERIC.MP) #1917: Thu Dec  8 15:24:02 MST 2022

Next install a set of handy tools:

v4u1t# pkg_add zsh neovim mosh rsync git login_oath py3-pyqrcode lynis
v4u1t# ln -s /usr/local/bin/nvim /usr/local/bin/vim
v4u1t# chsh -s /usr/local/bin/zsh root
v4u1t# chsh -s /usr/local/bin/zsh mrus

Hint: Changing vim to nvim or the default shell to zsh is personal preference and nothing that’s actually required.

Now continue by setting up an enhanced login procedure for our system user(s).

Public Key + TOTP + Password Authentication

Add the following configuration to the very end of /etc/login.conf:

totp:\
        :auth=-totp-and-pwd:\
        :tc=default:

In a second shell, connect via SSH as your user – mrus in my case – and create a TOTP key:

v4u1t% openssl rand -hex 20 > ~/.totp-key
v4u1t% chmod 400 ~/.totp-key
v4u1t% python3 -c "import pyqrcode;\
import binascii;\
import base64;\
f = open('.totp-key');\
key = f.read();\
f.close();\
qr = pyqrcode.create('otpauth://totp/"$USER"?secret='+\
base64.b32encode(binascii.unhexlify(key.rstrip('\00\n'))).decode('utf-8')+\
'&issuer=v4u1t');\
print(qr.terminal(quiet_zone=1));"

The QR code can be scanned with virtually any TOTP app on a phone and it’ll start generating one-time tokens, that we’ll use to log in on the system from now on.

Copy the local SSH public key from your computer (usually cat ~/.ssh/id_*.pub | wl-copy) and add it to your user on OpenBSD:

v4u1t% vim ~/.ssh/authorized_keys

Back in the root shell, add your user to the totp login class:

v4u1t# usermod -L totp mrus

And update the /etc/ssh/sshd_config:

Port 31231
Protocol 2
Compression no
LogLevel VERBOSE
ClientAliveInterval 180
ClientAliveCountMax 2
TCPKeepAlive no
LoginGraceTime 30
MaxAuthTries 2
MaxSessions 2

AllowUsers mrus
PermitRootLogin no
PermitEmptyPasswords no
PasswordAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
AuthenticationMethods publickey,password
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
X11Forwarding no
PermitUserEnvironment no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
GatewayPorts no
PrintMotd no
PrintLastLog yes

Important: Adjust the value of AllowUsers!

While we’re at it, let’s also change the Port to a number greater than 1024, e.g. 31231.

Before you do anything else, run rcctl restart sshd, open a third shell and test logging in with your user. Your SSH client will need to supply an SSH key and you will be asked to enter a password, for which you have to enter the TOTP token, followed by a / and then your password, e.g. 123456/mypassword123!.

If the login works you’re good to go. If not, double check every step until here and make sure you didn’t forget anything. Make sure to stay connected via SSH until you figured it out, otherwise you won’t be able to log back in again!

SSL

Let’s take care of the SSL certificate that we’ll be using to secure client connections with. If you’ve opted for the bare metal setup, we will create our own CA and use a self-signed certificate, for which we’ll install the intermediary certificate on our client devices. If you went for the cloud instance, we’re going to use a Let’s Encrypt certificate.

Option A: Own CA + Self-Signed Certificate

Start by installing HashiCorp Vault and jq:

v4u1t# pkg_add vault jq

Open the configuration file under /etc/vault/vault.hcl and set the node_id – for example to the machine’s hostname. Then start Vault using rcctl enable vault && rcctl start vault.

Before we use Vault, we need to initialize it once:

v4u1t# export VAULT_ADDR='http://127.0.0.1:8200'
v4u1t# vault operator init

Copy the output of this command and temporarily store it in a secure location. To understand what this is about, let’s have a look at the Vault documentation:

Initialization outputs two incredibly important pieces of information: the unseal keys and the initial root token. This is the only time ever that all of this data is known by Vault, and also the only time that the unseal keys should ever be so close together.

For the purpose of this getting started tutorial, save all of these keys somewhere, and continue. In a real deployment scenario, you would never save these keys together. Instead, you would likely use Vault’s PGP and Keybase.io support to encrypt each of these keys with the users’ PGP keys. This prevents one single person from having all the unseal keys. Please see the documentation on using PGP, GPG, and Keybase for more information.

Every initialized Vault server starts in the sealed state. From the configuration, Vault can access the physical storage, but it can’t read any of it because it doesn’t know how to decrypt it. The process of teaching Vault how to decrypt the data is known as unsealing the Vault.

Unsealing has to happen every time Vault starts. It can be done via the API and via the command line. To unseal the Vault, you must have the threshold number of unseal keys. In the output above, notice that the “key threshold” is 3. This means that to unseal the Vault, you need 3 of the 5 keys that were generated.

Continue by unsealing Vault. To do so, we need to run the following command three times, entering a different unseal key each time:

v4u1t# vault operator unseal

After the third run you’ll see the Sealed status changing to false, meaning that we’ve successfully unsealed Vault.

Finally, login to Vault using the root token from the previously stored output:

v4u1t# vault login
Token (will be hidden):

Now do the fun part: Generating the root CA!

v4u1t# export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='root-token-here'
v4u1t# vault secrets enable pki
v4u1t# vault secrets tune -max-lease-ttl=87600h pki
v4u1t# vault write -field=certificate pki/root/generate/internal \
  common_name="lan" \
  issuer_name="root-2022" \
  ttl=87600h > root_2022_ca.crt

Let’s check the certificate:

v4u1t# vault read pki/issuer/$(vault list pki/issuers/ | tail -n 1)

Next, we create a role for the root CA and configure the CA and CRL URLs:

v4u1t# vault write pki/roles/2022-servers allow_any_name=true
v4u1t# vault write pki/config/urls \
  issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
  crl_distribution_points="$VAULT_ADDR/v1/pki/crl"

Now, we generate the intermediate CA:

v4u1t# vault secrets enable -path=pki_int pki
v4u1t# vault secrets tune -max-lease-ttl=43800h pki_int
v4u1t# vault write -format=json pki_int/intermediate/generate/internal \
  common_name="LAN Intermediate Authority" \
  issuer_name="lan-intermediate" \
  | jq -r '.data.csr' > pki_intermediate.csr
v4u1t# vault write -format=json pki/root/sign-intermediate \
  issuer_ref="root-2022" \
  csr=@pki_intermediate.csr \
  format=pem_bundle ttl="43800h" \
  | jq -r '.data.certificate' > intermediate.cert.pem
v4u1t# vault write pki_int/intermediate/set-signed \
  certificate=@intermediate.cert.pem

Last but not least, we create the certificates:

v4u1t# vault write pki_int/roles/lan \
  issuer_ref="$(vault read -field=default pki_int/config/issuers)" \
  allowed_domains="lan" \
  allow_subdomains=true \
  max_ttl="720h" \
  key_bits=2048
v4u1t# VAULT_FORMAT=json vault write \
  pki_int/issue/lan \
  common_name="v4u1t.lan" \
  ttl="720h" > /tmp/req
v4u1t# jq --raw-output \
  '.data.certificate, .data.issuing_ca' \
  /tmp/req > /etc/ssl/vaultwarden.crt
v4u1t# jq --raw-output \
  '.data.private_key' \
  /tmp/req > /etc/ssl/private/vaultwarden.key
v4u1t# jq --raw-output \
  '.data.ca_chain[0], .data.ca_chain[1], .data.certificate, .data.issuing_ca, \
  .data.private_key' /tmp/req > /tmp/full.crt

Make sure to import/trust the root CA on all the devices you’re going to be using the Bitwarden clients from.

Option B: Let’s Encrypt

To get an SSL certificate (from Let’s Encrypt) we’ll use OpenBSD’s acme-client and set up its configuration, as well as a cron job that will make sure our certificate gets automatically prolonged.

First, create the file /etc/acme-client.conf with the following content:

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/ssl/private/letsencrypt.key"
}
domain my.domain.com {
  domain key "/etc/ssl/private/vaultwarden.key"
  domain certificate "/etc/ssl/vaultwarden.crt"
  domain full chain certificate "/etc/ssl/vaultwarden.pem"
  sign with letsencrypt
}

Make sure to replace my.domain.com with the (sub)domain that you pointed to your cloud instance. Next, we’ll need to configure httpd to run on port 80 and provide access to the ACME challenge. Create the file /etc/httpd.conf with the following content:

server "my.domain.com" {
  listen on * port 80
  root "/htdocs/my.domain.com"
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
  }
}

Additionally create the folder /var/www/htdocs/my.domain.com/ and place an empty index.html into it.

Let’s enable httpd:

v4u1t# rcctl enable httpd
v4u1t# rcctl restart httpd

We can now issue our SSL certificate by running the following command:

v4u1t# acme-client -v my.domain.com

Next, run crontab -e. This will open the current crontab for editing. Add the following line at the end of the table:

0 0 * * * acme-client -v my.domain.com && rcctl restart relayd

Option C: Let’s Encrypt (for Internal Services)

Another possibility for retrieving a valid SSL certificate for use with an internal service (e.g. on the Raspberry or a non-public cloud instance) is using Duck DNS or Cloudflare. Here you can find more info on this approach.

The Password Manager

Next we’re going to take care of the password manager. I initially stated that we’ll be using Bitwarden, but now that you’ve made it this far I feel I can no longer lie to you. We’re not quite going for Bitwarden. Bitwarden – specifically the server part – is a C# .NET and ASP.NET application that we certainly wouldn’t want to deal with on a lean, self-hosted environment like the one we’re setting up. Luckily, there’s an open source alternative to the Bitwarden server that’s written in Rust and can use a SQLite database (amongst others) to store our encrypted vaults: Vaultwarden.

Since Vaultwarden is available as an OpenBSD package, we can install it right away:

v4u1t# pkg_add vaultwarden vaultwarden-web
quirks-6.82 signed on 2022-12-07T11:23:36Z
Ambiguous: choose package for vaultwarden
a	0: <None>
	1: vaultwarden-1.26.0p2
	2: vaultwarden-1.26.0p2-mysql
	3: vaultwarden-1.26.0p2-postgresql
Your choice: 1
...

As we’re not using a dedicated database and instead go with SQLite, the first option is fine for us. However, if you’re looking to serve a broader audience and need to handle more load, the third option might probably make better sense. You will however have to set up a dedicated PostgeSQL database for that, which will require its own set of security enhancements that I won’t be covering here. Alternatively you could trust the AWS or Google engineers with running the database for you and use RDS/Cloud SQL for that matter.

Anyhow, let’s enable the vaultwarden service:

v4u1t# rcctl enable vaultwarden

The configuration for Vaultwarden is stored in /var/vaultwarden/.env. You can adjust the file to your needs, however, here are a few values that should be configured specifically this way:

DOMAIN=https://v4u1t.lan
WEBSOCKET_ENABLED=true
WEBSOCKET_ADDRESS=127.0.0.1
WEBSOCKET_PORT=3012

Use the DOMAIN for which you have generated a certificate. We can now start Vaultwarden:

v4u1t# rcctl start vaultwarden

relayd

Next we set up relayd as a reverse proxy for Vaultwarden, which takes care of handling SSL termination. While Vaultwarden could handle SSL on its own, I suggest using relayd for this purpose. We’re going to use the following configuration under /etc/relayd.conf:

table <vaultwarden-default-host> { 127.0.0.1 }
table <vaultwarden-websocket-host> { 127.0.0.1 }

http protocol vaultwarden-https {
  match request header append "X-Real-IP" value "$REMOTE_ADDR"
  match request header append "Host" value "$HOST"
  match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
  match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
  match request path "/*" forward to <vaultwarden-default-host>
  match request path "/notifications/hub" forward to \
    <vaultwarden-websocket-host>

  match request path "/notifications/hub/negotiate" forward to \
    <vaultwarden-default-host>

  tcp { nodelay, sack, backlog 128 }

  tls keypair vaultwarden
  tls { no tlsv1.0, ciphers HIGH }

  http websockets
}

relay vaultwarden-https-relay {
  listen on egress port 443 tls
  protocol vaultwarden-https
  forward to <vaultwarden-default-host> port 8000
  forward to <vaultwarden-websocket-host> port 3012
}

In /etc/rc.conf.local change relayd_flags to `` (empty). Then launch relayd:

v4u1t# rcctl enable relayd
v4u1t# rcctl start relayd

Further Hardening

We’ve already performed the first few steps in hardening our OpenBSD machine, by enabling hard disk encryption, combined pubkey + password + TOTP authentication and proper configuration of the OpenSSH server. Next we’re going to take a look at a few other areas.

First, enable accton to have system accounting information available in /var/account/acct:

v4u1t# rcctl enable accounting

Next, add bin paths to OpenBSD’s security(8) checks:

v4u1t# for pth in /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin;\
  do mtree -cx -p "$pth" \
  -K sha256digest,type > /etc/mtree/$(printf "%s" "$pth" | tr '/' '_').secure;\
  done; chown root:wheel /etc/mtree/_*.secure && chmod 600 /etc/mtree/_*.secure

Let’s adjust /etc/login.conf even further and make the default umask a little more strict for the future. The login.conf should now contain the following changes:

43c43
< 	:umask=022:\
---
> 	:umask=027:\
116a117,120
>
> totp:\
>         :auth=-totp-and-pwd:\
>         :tc=default:

Feel free to also adjust the permission on existing $HOME folders.

Next, check netstat -na -f inet | grep LISTEN and make sure only port 443 (HTTPS), port 80 (HTTP, in case you use Let’s Encrypt certificates), and your SSH port are bound to your system’s public/external IP. Everything else should only be listening on 127.0.0.1.

PF

Now, set up the firewall:

##########
# Macros #
##########
minefield="1024:9999"
ssh_alternate_port=31231

##########
# Tables #
##########
table <bruteforce> persist
table <troublemakers> persist

###########
# Options #
###########
set skip on lo

#########
# Rules #
#########
block return    # Block stateless traffic
pass            # Establish keep-state

# Block brute-forcers
block quick proto tcp from <bruteforce> \
  to (egress) port $ssh_alternate_port
block quick proto tcp from <troublemakers> \
  to (egress) port $ssh_alternate_port

# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp \
  to port 6000:6010

# Port build user does not need network
block return out log proto {tcp udp} \
  user _pbuild

# Connections that made it through to the SSH port but abusing it
pass proto tcp from any to (egress) \
  port {$ssh_alternate_port} \
  flags S/SA keep state \
  (max-src-conn 5, max-src-conn-rate 5/5, \
  overload <bruteforce> flush global \
  )

# Port 22 is a dead-giveaway 
# because we know we moved our SSH server
# elsewhere. Anyone poking there is up to no good.
pass in on egress proto tcp \
  from any \
  to (egress) port { \
    telnet, \
    ssh, \
    netbios-ns, \
    netbios-ssn, \
    microsoft-ds \
  } \
  synproxy state \
  tag trouble

# Tag stuff in the range $minefield as trouble ...
pass in on egress proto tcp from any to (egress) port $minefield \
  synproxy state \
  tag trouble

# ... unless it's to our alternate port
pass in proto tcp \
  from any \
  to (egress) \
  port $ssh_alternate_port \
  tag good

# ... or HTTP/HTTPS
pass in proto tcp \
  from any \
  to (egress) \
  port { 80 443 } \
  tag good

# Otherwise add to the troublemakers
pass proto tcp from any to (egress) port $ssh_alternate_port \
  tagged trouble \
  synproxy state \
  (max-src-conn 1, max-src-conn-rate 1/10, \
  overload <troublemakers> flush global \
  )

Feel free to extend the rules to your liking. For further insights on how to manage the state and tables, see this article on undeadly.

Log Forwarding

The (free) Grafana Cloud offers a logs service based on Loki. We can make use of that to forward our machine’s logs to Grafana, so that we could set up notifications based on specific log events. Here’s an example of how to set up log forwarding using promtail:

v4u1t# pkg_add loki-promtail
v4u1t# cat /etc/promtail/promtail-config.yaml
server:
  http_listen_port: 0
  grpc_listen_port: 0

positions:
  filename: /var/promtail/positions.yaml

clients:
  - url: https://xxx@logs-prod-eu-west-0.grafana.net/loki/api/v1/push

scrape_configs:
- job_name: system
  static_configs:
  - targets:
      - localhost
    labels:
      job: varlogs
      __path__: /var/log/*log

v4u1t# rcctl enable promtail
v4u1t# rcctl start promtail

You may as well add additional files (outside of /var/log/*log) to promtail. Consider however that logs might contain somewhat sensitive info and decide for yourself whether you’d want to trust Grafana Cloud with that.

For further insights into how to process the logs, check the Loki and Grafana (Cloud) documentation.

Further Enhancements

In the Vaultwarden wiki you can find more info on how to secure your installation, especially if you opted for a public-facing service. Using an alternate base dir is one option, for example. Also make sure to enable 2FA for all Bitwarden accounts on the system and, if not needed, disable functionalities like the Send feature.

Backups are another important topic that I haven’t covered here. Make sure to have a solid backup strategy in place; the Vaultwarden wiki names a few options here as well.

Appendix

Raspberry Pi 4B running OpenBSD and Vaultwarden

Cloud Password Manager Security Issues


Enjoyed this? Support me via Monero, Bitcoin, Lightning, or Ethereum!  More info.