Vaultwarden
En bref
Niveau : intermédiaire
Durée : 30 minutes
Prérequis : Traefik en place
Niveau de test : reconstitué
Vaultwarden, c'est quoi ?
Vaultwarden est un gestionnaire de mots de passe auto-hébergé, compatible avec les applications et extensions Bitwarden. Chaque utilisateur y range ses identifiants dans un coffre chiffré, accessible depuis le navigateur, l'ordinateur et le téléphone. Un seul conteneur suffit : le serveur et sa base de données SQLite.
| Élément | Rôle |
|---|---|
| Conteneur Vaultwarden | Serveur, interface web et API compatible Bitwarden |
Dossier data |
Base SQLite, pièces jointes, clés et journal |
Journal vaultwarden.log |
Lu par Fail2ban pour bloquer les échecs de connexion |
Concrètement, Vaultwarden est l'application la plus simple du catalogue à déployer, et la seule qui ne passe pas par le SSO.
Le coffre est chiffré avec le mot de passe principal de l'utilisateur, qui ne quitte jamais son appareil. Ce mot de passe ne peut pas être délégué à Authentik : le serveur ne pourrait plus rien déchiffrer sans lui. Le rôle ne crée donc pas de connexion SSO pour Vaultwarden. Certaines versions proposent une connexion SSO optionnelle, non activée ici.
Prérequis
- Traefik publie
vault.penhoat.example(variableclient_subdomains.vaultwarden). vaultwardenfigure dansnebuleuse_appset dansnebuleuse_catalogue.- Le secret
vault_vaultwarden_admin_tokenexiste dans le Vault du client (page « Inventaire et variables »).
Arborescence du rôle
roles/vaultwarden/
├── defaults/main.yml
├── tasks/main.yml
└── templates/
└── docker-compose.yml.j2
Variables
# roles/vaultwarden/defaults/main.yml
# Version de Vaultwarden : remplacer par la dernière version stable.
vaultwarden_version: "1.34.3"
# Dossier de travail sur la VM.
vaultwarden_dir: /opt/nebuleuse/vaultwarden
# Nom de domaine de l'application.
vaultwarden_host: "{{ client_subdomains.vaultwarden }}.{{ client_domain }}"
# Inscriptions : fermées en fonctionnement normal. À ouvrir temporairement pour créer
# les premiers comptes (voir la section « Créer les premiers comptes »).
vaultwarden_signups_allowed: false
# Seules les adresses de ce domaine peuvent s'inscrire quand les inscriptions sont ouvertes.
vaultwarden_signups_domain: "{{ client_domain }}"
# Panneau d'administration (/admin) : désactivé par défaut. Il donne accès à tous les comptes.
vaultwarden_admin_enabled: false
# Jeton du panneau d'administration, stocké dans le Vault sous forme de hachage Argon2.
vaultwarden_admin_token: "{{ vault_vaultwarden_admin_token }}"
# Réseau Docker partagé avec Traefik.
vaultwarden_network: "{{ docker_proxy_network }}"
Le fichier Docker Compose
# roles/vaultwarden/templates/docker-compose.yml.j2
# Fichier géré par Ansible : toute modification manuelle sera écrasée.
# Il peut contenir un jeton d'administration : accès réservé à root.
services:
vaultwarden:
image: vaultwarden/server:{{ vaultwarden_version }}
container_name: {{ client_name }}_vaultwarden
restart: unless-stopped
environment:
# Adresse publique : utilisée dans les liens des e-mails et par les extensions.
DOMAIN: "https://{{ vaultwarden_host }}"
SIGNUPS_ALLOWED: "{{ vaultwarden_signups_allowed | lower }}"
SIGNUPS_DOMAINS_WHITELIST: "{{ vaultwarden_signups_domain }}"
# Les comptes existants peuvent inviter d'autres personnes (organisations).
INVITATIONS_ALLOWED: "true"
# Journal dans un fichier : c'est lui que lit Fail2ban.
LOG_FILE: /data/vaultwarden.log
LOG_LEVEL: warn
TZ: Europe/Paris
{% if vaultwarden_admin_enabled %}
# Un hachage Argon2 contient des "$", qui doivent être doublés dans un fichier Compose.
ADMIN_TOKEN: '{{ vaultwarden_admin_token | replace("$", "$$") }}'
{% endif %}
volumes:
- {{ vaultwarden_dir }}/data:/data
networks:
- {{ vaultwarden_network }}
labels:
- "traefik.enable=true"
- "traefik.docker.network={{ vaultwarden_network }}"
- "traefik.http.routers.vaultwarden-{{ client_name }}.rule=Host(`{{ vaultwarden_host }}`)"
- "traefik.http.routers.vaultwarden-{{ client_name }}.entrypoints=websecure"
# Port d'écoute de Vaultwarden dans le conteneur.
- "traefik.http.services.vaultwarden-{{ client_name }}.loadbalancer.server.port=80"
networks:
{{ vaultwarden_network }}:
external: true
Vaultwarden lit l'adresse du visiteur dans l'en-tête X-Real-IP, que Traefik renseigne. Les échecs de connexion de son journal portent donc la vraie adresse du visiteur, ce qui permet à Fail2ban de la bloquer.
Déploiement
# roles/vaultwarden/tasks/main.yml
- name: Créer les dossiers de Vaultwarden
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ vaultwarden_dir }}"
- "{{ vaultwarden_dir }}/data"
- name: Déposer le fichier Docker Compose de Vaultwarden
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ vaultwarden_dir }}/docker-compose.yml"
# Le fichier peut contenir un jeton : root uniquement.
mode: "0600"
no_log: true
- name: Démarrer Vaultwarden
community.docker.docker_compose_v2:
project_src: "{{ vaultwarden_dir }}"
state: present
- name: Attendre que Vaultwarden réponde
ansible.builtin.command:
argv:
- curl
- -fsS
- -k
- --resolve
- "{{ vaultwarden_host }}:443:127.0.0.1"
- "https://{{ vaultwarden_host }}/alive"
register: vaultwarden_alive
until: vaultwarden_alive.rc == 0
retries: 30
delay: 5
changed_when: false
Créer les premiers comptes
Les inscriptions sont fermées par défaut : personne ne peut créer de compte sur l'adresse publique. Pour intégrer un client, le support ouvre les inscriptions le temps de créer les premiers comptes, puis les referme.
# 1. Ouvrir les inscriptions pour ce client, pour les adresses de son domaine seulement.
ansible-playbook site.yml --limit client-alpha --tags vaultwarden -e vaultwarden_signups_allowed=true
# 2. Le client crée son compte sur https://vault.penhoat.example (mot de passe principal choisi par lui).
# 3. Refermer les inscriptions : relancer sans la variable.
ansible-playbook site.yml --limit client-alpha --tags vaultwarden
Les comptes suivants sont créés par invitation depuis une organisation, sans rouvrir les inscriptions.
Vérification
# Sur la VM : le conteneur est démarré.
docker ps --filter name=vaultwarden --format '{{.Names}}\t{{.Status}}'
# L'adresse publique répond : doit afficher une date ISO.
curl -sS https://vault.penhoat.example/alive
# Le fichier de journal existe (créé au premier événement).
ls -l /opt/nebuleuse/vaultwarden/data/vaultwarden.log
Pièges
- Inscriptions laissées ouvertes : une instance publique avec les inscriptions ouvertes accepte n'importe quelle adresse du domaine autorisé, sans vérification. La variable
vaultwarden_signups_alloweddoit revenir àfalsedès les comptes créés. - Jeton d'administration en clair : le jeton doit être stocké sous forme de hachage Argon2 dans le Vault, pas en clair. Dans le fichier Compose, chaque
$doit être doublé, ce que fait le modèle avecreplace. - Panneau d'administration exposé : il donne accès à tous les comptes. Il reste désactivé tant que
vaultwarden_admin_enabledn'est pas passé àtrue, et ne doit l'être que le temps d'une intervention. - Mot de passe principal perdu : le serveur ne peut ni le retrouver ni le réinitialiser, car il ne le connaît pas. Sans lui, le coffre est perdu. Prévenir le client à l'ouverture de son compte.
- Extensions qui n'atteignent pas le serveur : l'adresse du serveur doit être saisie en entier,
https://vault.penhoat.example, et le certificat doit être reconnu. Les certificats de test de Let's Encrypt sont refusés par les applications. - Journal absent pour Fail2ban : sans
LOG_FILE, Vaultwarden écrit sur la sortie standard et le filtre ne trouve rien. Le chemin hôte du journal est/opt/nebuleuse/vaultwarden/data/vaultwarden.log. - Sauvegarde : tout le contenu est dans le dossier
data. Une copie du dossier à froid, ou une copie de la base avecsqlite3 ... ".backup", suffit. Une copie à chaud du seul fichier de base peut être corrompue.
Page suivante : Fail2ban.
No comments to display
No comments to display