# Self-Hosting Vaultwarden Password Manager

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/guide/service/homelab/vaultwarden-helm-self-host>.

[Vaultwarden](https://github.com/dani-garcia/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](https://octelium.com/docs/octelium/latest/management/core/service/anonymous-access.md)) 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](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md)) as well as Passkeys.
- OpenTelemetry-native, L7 aware visibility and auditing of every request, including the blocked ones (read more [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)).

> **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](https://github.com/guerzon/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:

```bash
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:

```bash
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](https://github.com/guerzon/vaultwarden/tree/main/charts/vaultwarden)) as follows:

```yaml
image:
  tag: "1.37.3-alpine"

# !mark
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:

```bash
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](https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault).

## 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](https://octelium.com/docs/octelium/latest/management/core/service/anonymous-access.md#anonymous-authorization)). 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:

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

Since `DENY` rules override `ALLOW` rules of the same priority (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md#policy-priority)), 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](https://octelium.com/docs/octelium/latest/management/core/service/http.md#authorization-header)). 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](https://octelium.com/docs/octelium/latest/management/core/group.md)) as follows:

```yaml
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:

```yaml
kind: User
metadata:
  name: john
spec:
  type: HUMAN
  email: john@example.com
  # !mark
  groups: ["admins"]
```

You can now apply the creation of the *Group*, both *Services* and the *Users* via the `octeliumctl apply` command (read more [here](https://octelium.com/docs/octelium/latest/management/core/overview.md)) as follows:

```bash
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](https://octelium.com/docs/octelium/latest/management/core/service/clientless.md)), 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](https://octelium.com/docs/octelium/latest/user/cli/connect.md)). 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:

```bash
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](https://octelium.com/docs/octelium/latest/management/core/service/http.md#forwarded-header)), 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](https://octelium.com/docs/octelium/latest/management/guide/service/homelab/authentik-helm-self-host.md)) or Keycloak (read more [here](https://octelium.com/docs/octelium/latest/management/guide/service/homelab/keycloak-helm-self-host.md)). Read more about Vaultwarden's SSO support in the Vaultwarden wiki [here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-SSO-support-using-OpenId-Connect).

## Visibility

Octelium also provides OpenTelemetry-ready, application-layer L7 aware visibility and access logging in real time (read more about visibility [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)), 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*:

```yaml
{
  "apiVersion": "core/v1",
  "entry": {
    "common": {
      # !mark(1:17)
      "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](https://octelium.com/docs/octelium/latest/management/guide/service/homelab/immich-helm-self-host.md)).
- Self-hosting Jellyfin for your media (read more [here](https://octelium.com/docs/octelium/latest/management/guide/service/homelab/jellyfin-helm-self-host.md)).
- HTTP plugins such as rate limiting and Lua scripting (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/http-plugins.md)).
- Application layer-aware ABAC access control via policy-as-code using CEL and Open Policy Agent (read more [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)).
