Octelium documentation · Latest

Self-Hosting Vaultwarden Password Manager

Vaultwarden is a popular open source, lightweight server implementation of the Bitwarden API written in Rust that is compatible with the official Bitwarden clients, including the browser extensions, the desktop and mobile apps, and the CLI. This guide shows you how to deploy Vaultwarden via Helm on the same underlying Kubernetes cluster that is running the Octelium Cluster, and then serve it via Octelium Services.

A password manager has a special access pattern. The Bitwarden clients need to reach the server directly from anywhere, and they cannot go through the Cluster's browser-based login. On the other hand, the vault itself is end-to-end encrypted where the master password never leaves the client, while the Vaultwarden admin panel, which manages users and server settings, is the most sensitive part of the server. Therefore, in this guide Octelium provides the following:

  • A public anonymous Service (read more here) that operates as a web application firewall (WAF) and serves the Bitwarden clients from anywhere, while completely blocking the admin panel.

  • A separate Service for the admin panel which is only accessible by the members of an admins Group after authenticating to the Cluster via OpenID Connect, SAML 2.0 or GitHub IdentityProviders (read more here) as well as Passkeys.

  • OpenTelemetry-native, L7 aware visibility and auditing of every request, including the blocked ones (read more here).

note

If you installed the Cluster via the quick installation guide, you can simply use the command export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" in your Cluster VM/VPS before running the kubectl and helm commands below.

Install Vaultwarden

Vaultwarden does not publish an official Helm chart. In this guide we use the vaultwarden Helm chart which is the chart recommended by the Vaultwarden wiki for Kubernetes deployments.

Create the Admin Token

The admin panel is protected by an admin token. Instead of storing a plaintext token, Vaultwarden can generate an Argon2id hash of a password of your choice. First, create a Kubernetes namespace for Vaultwarden and then generate the hash via a temporary pod as follows:

kubectl create namespace vaultwarden kubectl run vaultwarden-hash -n vaultwarden --rm -it --restart=Never --image=vaultwarden/server:1.37.3-alpine -- /vaultwarden hash --preset owasp

After entering your password twice, the command outputs the Argon2id PHC string, which starts with $argon2id$. Now store that string in a Kubernetes secret as follows:

kubectl create secret generic vaultwarden-admin -n vaultwarden --from-literal=ADMIN_TOKEN='<ARGON2_PHC_STRING>'
note

Make sure to use single quotes around the PHC string as shown above, since it contains $ characters that your shell would otherwise try to expand.

Install the Chart

Now we create a values.yaml file for the chart (see the chart here) as follows:

image: tag: "1.37.3-alpine" domain: "https://vault.<DOMAIN>" signupsAllowed: false invitationsAllowed: true adminToken: existingSecret: vaultwarden-admin existingSecretKey: ADMIN_TOKEN storage: data: name: vaultwarden-data size: 2Gi

The domain value must be the public URL that the Bitwarden clients use, which is the public URL of the vault Service that we create below. We disable open signups so that only the users that you invite via the admin panel can create accounts. By default, Vaultwarden uses a SQLite database that is stored, along with the attachments, in the vaultwarden-data persistent volume claim.

Now install the chart and wait for Vaultwarden to become ready as follows:

helm repo add vaultwarden https://guerzon.github.io/vaultwarden helm repo update helm install vaultwarden vaultwarden/vaultwarden --version 0.46.2 --namespace vaultwarden -f /PATH/TO/VALUES.YAML kubectl rollout status statefulset/vaultwarden -n vaultwarden --timeout=10m

The chart creates the vaultwarden Kubernetes service in the vaultwarden Kubernetes namespace which listens over the port 80.

note

You can create a consistent backup of the SQLite database at any time via the command kubectl exec -n vaultwarden vaultwarden-0 -- /vaultwarden backup. The backup file is created inside the data volume, from which you can copy it out via kubectl cp. Read more about backing up Vaultwarden in the Vaultwarden wiki here.

Create the Services

Both of the following Services use the same vaultwarden Kubernetes service as their upstream.

Public Service for Bitwarden Clients

We create a public anonymous vault Service with anonymous authorization enabled (read more here). In this mode, the Service does not authenticate the requests, but it still authorizes every request according to its Policies, effectively operating as a WAF. Here we allow all requests except for the ones to the admin panel as follows:

kind: Service metadata: name: vault spec: mode: WEB isPublic: true isAnonymous: true config: upstream: url: http://vaultwarden.vaultwarden.svc authorization: enableAnonymous: true inlinePolicies: - spec: rules: - effect: ALLOW condition: matchAny: true - effect: DENY condition: match: 'ctx.request.http.path.matches("^/+admin(/|$)")'

