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 Navigateur ou application → Traefik → Vaultwarden 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 (variable client_subdomains.vaultwarden). vaultwarden figure dans nebuleuse_apps et dans nebuleuse_catalogue. Le secret vault_vaultwarden_admin_token existe 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 Dans le navigateur, https://vault.penhoat.example affiche la page de connexion. Si les inscriptions sont fermées, le bouton « Créer un compte » est absent. 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_allowed doit revenir à false dè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 avec replace. Panneau d'administration exposé : il donne accès à tous les comptes. Il reste désactivé tant que vaultwarden_admin_enabled n'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 avec sqlite3 ... ".backup", suffit. Une copie à chaud du seul fichier de base peut être corrompue. Page suivante : Fail2ban.