The Bitwarden Password Manager - Host Your Own Server

Categories: Security, Linux

Overview

This article looks at the Bitwarden online password manager - why it is useful, and how to run it on your own server infrastructure as an alternative to using a hosted service.

Important: if you do install Bitwarden locally, then please make a donation to the original developers - the developer 8bit Solutions is a very small (possible one-person) company. If you are not paying for a web-based subscription, then a contribution is appropriate.

The Problem

We all need to log on to multiple sites around the internet.

For a few people, “single sign on” with a well-known organisation works; eg once you have a google login, many other sites offer “log in via google” option. However that provides a lot of undesirable information to your central credential provider. And not all sites support such integration.

The alternative is to manage multiple passwords, as reusing the same password in multiple places is a bad idea. However those passwords soon get difficult to keep track of - the best solution is to have some central database of passwords somewhere.

Most web browsers offer a password database, and provide nice integration with websites - autofilling login fields, saving passwords when we create new accounts somewhere, and updating the password database entries when we change passwords via HTML forms. However that only works when using that same browser on that same machine - and needs to be backed up. Browsers have started to offer “password sync” so that the same passwords are both backed up and available from the same browser on other machines - but:

  • you are required to sign up for the sync service
  • using a remote service might provide undesirable info to the provider of that service, depending on the implementation (Mozilla’s “firefox sync” is currently ok, not sure about others)
  • it is not portable across browsers
  • doesn’t help with credentials for things other than web-sites
  • and raises the risk of all your passwords being stolen at once (again, dependent on implementation)

Browser-based password storage is also vulnerable to security bugs - browsers are complex applications with a poor history of security holes.

An alternative to letting the browser store passwords is to install a password manager application locally on your machine. This makes credentials available for more than websites, but:

  • is less convenient to use (need to regularly copy-and-paste between browser and password manager app)
  • is not cross-machine
  • and needs to be backed up

At least these dedicated apps are more secure than browsers, being much simpler and less exposed to the internet.

Various “hosted password repos” exist (lastpass etc) which allow credentials to be stored “in the cloud”. They get backed up, are available on any machine and not just from browsers. Typically these providers also provide browser-plugins to make saving/updating credentials easy. However this puts a lot of trust in the repo provider. These services are also subscription-based.

Bitwarden is one of these “hosted password repos”, but one with several advantages that are discussed in the next section.

About Bitwarden

Bitwarden is a service for hosting credentials, similar to the “hosted password repos” described above, but with a difference:

  • it is completely open-source (both client and server side)
  • the protocol is documented (at least indirectly; people have read the code and written documentation for it)
  • the protocol ensures the server never sees unencrypted credentials (client code does encryption/decryption)

The bitwarden site is very professional, as are the (wide) selection of available client software. The company backing it may be small, but their work seems very good.

The bitwarden code can be divided into two parts:

  • server-side
  • client-side

The server-side code consists of a single application, providing a REST api for reading/writing credentials.

The client-side code can be divided into:

  • native graphical and commandline desktop apps for various OSes (linux, mac, windows)
  • cross-platform commandline app
  • web-based desktop app (known as “vault”)
  • mobile apps
  • browser plugins

The desktop apps allow users to configure their “account”, save/search/view credentials, and various other options. These apps make REST calls to the server component to actually read/write credentials. These apps are available in both graphical and commandline versions.

The cross-platform commandline app is a nodejs-based tool that provides the same functionality as the native desktop apps, but does not offer a GUI. As with all other desktop apps, it performs encryption/decryption locally and makes calls to the central REST server to persist data.

The web-based desktop app is a pure javascript “single page app” that provides the same functionality as the native GUI desktop apps, and also makes REST calls to the server component in the same way. The javascript, html, etc. is served by the server-side component (acting as a simple HTTP server of static files). Note that there is no logic on the web-server-side; the server simply provides the javascript files which are then executed client-side within the user’s browser.

Browser plugins provide “auto-fill” and “auto-save” integration by calling the REST api of the bitwarden server (whichever address and port is configured). The Firefox browser plugin is cross-platform; not sure about plugins for other browsers.