Since DENY rules override ALLOW rules of the same priority (read more here), any request to the admin panel is denied with a 403 status code before it ever reaches Vaultwarden. The regular expression also matches paths with repeated leading slashes such as //admin, which some web servers treat exactly like /admin.

note

In the anonymous mode, the Service passes the downstream's Authorization request header as is (read more here). This is required since the Bitwarden clients authenticate to Vaultwarden via Bearer tokens in that header.

Admin Service

Now we create a separate vault-admin Service for the admin panel. Unlike the vault Service, this one is not anonymous and is only accessible by the members of the admins Group (read more about Groups here) as follows:

kind: Group metadata: name: admins spec: {} --- kind: Service metadata: name: vault-admin spec: mode: WEB isPublic: true config: upstream: url: http://vaultwarden.vaultwarden.svc authorization: inlinePolicies: - spec: rules: - effect: ALLOW condition: match: '"admins" in ctx.user.spec.groups'

Now add your administrator Users to the admins Group. Here is an example:

kind: User metadata: name: john spec: type: HUMAN email: john@example.com groups: ["admins"]

You can now apply the creation of the Group, both Services and the Users via the octeliumctl apply command (read more here) as follows:

octeliumctl apply /PATH/TO/YAML_FILE_OR_PARENT_DIRECTORY

Now members of the admins Group can visit the admin panel from their browsers at the URL https://vault-admin.<DOMAIN>/admin via the clientless BeyondCorp mode (read more here), or at the URL http://vault-admin/admin via the client-based mode once connected to the Cluster via the octelium connect command (read more here). Vaultwarden then asks for the password that you used to generate the admin token.

note

The Diagnostics page of the admin panel shows a No Match warning for the domain configuration since you are visiting it from a different hostname than the configured domain value. You can safely ignore that warning since the Bitwarden clients only use the vault Service.

Inviting Users

Since open signups are disabled, go to the Users page of the admin panel and invite a user via their email address. The invited user can then create their account at the web vault at https://vault.<DOMAIN> using that email address. This works even if you have not configured SMTP for Vaultwarden.

note

It is strongly recommended for every user to enable two-step login (e.g. via an authenticator app or a security key) from the Security settings of their account in the web vault.

Using Bitwarden Clients

All the official Bitwarden clients support self-hosted servers. On the login screen of the browser extension, desktop app or mobile app, choose the self-hosted environment and set the server URL to https://vault.<DOMAIN>. You can also configure the Bitwarden CLI as follows:

bw config server https://vault.<DOMAIN> bw login
note

Since the Service drops the X-Forwarded-For and X-Real-IP request headers by default (read more here), Vaultwarden sees every request as coming from the private IP address of the Cluster. Therefore, Vaultwarden's per-IP rate limiting of login attempts applies to all clients collectively, which is typically fine for a small number of users.

note

Vaultwarden also supports single sign-on via OpenID Connect through the chart's sso values. You can use the same self-hosted identity provider that you use to login to the Cluster, such as Authentik (read more here) or Keycloak (read more here). Read more about Vaultwarden's SSO support in the Vaultwarden wiki here.

Visibility

Octelium also provides OpenTelemetry-ready, application-layer L7 aware visibility and access logging in real time (read more about visibility here), including for anonymous Services. Here is an example of an access Log of a request to the admin panel that was blocked by the vault Service:

{ "apiVersion": "core/v1", "entry": { "common": { "status": "DENIED", "mode": "WEB", "reason": { "type": "POLICY_MATCH", "details": { "policyMatch": { "inlinePolicy": { "resourceRef": { "apiVersion": "core/v1", "kind": "Service", "uid": "5b1e2c7a-9d3f-4b8e-a6c1-2f7d9e0a4b13", "name": "vault.default" } } } } }, // The rest is omitted for the sake of brevity of the example }, "info": { "http": { "request": { "path": "//admin", "userAgent": "Mozilla/5.0 (compatible; scanner/1.0)", "method": "GET", "uri": "//admin" }, "httpVersion": "HTTP11" } } }, "kind": "AccessLog", "metadata": { // Omitted for the sake of brevity of the example } }

Here are a few more features that you might be interested in:

  • Self-hosting Immich for your photos and videos (read more here).

  • Self-hosting Jellyfin for your media (read more here).

  • HTTP plugins such as rate limiting and Lua scripting (read more here).

  • Application layer-aware ABAC access control via policy-as-code using CEL and Open Policy Agent (read more here).