Vaultwarden for ALL the Passwords
Vaultwarden is an implementation of the Bitwarden API in Rust. It’s fast, lightweight, secure, and compatible with the official Bitwarden clients. It’s perfect for self-hosting a password manager for your family and a few friends. I’ve run it for over two years and it is rock solid. I support Bitwarden as well by buying a personal subscription and recommend it to my clients for their enterprise password management needs.
As this is part of the series of posts where I’m sharing how I roll out applications on Kubernetes using Ansible, I’m assuming that you are familiar with the previous post, starting with The Great Migration, which detail the underlying components and pieces that got us to this point.
Since this Ansible role uses a Kubernetes persistent volume, I’ll also remind you that I prefer to create the Longhorn volume directly in the Longhorn dashboard and reference it in the Kubernetes manifests. You could also let Longhorn provision the volume by removing that from the manifest.
A 10Gi volume called vaultwarden-vol is what I used. I’m just using the default sqlite database.
Vaultwarden role
These variable need to go in inventory/group_vars/k3s_cluster:
vaultwarden_namespace: vaultwarden
vaultwarden_image: vaultwarden/server:1.26.0
vaultwarden_hostname: vault.domain.tld
vaultwarden_vol_size: 10Gi
yubico_clientid: see Vaultwarden documentation
yubico_secretkey: see Vaultwarden documentation
roles/k3s_cluster/vaultwarden/tasks/main.yml:
- name: Vaultwarden Namespace
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/namespace.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
- name: Vaultwarden Volume
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/volume.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
- name: Vaultwarden Deployment
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/deployment.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
- name: Vaultwarden Secret
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/secret.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
- name: Vaultwarden Service
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/service.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
- name: Vaultwarden Ingress
kubernetes.core.k8s:
kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
state: present
definition: "{{ lookup('template', 'manifests/ingress.j2') }}"
validate:
fail_on_error: yes
delegate_to: "{{ ansible_host }}"
run_once: true
roles/k3s_cluster/vaultwarden/manifests/namespace.j2:
apiVersion: v1
kind: Namespace
metadata:
name: {{ vaultwarden_namespace }}
roles/k3s_cluster/vaultwarden/manifests/volume.j2:
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: vaultwarden-vol-pv
namespace: {{ vaultwarden_namespace }}
labels:
backup: daily
spec:
capacity:
storage: {{ vaultwarden_vol_size }}
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: longhorn-static
csi:
driver: driver.longhorn.io
fsType: ext4
volumeAttributes:
numberOfReplicas: "3"
staleReplicaTimeout: "2880"
volumeHandle: vaultwarden-vol
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vaultwarden-vol-pvc
namespace: {{ vaultwarden_namespace }}
spec:
accessModes:
- ReadWriteMany
storageClassName: longhorn-static
volumeName: vaultwarden-vol-pv
resources:
requests:
storage: {{ vaultwarden_vol_size }}
roles/k3s_cluster/vaultwarden/manifests/deployment.j2:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultwarden
namespace: {{ vaultwarden_namespace }}
labels:
app: vaultwarden
spec:
replicas: 1
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: {{ vaultwarden_image }}
env:
- name: SIGNUPS_ALLOWED
value: "true"
- name: SMTP_FROM
value: noreply@domain.tld
- name: SMTP_HOST
value: mail.domain.tld
- name: SMTP_PORT
value: "587"
- name: SMTP_SECURITY
value: "starttls"
- name: WEBSOCKET_ENABLED
value: "true"
envFrom:
- secretRef:
name: vaultwarden-secret
optional: false
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: bitwarden-www
protocol: TCP
- containerPort: 3012
name: bitwarden-ws
protocol: TCP
volumeMounts:
- name: vaultwarden-vol
mountPath: /data
volumes:
- name: vaultwarden-vol
persistentVolumeClaim:
claimName: vaultwarden-vol-pvc
roles/k3s_cluster/vaultwarden/manifests/secret.j2:
apiVersion: v1
kind: Secret
metadata:
name: vaultwarden-secret
namespace: {{ vaultwarden_namespace }}
type: Opaque
stringData:
SMTP_PASSWORD: "{{ smtp_password }}"
SMTP_USERNAME: "{{ smtp_username }}"
YUBICO_CLIENT_ID: "{{ yubico_clientid }}"
YUBICO_SECRET_KEY: "{{ yubico_secretkey }}"
roles/k3s_cluster/vaultwarden/manifests/service.j2:
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
namespace: {{ vaultwarden_namespace }}
spec:
selector:
app: vaultwarden
ports:
- name: http
port: 80
- name: ws
port: 3012
roles/k3s_cluster/vaultwarden/manifests/ingress.j2:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: vaultwarden
namespace: {{ vaultwarden_namespace }}
spec:
entryPoints:
- web
- websecure
routes:
- match: Host(`{{ vaultwarden_hostname }}`)
kind: Rule
services:
- name: vaultwarden
port: 80
- match: Host(`{{ vaultwarden_hostname }}`) && PathPrefix(`/notifications/hub`)
kind: Rule
services:
- name: vaultwarden
port: 3012
- match: Host(`{{ vaultwarden_hostname }}`) && PathPrefix(`/notifications/hub/negotiate`)
kind: Rule
services:
- name: vaultwarden
port: 80
Vaultwarden Playbook
k3s-vaultwarden.yml:
---
- hosts: master[0]
become: yes
vars:
ansible_python_interpreter: /usr/bin/python3
remote_user: ansible
pre_tasks:
- name: Install Kubernetes Python module
pip:
name: kubernetes
- name: Install Kubernetes-validate Python module
pip:
name: kubernetes-validate
roles:
- role: k3s_cluster/vaultwarden
Wrap up
Be sure to refer to the Vaultwarden configuration documentation to customize it to meet your needs. Most options can be configured through environment variables and it’s easy to add or modify the ones I’ve included in the Vaultwarden role. I recommend creating your initial account and then disabling sign ups. Registered users can still invite new users.