The official server-side rest server from bitwarden.com is implemented in C# and relies on Microsoft-SQLServer as a database. The Bitwarden developers have (somehow) created docker containers which wrap these and can be executed on Linux. However these containers are very resource-intensive. The original developers probably do not care about resources - they use these containers to run their bitwarden.com hosting operation for over 100 thousand customers - and thus requiring a large database is not a significant problem. They kindly offer the containers for others to use - but don’t care that this approach is not convenient for people wanting to host an instance for a small number of users (possibly one).

Fortunately, various people have implemented alternative less-resource-intensive versions of the server-side rest component, rubywarden, bitwarden-go and bitwarden-rs. The bitwarden-rs (Rust-based) implementation seems to be the most active and the least resource-intensive. The remainder of this article looks at how to install this on Linux.

Note: the “team” and “enterprise” accounts offered for the official hosted service have a few extra features (groups, audit-logs, etc). Whether these are available in the self-hosted variant depends on how complete the “back end” implementation is. The bitwarden_rs implementation (discussed later) appears to implement all of the commercial features.

Using the Hosted Service

Before looking at installing a bitwarden server, it is worth looking at the hosted offering available from bitwarden.com. They offer truly free accounts for single users - with all necessary features. And what is special about bitwarden is that all data is encrypted before being sent to the hosted service - and thus you do not need to worry about hacks of the central service, or even dishonest sysadmins, as long as:

  • you use a reasonable master password
  • and the client app you are using has not been modified

Paid plans are available for “family” and “business” use. The most important feature of these plans is the ability to share a password across multiple accounts - updating it from one account will update it in all accounts, even though the central service does not know the master passwords of any account.

The prices are extremely reasonable. Currently:

  • personal account, including sharing with 1 other user: free
  • family account, with up to 5 users: $1/month
  • team account, with up to 5 users: $5/month (per account, not per user!). Extra users: $2/month.
  • enterprise account: $3/user/month.

Despite the good security design of Bitwarden, I still like the idea of hosting my passwords myself - so on to the rest of the article.

About the Bitwarden-rs alternative implementation

As noted above, the standard back-end (rest server) implementation uses c# and sqlserver, and is distributed as a set of docker containers that have relatively high resource requirements. This is not very friendly for self-hosting, at least for small-scale use.

The bitwarden_rs project reimplements the back-end in a simpler manner. Bitwarden_rs uses the SQLite embedded database, ie no separate “database server” process is needed and data is stored in a single file on the server local filesystem. Bitwarden-rs is a single process that listens for HTTP requests on a single port, responding to REST api URLs. It can also act as a simple static HTTP web server in order to serve the static files that make up the “vault” web-based client application, if configured to do so.

According to its author, bitwarden_rs requires only 10MB of ram.

Requirements

The bitwarden server should be accessible only over https; this is particularly important when serving the “vault” web client app, as any attacker who can modify that code effectively has access to passwords.

bitwarden_rs may be configured directly with appropriate HTTPS certificates, or it may be configured to listen on a local port with a reverse-proxy-server on the same machine terminating https requests and forwarding them as http to bitwarden_rs. I personally use nginx already, and so use it as a front-end to bitwarden.

When using https, a certificate is required. One can be obtained for free via “letsencrypt”. HTTPS also requires that the host has a suitable DNS name.

Bitwarden_rs should of course be started via systemd, and run using a dedicated user account.

Installing via Docker

The bitwarden-rs site describes how to run the code in a docker container. If you have enough resources for this, then it is definitely the easiest solution - and just follow the website.

I’m running a very low-resource (cheap) VPS for my personal use (this website, email, and a few other things) - and thus prefer to install directly (see next section).

Installing without Docker

The code is not available as a prebuilt download; it is necessary to build from source. However that is not too hard.

There are reasonable instructions for building bitwarden_rs; however they primarily show how to build the app into a docker container. As the server is just a single binary application, I don’t see the need for docker here; it is simple enough to just run that binary directly. The process for doing that is also documented on the bitwarden_rs site, but not so clearly. Hopefully the instructions below are easy to follow for this use-case.

