💾 Archived View for wilw.capsule.town › notes › vaultwarden.gmi captured on 2023-11-14 at 08:09:06. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-04-19)

➡️ Next capture (2024-08-18)

-=-=-=-=-=-=-

🏡 Home

Back to notes

Vaultwarden

Last updated on 24 March 2023

I self-host a Vaultwarden [1] instance to manage my usernames, passwords, two-factor codes, and other details for my accounts everywhere.

1

Standard Bitwarden [2] clients (including browser extensions and mobile apps) can use Vaultwarden instances as their backend server.

2

In this note I describe my particular setup.

Notes

- I access Vaultwarden through Tailscale. Even though this is encrypted as-is, Bitwarden clients require use of TLS. As such, we need to use a reverse proxy. In my case, I use Traefik, as this has a handy Tailscale plugin for provisioning and renewing certificates.

- If you don't use Tailscale, then adapt the instructions below accordingly.

Setup

To begin, set-up a new `docker-compose.yml` file that includes the Vaultwarden and reverse-proxy containers.

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    restart: unless-stopped
    volumes:
      - ./data:/data
    expose:
      - 80
    labels:
      - traefik.http.routers.vault.rule=Host(`HOST.TAILSCALE_DOMAIN.ts.net`)
      - traefik.http.routers.vault.tls.certresolver=myresolver

  reverse-proxy:
    image: traefik:3.0
    restart: unless-stopped
    command:
      - "--providers.docker"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.tailscale=true"
    ports:
      - "HTTPPORT:80"
      - "HTTPSPORT:443"
    volumes:
      - ./acme:/etc/traefik/acme
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock
    

Change `HTTPPORT` and `HTTPSPORT` in the Traefik configuration to the ports you wish to access Vaultwarden over, and change `HOST` to your Tailscale machine's hostname, and the `TAILSCALE_DOMAIN` to the Tailscale Magic DNS name for your Tailnet.

Bring the containers up (`docker-compose up -d`) and you can access Vaultwarden on `https://HOST.TAILSCALE_DOMAIN.ts.net:HTTPSPORT`.

Usage

Create an account on the Vaultwarden UI. After this point you can just use the Bitwarden clients (mobile apps and browser extensions) to access and manage your passwords, etc.

When logging-in with a Bitwarden client, use the option to select an alternative server and enter your Vaultwarden address.

Two-Factor Authentication

Vaultwarden fully supports 2FA also, and the Bitwarden mobile app includes a camera for scanning 2FA setup QR codes.

Exporting from Authy

I previously used Authy as my 2FA system, but wanted to move my tokens into my Vaultwarden instance.

I used the (very helpful) instructions and Gist here [3] to export my Authy tokens. The resultant JSON file can be directly imported into Vaultwarden/Bitwarden.

3

For archival purposes, I copy the instructions below:

1. Close Authy on the desktop, and re-open using `open -a "Authy Desktop" --args --remote-debugging-port=5858` (this works for a Mac, use the command relevant to your system)

1. Open http://localhost:5858 [4] in your browser (e.g. Firefox)

4

1. In the dev tools console, copy and paste the below code, and a JSON file will be downloaded for you

// COPYRIGHT gboudreau (github.com/gboudreau)

// Based on https://github.com/LinusU/base32-encode/blob/master/index.js
function hex_to_b32(hex) { let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let bytes = []; for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substr(i, 2), 16)); } let bits = 0; let value = 0; let output = ''; for (let i = 0; i < bytes.length; i++) { value = (value << 8) | bytes[i]; bits += 8; while (bits >= 5) { output += alphabet[(value >>> (bits - 5)) & 31]; bits -= 5; } } if (bits > 0) { output += alphabet[(value << (5 - bits)) & 31]; } return output; }

// from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid#answer-2117523
function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }

// from https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93
function saveToFile(data, filename) { if (!data) { console.error('Console.save: No data'); return; } if (typeof data === "object") { data = JSON.stringify(data, undefined, 4) } const blob = new Blob([data], { type: 'text/json' }); const e = document.createEvent('MouseEvents'); const a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); }

function deEncrypt({ log = false, save = false }) {
  const folder = {
      id: uuidv4(),
      name: 'Imported from Authy'
  };

  const bw = {
      "encrypted": false,
      "folders": [
          folder
      ],
      "items": appManager.getModel().map((i) => {
          let secretSeed = i.secretSeed;
          if (typeof secretSeed == "undefined") {
              secretSeed = i.encryptedSeed;
          }
          const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(secretSeed));
          const period = (i.digits === 7 ? 10 : 30);

          const [issuer, rawName] = (i.name.includes(":"))
              ? i.name.split(":")
              : ["", i.name];
          const name = [issuer, rawName].filter(Boolean).join(": ");
          const totp = `otpauth://totp/${name}?secret=${secret}&digits=${i.digits}&period=${period}${issuer ? '&issuer=' + issuer : ''}`;

          return ({
              id: uuidv4(),
              organizationId: null,
              folderId: folder.id,
              type: 1,
              reprompt: 0,
              name,
              notes: null,
              favorite: false,
              login: {
                  username: null,
                  password: null,
                  totp
              },
              collectionIds: null
          });
      }),
  };

  if (log) console.log(JSON.stringify(bw));
  if (save) saveToFile(bw, 'authy-to-bitwarden-export.json');
}

deEncrypt({
  log: true,
  save: true,
});

Back to notes