Add Vaultwarden self-hosted password vault at vault.ladkau.de

- New vaultwarden role — Vaultwarden container with SQLite storage, admin
  panel protected by token, Keycloak SSO enabled on second deploy after
  the OIDC client secret is available (SSO_ENABLED conditionally set so
  first provisioning deploy works without Keycloak being configured yet)
- Traefik routes vault.ladkau.de with lax rate limiting (SPA loads many assets)
- vault.ladkau.de added to DNS table, check-services.sh, status dashboard,
  and check-vault.sh (admin token required; SSO secret is post-provisioning)
- Configuration runbook: step 2.6 for Keycloak client, section 6 for
  Vaultwarden setup including admin panel, SSO login, and client configuration
This commit is contained in:
ml
2026-06-28 15:39:42 +02:00
parent 187c6bdea4
commit 1c36b7bbcb
10 changed files with 137 additions and 6 deletions
+8
View File
@@ -8,6 +8,7 @@ domain_sso: "sso.{{ domain_base }}"
domain_mail: "mail.{{ domain_base }}"
domain_registry: "cr.{{ domain_base }}"
domain_k8s: "k8s.{{ domain_base }}"
domain_vault: "vault.{{ domain_base }}"
# Let's Encrypt
acme_email: matthias.ladkau@gmail.com
@@ -97,5 +98,12 @@ registry_data_dir: /opt/registry
# k8s (placeholder)
k8s_data_dir: /opt/k8s
# Vaultwarden (self-hosted Bitwarden-compatible password vault)
vaultwarden_version: "latest"
vaultwarden_data_dir: /opt/vaultwarden
# Secrets — store values in ansible/group_vars/all/vault.yml (Ansible Vault)
# vaultwarden_admin_token: "" # generate: openssl rand -hex 32
# vaultwarden_sso_client_secret: "" # added after Keycloak is configured — see runbook-configuration.md step 2.6
# Status dashboard (public — cloud.ladkau.de root)
dashboard_data_dir: /opt/dashboard
@@ -23,6 +23,7 @@ SERVICES = [
("Roundcube", "https://{{ domain_mail }}", 200, "https://{{ domain_mail }}"),
("Registry", "https://{{ domain_registry }}/v2/", 401, "https://{{ domain_registry }}"),
("k8s", "https://{{ domain_k8s }}", 200, "https://{{ domain_k8s }}"),
("Vaultwarden", "https://{{ domain_vault }}", 200, "https://{{ domain_vault }}"),
]
{% raw %}
@@ -0,0 +1,7 @@
---
- name: Restart vaultwarden
community.docker.docker_compose_v2:
project_src: "{{ vaultwarden_data_dir }}"
state: present
pull: missing
recreate: always
+29
View File
@@ -0,0 +1,29 @@
---
- name: Create Vaultwarden data directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ vaultwarden_data_dir }}"
- "{{ vaultwarden_data_dir }}/data"
tags: vaultwarden
- name: Deploy Docker Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ vaultwarden_data_dir }}/docker-compose.yml"
owner: root
group: root
mode: "0644"
notify: Restart vaultwarden
tags: vaultwarden
- name: Start Vaultwarden
community.docker.docker_compose_v2:
project_src: "{{ vaultwarden_data_dir }}"
state: present
pull: missing
tags: vaultwarden
@@ -0,0 +1,37 @@
# Managed by Ansible — do not edit manually
services:
vaultwarden:
image: vaultwarden/server:{{ vaultwarden_version }}
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://{{ domain_vault }}"
# Admin panel at /admin/ — use a long random token
ADMIN_TOKEN: "{{ vaultwarden_admin_token }}"
# Disable public sign-up — accounts created via SSO or admin panel
SIGNUPS_ALLOWED: "false"
{% if vaultwarden_sso_client_secret | default('') %}
# SSO via Keycloak — enabled after Keycloak client is configured (step 2.6)
SSO_ENABLED: "true"
SSO_CLIENT_ID: "vaultwarden"
SSO_CLIENT_SECRET: "{{ vaultwarden_sso_client_secret }}"
SSO_AUTHORITY: "https://{{ domain_sso }}/realms/ladkau"
{% else %}
SSO_ENABLED: "false"
{% endif %}
volumes:
- {{ vaultwarden_data_dir }}/data:/data
networks:
- traefik_public
labels:
- "traefik.enable=true"
- "traefik.http.routers.vaultwarden.rule=Host(`{{ domain_vault }}`)"
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
# Lax rate limit — Vaultwarden SPA loads many assets on first visit
- "traefik.http.routers.vaultwarden.middlewares=rate-limit-lax@docker"
networks:
traefik_public:
external: true
+1
View File
@@ -12,4 +12,5 @@
- mail
- registry
- k8s
- vaultwarden
- dashboard
+39 -1
View File
@@ -23,6 +23,7 @@ The script checks these endpoints and verifies the expected HTTP status code:
| `https://mail.ladkau.de` | 200 | Roundcube webmail login |
| `https://cr.ladkau.de/v2/` | 401 | Registry API — auth required, correct without credentials |
| `https://k8s.ladkau.de` | 200 | Placeholder page |
| `https://vault.ladkau.de` | 200 | Vaultwarden web vault |
A `000` result means the connection was refused or timed out — a container that
did not start. Keycloak and Nextcloud may return `502` for up to 90 seconds on
@@ -91,6 +92,21 @@ For each user you want to be able to log in to Gitea and Nextcloud:
7. Go to the **Credentials** tab and copy the **Client secret** — you will
need it in step 4.
### 2.6 Create the Vaultwarden OIDC client
1. **Clients****Create client**.
2. **Client type**: OpenID Connect — **Client ID**: `vaultwarden`**Next**.
3. Turn **Client authentication** on → **Next**.
4. **Valid redirect URIs**: `https://vault.ladkau.de/*`
5. **Valid post logout redirect URIs**: `https://vault.ladkau.de/*`
6. **Web origins**: `https://vault.ladkau.de`**Save**.
7. Go to the **Credentials** tab and copy the **Client secret**.
8. Add it to the vault as `vaultwarden_sso_client_secret` and redeploy:
```bash
ansible-vault edit ansible/group_vars/all/vault.yml
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags vaultwarden --ask-vault-pass
```
## 3. Gitea
### 3.1 Create the admin account
@@ -196,7 +212,29 @@ imapsync \
| Security | SSL/TLS |
| Username | as set in vault |
## 6. Container registry
## 6. Vaultwarden
Vaultwarden is a self-hosted Bitwarden-compatible password vault. Any official
Bitwarden client (browser extension, desktop, mobile) can connect to it.
### 6.1 Log in to the admin panel
Go to `https://vault.ladkau.de/admin/` and enter the `vaultwarden_admin_token`
from the vault. From here you can invite users and manage the instance.
### 6.2 Sign in with Keycloak SSO
Users can sign in at `https://vault.ladkau.de` using the **Enterprise Single
Sign-On** button — enter `https://sso.ladkau.de/realms/ladkau` as the
organisation identifier. On first login Vaultwarden creates an account
automatically (public self-registration is otherwise disabled).
### 6.3 Connect a Bitwarden client
In any official Bitwarden client, set the **Server URL** to
`https://vault.ladkau.de` before logging in.
## 7. Container registry
```bash
# Login
+11 -3
View File
@@ -78,6 +78,7 @@ Let's Encrypt certificates on first start and DNS must resolve at that point.
| mail.ladkau.de | A → server IP |
| cr.ladkau.de | A → server IP |
| k8s.ladkau.de | A → server IP |
| vault.ladkau.de | A → server IP |
### 4. Create the vault and populate secrets
@@ -118,17 +119,23 @@ registry_users:
- username: alice
password: "your-password"
# Dovecot IMAP users — generate with: openssl rand -hex 32
# Vaultwarden password vault
vaultwarden_admin_token: "" # generate: openssl rand -hex 32
# vaultwarden_sso_client_secret is added after Keycloak is configured — see
# step 2.6 of runbook-configuration.md
# Dovecot IMAP users
dovecot_users:
- username: alice
password: "your-password"
# Fetchmail — external POP3 accounts to pull from (omit section if not needed)
# local_user must match a username defined in dovecot_users above
# fetchmail_accounts:
# - server: pop.gmail.com
# username: user@gmail.com
# password: app-password # use a Gmail App Password, not your main password
# local_user: alice
# local_user: alice # must match a dovecot_users entry
# ssl: true
# keep: true # set false to delete from source after fetching
# poll_minutes: 10 # how often to poll this account (default: 10)
@@ -189,7 +196,8 @@ This runs all roles in order:
| 7 | `mail` | Roundcube webmail client with PostgreSQL |
| 8 | `registry` | Docker Registry v2 with htpasswd auth |
| 9 | `k8s` | Placeholder page at k8s.ladkau.de |
| 10 | `dashboard` | Public status dashboard at cloud.ladkau.de — service health and server stats |
| 10 | `vaultwarden` | Vaultwarden password vault at vault.ladkau.de |
| 11 | `dashboard` | Public status dashboard at cloud.ladkau.de — service health and server stats |
To apply a single role:
+1
View File
@@ -16,6 +16,7 @@ CHECKS=(
"https://mail.ladkau.de 200 10"
"https://cr.ladkau.de/v2/ 401 10"
"https://k8s.ladkau.de 200 10"
"https://vault.ladkau.de 200 10"
)
if ! command -v curl &>/dev/null; then
+1
View File
@@ -39,6 +39,7 @@ required_scalars = [
"nextcloud_admin_password",
"roundcube_db_password",
"roundcube_des_key",
"vaultwarden_admin_token",
]
# Required non-empty lists (must contain at least one entry)