The following build-steps can be performed as any standard user on the host system. Sudo rights will be needed to actually install the results.

Install prerequisites

apt install git
apt install libssl-dev
apt install pkg-config 

# necessary only if building "vault" web interface
apt install nodejs # ? maybe not necessary ? or maybe pulled in by npm ?
apt install npm

Build Vault Web Client (optional)

As noted earlier, the Vault web client is optional. If all users are content to install native desktop clients to interact with Bitwarden, then there is no need to build the “vault” component.

Vault is implemented using Microsoft Typescript, and various other technologies which require the “source code” to be processed to generate javascript/html/css/etc. The resulting javascript is also compressed (minimised) in the usual way. There is therefore a “build” process necessary, even though the result is a set of static files which the bitwarden rest-server simply provides to the user on request. Once loaded into the user’s browser, the code acts just like any other Bitwarden desktop client application.

Although the build process uses npm and nodejs, they are not needed at runtime.

Clone the standard bitwarden “web vault” repo:

  • git clone https://github.com/bitwarden/web.git
  • cd web
  • git tag
  • git checkout ... # most recent tag

Build:

  • npm run sub:init
  • npm install
  • npm install @types/source-map@0.5.2 # required at least in vault v2.5.0 (see below)
  • npm run dist

Vault currently depends on the “source-map” package, v0.5.2. This appears to have a packaging bug (fixed in 0.5.7 and later), which leads to a build failure. The workaround is to explicitly install the missing source-map type definitions (see above). Note: source-map is a module from Microsoft for dealing with the TypeScript language :/)

Build the Rust-based Alternative Server

Install rustc/cargo if you don’t already have it:

  • curl https://sh.rustup.rs -sSf | sh

Clone the bitwarden_rs repo (choose latest tag):

  • git clone https://github.com/dani-garcia/bitwarden_rs.git
  • git tag
  • git checkout ... # most recent tag

Build:

  • cargo build --release

Configure server

File “.env” in the root checkout of bitwarden_rs defines all the available config options. Paths are relative.

Warning: if you enable “account creation enabled” then anyone on the internet who can reach the vault site (or the appropriate REST endpoint) can create an account. When this is disabled, users can only create an account when “invited” by an existing user (sadly, UI is not so elegant; everything works until the last step, then fails). AFAICT, the setup process when you don’t want to host a fully open server is to start the server with account-creation enabled, quickly create at least a “server admin” user (see later for SERVER_ADMIN_EMAIL), and then restart the server with this config option turned off. Then as the admin user, invite users to the “admin organisation”; they can then use the “create account” button with whatever email-address you specified, and their own chosen password. There is a slight security hole here AFAICT, in that anyone can create an account as an invited-but-not-yet-created user if they know the email address.

See the bitwarden_rs README.md for more information on config options. This page also includes a section on “differences from upstream implementation”; in particular:

  • users “invited” by existing users can use the “create account” button even when “account creation enabled” config option is disabled (other users get an error message at the last step)

Optionally test it now:

  • target/release/bitwarden_rs

Notes:

  • defaults to running on localhost:8000, ie not accessible over internet
  • it appears possible to set the app up to listen on an external interface and use https (see file .env), but the best option is probably to use a reverse proxy to forward to the local daemon, eg nginx.

Deploy the app

Rather than run it from where it was built, it seems best to put it somewhere “official”.

# choose user and install-dir
USER=bitwarden
DEST=/var/lib/bitwarden

# create user for bitwarden to run as
sudo adduser --system --no-create-home --disabled-login $USER 

# move required files to $DEST
sudo mkdir $DEST
sudo cp -r target/release/bitwarden_rs $DEST
sudo cp .env $DEST
sudo mkdir $DEST/data
sudo cp ../web/build $DEST/web-vault   # -- only if you have built Vault earlier, and have enabled it in the .env file
sudo chown -R $USER $DEST

Configure nginx reverse proxy

This assumes you have used letsencrypt to generate a certificate (eg into /etc/letsencrypt/live/{domain}/fullchain.pem)

