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:
@@ -8,6 +8,7 @@ domain_sso: "sso.{{ domain_base }}"
|
|||||||
domain_mail: "mail.{{ domain_base }}"
|
domain_mail: "mail.{{ domain_base }}"
|
||||||
domain_registry: "cr.{{ domain_base }}"
|
domain_registry: "cr.{{ domain_base }}"
|
||||||
domain_k8s: "k8s.{{ domain_base }}"
|
domain_k8s: "k8s.{{ domain_base }}"
|
||||||
|
domain_vault: "vault.{{ domain_base }}"
|
||||||
|
|
||||||
# Let's Encrypt
|
# Let's Encrypt
|
||||||
acme_email: matthias.ladkau@gmail.com
|
acme_email: matthias.ladkau@gmail.com
|
||||||
@@ -97,5 +98,12 @@ registry_data_dir: /opt/registry
|
|||||||
# k8s (placeholder)
|
# k8s (placeholder)
|
||||||
k8s_data_dir: /opt/k8s
|
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)
|
# Status dashboard (public — cloud.ladkau.de root)
|
||||||
dashboard_data_dir: /opt/dashboard
|
dashboard_data_dir: /opt/dashboard
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ SERVICES = [
|
|||||||
("Keycloak", "https://{{ domain_sso }}/realms/master", 200, "https://{{ domain_sso }}"),
|
("Keycloak", "https://{{ domain_sso }}/realms/master", 200, "https://{{ domain_sso }}"),
|
||||||
("Roundcube", "https://{{ domain_mail }}", 200, "https://{{ domain_mail }}"),
|
("Roundcube", "https://{{ domain_mail }}", 200, "https://{{ domain_mail }}"),
|
||||||
("Registry", "https://{{ domain_registry }}/v2/", 401, "https://{{ domain_registry }}"),
|
("Registry", "https://{{ domain_registry }}/v2/", 401, "https://{{ domain_registry }}"),
|
||||||
("k8s", "https://{{ domain_k8s }}", 200, "https://{{ domain_k8s }}"),
|
("k8s", "https://{{ domain_k8s }}", 200, "https://{{ domain_k8s }}"),
|
||||||
|
("Vaultwarden", "https://{{ domain_vault }}", 200, "https://{{ domain_vault }}"),
|
||||||
]
|
]
|
||||||
{% raw %}
|
{% raw %}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
- name: Restart vaultwarden
|
||||||
|
community.docker.docker_compose_v2:
|
||||||
|
project_src: "{{ vaultwarden_data_dir }}"
|
||||||
|
state: present
|
||||||
|
pull: missing
|
||||||
|
recreate: always
|
||||||
@@ -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
|
||||||
@@ -12,4 +12,5 @@
|
|||||||
- mail
|
- mail
|
||||||
- registry
|
- registry
|
||||||
- k8s
|
- k8s
|
||||||
|
- vaultwarden
|
||||||
- dashboard
|
- dashboard
|
||||||
|
|||||||
@@ -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://mail.ladkau.de` | 200 | Roundcube webmail login |
|
||||||
| `https://cr.ladkau.de/v2/` | 401 | Registry API — auth required, correct without credentials |
|
| `https://cr.ladkau.de/v2/` | 401 | Registry API — auth required, correct without credentials |
|
||||||
| `https://k8s.ladkau.de` | 200 | Placeholder page |
|
| `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
|
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
|
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
|
7. Go to the **Credentials** tab and copy the **Client secret** — you will
|
||||||
need it in step 4.
|
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. Gitea
|
||||||
|
|
||||||
### 3.1 Create the admin account
|
### 3.1 Create the admin account
|
||||||
@@ -196,7 +212,29 @@ imapsync \
|
|||||||
| Security | SSL/TLS |
|
| Security | SSL/TLS |
|
||||||
| Username | as set in vault |
|
| 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
|
```bash
|
||||||
# Login
|
# Login
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ Let's Encrypt certificates on first start and DNS must resolve at that point.
|
|||||||
| mail.ladkau.de | A → server IP |
|
| mail.ladkau.de | A → server IP |
|
||||||
| cr.ladkau.de | A → server IP |
|
| cr.ladkau.de | A → server IP |
|
||||||
| k8s.ladkau.de | A → server IP |
|
| k8s.ladkau.de | A → server IP |
|
||||||
|
| vault.ladkau.de | A → server IP |
|
||||||
|
|
||||||
### 4. Create the vault and populate secrets
|
### 4. Create the vault and populate secrets
|
||||||
|
|
||||||
@@ -118,17 +119,23 @@ registry_users:
|
|||||||
- username: alice
|
- username: alice
|
||||||
password: "your-password"
|
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:
|
dovecot_users:
|
||||||
- username: alice
|
- username: alice
|
||||||
password: "your-password"
|
password: "your-password"
|
||||||
|
|
||||||
# Fetchmail — external POP3 accounts to pull from (omit section if not needed)
|
# 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:
|
# fetchmail_accounts:
|
||||||
# - server: pop.gmail.com
|
# - server: pop.gmail.com
|
||||||
# username: user@gmail.com
|
# username: user@gmail.com
|
||||||
# password: app-password # use a Gmail App Password, not your main password
|
# 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
|
# ssl: true
|
||||||
# keep: true # set false to delete from source after fetching
|
# keep: true # set false to delete from source after fetching
|
||||||
# poll_minutes: 10 # how often to poll this account (default: 10)
|
# poll_minutes: 10 # how often to poll this account (default: 10)
|
||||||
@@ -188,8 +195,9 @@ This runs all roles in order:
|
|||||||
| 6 | `sso` | Keycloak single sign-on with PostgreSQL |
|
| 6 | `sso` | Keycloak single sign-on with PostgreSQL |
|
||||||
| 7 | `mail` | Roundcube webmail client with PostgreSQL |
|
| 7 | `mail` | Roundcube webmail client with PostgreSQL |
|
||||||
| 8 | `registry` | Docker Registry v2 with htpasswd auth |
|
| 8 | `registry` | Docker Registry v2 with htpasswd auth |
|
||||||
| 9 | `k8s` | Placeholder page at k8s.ladkau.de |
|
| 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:
|
To apply a single role:
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ CHECKS=(
|
|||||||
"https://mail.ladkau.de 200 10"
|
"https://mail.ladkau.de 200 10"
|
||||||
"https://cr.ladkau.de/v2/ 401 10"
|
"https://cr.ladkau.de/v2/ 401 10"
|
||||||
"https://k8s.ladkau.de 200 10"
|
"https://k8s.ladkau.de 200 10"
|
||||||
|
"https://vault.ladkau.de 200 10"
|
||||||
)
|
)
|
||||||
|
|
||||||
if ! command -v curl &>/dev/null; then
|
if ! command -v curl &>/dev/null; then
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ required_scalars = [
|
|||||||
"nextcloud_admin_password",
|
"nextcloud_admin_password",
|
||||||
"roundcube_db_password",
|
"roundcube_db_password",
|
||||||
"roundcube_des_key",
|
"roundcube_des_key",
|
||||||
|
"vaultwarden_admin_token",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Required non-empty lists (must contain at least one entry)
|
# Required non-empty lists (must contain at least one entry)
|
||||||
|
|||||||
Reference in New Issue
Block a user