Add Vaultwarden note
This commit is contained in:
parent
04001b0e18
commit
2a879f4b86
146
content/notes/vaultwarden.md
Normal file
146
content/notes/vaultwarden.md
Normal file
@ -0,0 +1,146 @@
|
||||
---
|
||||
date: "2023-03-24T20:08:00Z"
|
||||
title: Vaultwarden
|
||||
description: "Self-hosting a Bitwarden-compatible Vaultwarden back-end."
|
||||
tags: [technology, development, selfhost]
|
||||
---
|
||||
|
||||
I self-host a [Vaultwarden](https://github.com/dani-garcia/vaultwarden) instance to manage my usernames, passwords, two-factor codes, and other details for my accounts everywhere.
|
||||
|
||||
Standard [Bitwarden](https://bitwarden.com) clients (including browser extensions and mobile apps) can use Vaultwarden instances as their backend server.
|
||||
|
||||
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.
|
||||
|
||||
```docker
|
||||
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](https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93) to export my Authy tokens. The resultant JSON file can be directly imported into Vaultwarden/Bitwarden.
|
||||
|
||||
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](http://localhost:5858) in your browser (e.g. Firefox)
|
||||
1. In the dev tools console, copy and paste the below code, and a JSON file will be downloaded for you
|
||||
|
||||
```javascript
|
||||
// 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,
|
||||
});
|
||||
```
|
Loading…
Reference in New Issue
Block a user