sudo cat > /etc/nginx/sites-available/bitwarden.your.domain <\<EOF
server {
  # SSL configuration
  listen 8443 ssl http2;
  listen [::]:8443 ssl http2;
  ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
#	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#	ssl_ciphers HIGH:!aNULL:!MD5;

  server_name your.domain;

  location / {
    proxy_pass http://localhost:8000/;
  }

  # websockets support
  location /notifications/hub {
    proxy_pass http://localhost:3012;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
  
  location /notifications/hub/negotiate {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://localhost:8000;
  }
}
EOF

Running on localhost using http theoretically allows local attackers to make requests to the server, but in practice that seems a very small vulnerability as bitwarden does not store any unencrypted data - it never has any master keys itself. If you have a different opinion, please let me know in the comments.

For configuration examples for other reverse-proxies, see file PROXY.md in the bitwarden_rs git repo.

As noted in bitwarden_rs documentation, the use of a separate port for websockets is due to current limitations in the Rocket library; this will not be necessary in some future version.

The proxy_set_header commands are primarily to ensure that when bitwarden_rs logs authentication failures, then the IP address it logs is the real external one, and not localhost. This allows fail2ban to be used (see later).

Configure Firewall

If you are using Ubuntu (or other distro that runs the ufw firewall by default), don’t forget to:

sudo ufw allow 8443

Set up Systemd

Ensure the bitwarden daemon starts on server boot.

sudo cat > /etc/systemd/system/bitwarden.service <\<EOF
# See https://www.freedesktop.org/software/systemd/man/systemd.unit.html for details
[Unit]
Description=Bitwarden server
After=network.target auditd.service

[Service]
User=bitwarden
Group=nogroup

# todo: use RootDirectory to jail the app
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/bitwarden/data
PrivateTmp=true

Environment=SERVER_ADMIN_EMAIL=bwadmin@your.domain
WorkingDirectory=/var/lib/bitwarden 
ExecStart=/var/lib/bitwarden/bitwarden_rs
Restart=on-failure
RestartPreventExitStatus=255
Type=simple

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable bitwarden.service --now
sudo journalctl --unit=bitwarden.service

The SERVER_ADMIN_EMAIL variable defines an address which you can use with “create account” even when configuration has disabled account-creation. That means you should use some bitwarden client app to create this account (set initial password) immediately after starting the server. This account-id should only be used for administration purposes - and to invite new users when account-creation has been disabled.

Using Fail2Ban

The fail2ban monitoring app can be used to prevent attackers from making guessing attacks on the Vault login page (or bitwarden-rs rest-service login endpoint); login failures are written to a logfile and fail2ban can be set up to scan this logfile, detect the failures, and update the local firewall to (temporarily) block the source IP address from which the login failures came. The temporary point is important in case you accidentally make a few incorrect login attempts yourself!

cat >>/etc/fail2ban/filter.d/bitwarden.conf <\<EOF
# Fail2Ban filter for Bitwarden
# Detecting unauthorized access
# Typically logged in /var/log/messages syslog
# Message format to match: 
#   somedate somehost bitwarden_rs[somepid]: ERROR: Username or password is incorrect. Try again. IP: 1.2.3.4. Username: no@body.

[INCLUDES]
# Read syslog common prefixes
before = common.conf

[Definition]
_daemon = bitwarden_rs
failregex = ^%(__prefix_line)sERROR: Username or password is incorrect. Try again. IP: <HOST>\. Username: \S+.\s*$
ignoreregex =
EOF

cat >>/etc/fail2ban/jail.d/local.conf <\<EOF
[bitwarden]
enable=true
logpath=/var/log/syslog
EOF

systemctl restart fail2ban
tail -f /var/log/fail2ban.log # and now do some incorrect logins...

Unfortunately, syslog by default converts multiple identical messages occurring within a short time-period into the following form:

  • {prefix}: message repeated 5 times: [ ERROR: Username or password is incorrect. Try again. IP: 1.2.3.4. Username: no@body.]

The only current solution for working with fail2ban is to disable this syslog option:

  • sudo sed -i 's/RepeatedMsgReduction\ on/RepeatedMsgReduction\ off/' /etc/rsyslog.conf
  • systemctl restart rsyslog

The Client/Server Protocol

TODO: write some info on how Bitwarden saves passwords and interacts with clients.

The Browser Plugin

Plugins are available for Firefox, Chrome and Safari. These integrate with the browser to auto-save and auto-fill passwords. When installed, a bitwarden icon appears next to the nav-bar in the browser; clicking on this gives access to the same features available in the bitwarden desktop client apps. As in the desktop apps, the “gear” icon gives access to the config settings - including setting the path to the bitwarden server.

The built-in password manager in many browsers uses pop-up windows to ask whether to save a new password, etc. - bitwarden instead modifies the HTML to add a prompt at the top of the page. While elegant, I have had problems with one site where the login page keeps refreshing (multiple times per second) until this bar is “dismissed” - I suspect that javascript in this page acts badly when bitwarden changes the page dom.

Notes

When you “log in” to vault (or any other client), you are prompted to provide your “master password”. However actually this password never leaves your browser; the login page uses complex javascript to compute a hash of the master password which verifies your identity but is not sufficient to decrypt any secrets you have stored in bitwarden. This is in effect “log in with password” where the login-pwd can be derived from the master-pwd but not the other way around.

Bitwarden supports both passwords and TOTP codes. In fact, any entry consists of (user-email, encrypted-url) => (set of encrypted properties), where the properties can be anything you need.

There is no need to take particular care when backing up the credentials DB - that is fully encrypted with keys that only the client apps have.

The user interface includes a number of options like “number of iterations for PBKDF”, etc. These options are stored in the central repository so they are available to each client, but are implemented within the client itself.

The Vault web interface is nicely implemented, and elegant to use.

All entries in the repository can be exported if desired, ie there is no “lock in”. Import is supported for the main password manager formats. There is also a “password generator” available.

Sharing of passwords between multiple users is done by defining an “organisation” and then linking multiple users to the same organisation. Each “organisation” effectively has its own vault, and its own copy of the relevant data. Each user who is a “member” of the organisation effectively has access to the “master password” of that organisation. There is a complex structure of rights that an “organisation admin” can configure for users.

The Vault interface for creating organisations lists various free/paid plans. However the bitwarden_rs implementation supports all enterprise features for all organisation types - so when using bitwarden_rs as server, just choose the “free” tier. Fixing this UI quirk would mean forking the Vault code, which is not worth the effort.

There is a command-line tool for interacting with the Bitwarden server; this is implemented in javascript and can be installed with the nodejs package-manager “npm”.

Each item stored has:

  • a “name” (descriptive name for showing in the interface)
  • a “url” (can be searched for)
  • the username for the service to be logged into
  • the password itself, and/or a TOTP code
  • a free-text notes field
  • zero or more “custom fields” associated with the entry
  • a “folder” for organising (grouping) entries

Searches for entries is done client-side, not server-side: the entire repo is “synced” to the local machine and then a search is done locally. Modifications to data include a synchronous update of the central data.

Bitwarden code manages master passwords responsibly; when you provide such a password to any bitwarden app, it immediately derives some other more temporary/limited token from that password and then erases the original master password from memory. In particular, communication with the REST service is done via a “session key” which has a limited time duration; after this has expired the account is considered “locked” and a new session key must be created (by a client app, using the master password). Login is a somewhat different process from a session key - it authenticates against the bitwarden server.

The fact that the rest-server does not actually have access to any credentials means that the fact that bitwarden_rs is not from a well-known source is not a major concern.

Bitwarden server uses WebSockets to notify client applications of changes. The bitwarden_rs server itself provides WebSocket support on a different port than its REST services (3012 by default) - though this different port is not visible if using a reverse_proxy in front (eg the nginx config shown above). This means that client apps connect to bitwarden rest server on the relevant port and stay connected for long periods of time. This functionality is not critical, eg the CLI client does not do this.

Bitwarden’s standard server can interact with LetsEncrypt to allocate certificates automatically; however this requires that it runs on ports 80/443 on its host. Certificate renewal occurs only each time the server is started, ie it is necessary to restart the server at least every 90 days (duration of a letsencrypt certificate). It isn’t clear whether the bitwarden_rs implementation also supports this.