Aller au contenu

Installation de Vaultwarden

👋 Présentation

Vaultwarden est une alternative légère et auto-hébergée à Bitwarden, un gestionnaire de mots de passe open-source. Il permet aux utilisateurs de stocker et de gérer leurs mots de passe en toute sécurité tout en gardant le contrôle total de leurs données.

Caractéristiques principales :

✅ Alternative légère à Bitwarden : Conçu pour être moins gourmand en ressources, idéal pour les petits serveurs (il tourne très bien sur un Raspberry Pi).

✅ Auto-hébergement : Vous contrôlez entièrement vos données, contrairement aux services cloud.

✅ Compatible avec les clients officiels Bitwarden : Fonctionne avec les applications mobiles, extensions de navigateur et applications de bureau de Bitwarden.

✅ Sécurisé et chiffré : Toutes les données sont chiffrées côté client avant d’être envoyées au serveur.

✅ Facile à installer : Disponible sous forme de conteneur Docker, simplifiant grandement son déploiement.

J'utilise personnellement Vaultwarden depuis 2019 (du temps où il s'appelait Bitwarden_RS) mais aussi au travail avec mes équipes depuis 2020. Je n'ai jamais eu un seul problème.

Dans cette première partie, nous allons voir comment déployer le service Vaultwarden avec Docker.

Installation avec Docker, Traefik et MariaDB

Par défaut Vaultwarden peut tourner avec Sqlite mais qui peut s'avérer non adpaté quand il s'agit de traiter un volume de données important. J'ai décidé également de partir sur une architecture 3-Tiers. Le SGBD tourne dans un serveur virtuel dédié et MariaDB est installé en bare-metal dessus.

Architecture

Afin de ne pas avoir une pki à gérer ainsi que l'intégration du certificat racine sur les clients, j'utilise un nom de domaine enregistré chez un registrar mais sans RR déclaré. Pour générer alors un certificat TLS, j'utiliserai Let's Encrypt et le DNS Challenge. Ainsi j'obtiendrai un certificat reconnu par tous les clients sans pour autant exposer Vaultwarden sur Internet. Par conséquent il vous faut déclarer les RR sur votre serveur DNS local.

Exposition sur Internet

Je pense que Vaultwarden ne doit jamais être exposé sur Internet. Nous ne sommes jamais à l'abri d'une vulnérabilité logicielle ou bien d'une tentative de phishing. Si Vaultwarden devait être exposé sur le net, je passerai alors à minima par une restriction des IP sources ou bien un VPN ou encore par un bastion comme Netbird ou Teleport.

MariaDB

MariaDB tourne sur un serveur GNU/Linux Debian 12.

Installation de MariaDB (commandes exécutées en tant que root)
apt install mariadb-server mariadb-client
mysql_secure_installation
# Connexion à MariaDB
mysql -u root
Création de la base de données
MariaDB [(none)]> CREATE DATABASE vaultwarden CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# Remplacer USER_DB, IP_SERVEUR_DOCKER (IP du serveur faisant tourner le conteneur docker Vaultwarden) et MOT_DE_PASSE_DU_USER 
MariaDB [(none)]> CREATE USER 'USER_DB'@'IP_SERVEUR_DOCKER' IDENTIFIED BY 'MOT_DE_PASSE_DU_USER';
MariaDB [(none)]> GRANT ALTER, CREATE, DELETE, DROP, INDEX, INSERT, SELECT, UPDATE ON `vaultwarden`.* TO 'USER_DB'@'IP_SERVEUR_DOCKER';
MariaDB [(none)]> FLUSH PRIVILEGES;
Configuration de Mariadb
vim /etc/mysql/mariadb.conf.d/50-server.cnf
# Cherchez bind-address et remplacez 127.0.0.1 par 0.0.0.0
bind-address            = 0.0.0.0

systemctl restart mariadb

Vaultwarden

Arborescence
mkdir -p vaultwarden/datas

vaultwarden/
├── datas/
Avant de passer à la configuration du compose.yml, vous devez générer :

  • Le token de l'admin.
  • Le mot de passe protégeant l'accès à la saisie du token

Le token de l'admin est très important car il vous donne l'accès à la gestion de VaultWarden (la configuration générale, les users,...). Il ne vous donne pas accès aux mots de passe stockés des utilisateurs.

Génération du token admin
sudo apt install argon2
# On double le symbole $ sinon sans cela Docker les interprètera comme des variables.
echo -n "MySecretPassword" | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1 | sed -e s/\\$/\\$\\$/g
# Sortie
$$argon2id$$v=19$$m=19456,t=2,p=1$$b25yWEhpaytQMkxWZUFCTlp1aEE1Q0xxZytPZFNGNkJaREJwU1lBdmhZMD0$$Uws8HYdfm1N9q1sSe/OasqLUv/h2h6ykj0rTqgKFfTs

Afin de rajouter une couche de sécurité, l'accès à https://[URL_DE_VOTRE_COFFRE]/admin (formulaire où sera demandé le token de l'admin) sera protégé par une authentification de type basicauth.

Création du compte pour le basicauth
sudo apt install apache2-utils -y
echo $(htpasswd -nbB <VOTRE_USER> "UnAutreSuperMotDePasse") | sed -e s/\\$/\\$\\$/g
<VOTRE_USER>:$$2y$$05$$aoC1Rdt0GN4DXWBx6ibzT.QjQ6eEXteRLvZVuaKs.Awu78QGR6zjK
On peut remplacer le basicauth par le "forwardAuth" en se basant sur un IAM comme Authelia ou Keycloak par exemple.

vaultwarden/compose.yml
---
networks:                                
  traefik:
    external: true

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    networks:
      - traefik
    volumes:
      - ./datas:/data
    environment:
      # Lien concernant l'encodage des mots de passe avec caractères spéciaux -> 
      # https://github.com/dani-garcia/vaultwarden/wiki/Using-the-MariaDB-%28MySQL%29-Backend
      DATABASE_URL: 'mysql://<USER_DB>:<MOT_DE_PASSE_DU_USER>@<IP_SRV_MARIADB>/vaultwarden'
      WEBSOCKET_ENABLED: "true" # Required to use websockets
      # Désactivation des inscriptions.
      SIGNUPS_ALLOWED: "false"
      # Désactivation des invitations aux utilisateurs externes pour les propriétaires/admin d'organisations.
      # Seul les utilisateurs préalablement ajoutés à Vaultwarden depuis l'interface d'administrations pourront
      # être invités à rejoindre l'organisation.
      INVITATIONS_ALLOWED: "false"
      DOMAIN: "https://coffre.domaine.fr"
      SMTP_HOST: "mail.server.com"
      SMTP_FROM: "mail@server.com"
      SMTP_PORT: "587"
      SMTP_SSL: "true"
      SMTP_USERNAME: "mail@server.com"
      SMTP_PASSWORD: "PASSWORD"
      SMTP_SECURITY: "starttls"
      ADMIN_TOKEN: "$$argon2id$$v=19$$m=19456,t=2,p=1$$b25yWEhpaytQMkxWZUFCTlp1aEE1Q0xxZytPZFNGNkJaREJwU1lBdmhZMD0$$Uws8HYdfm1N9q1sSe/OasqLUv/h2h6ykj0rTqgKFfTs"
      LOG_FILE: "/data/vaultwarden.log"
      ROCKET_LOG: "critical"
      EXTENDED_LOGGING: "true"
      TZ: "Europe/Paris"
    mem_limit: 120M
    mem_reservation: 92M
    restart: always
    labels:
        - "traefik.enable=true"
        - "traefik.docker.network=traefik"
        - "traefik.http.routers.vaultwarden.rule=Host(`coffre.domaine.fr`)"
        - "traefik.http.routers.vaultwarden.tls=true"
        - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
        - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
        - "traefik.http.routers.vaultwarden.service=vaultwarden"
        #ADMIN
        - "traefik.http.routers.vaultwarden-admin.rule=Host(`coffre.domaine.fr`) && PathPrefix(`/admin`)"
        - "traefik.http.routers.vaultwarden-admin.tls=true"
        ## L'accès /admin est protégée par une auth basicauth et seules les IP locales peuvent y accéder 
        - "traefik.http.routers.vaultwarden-admin.middlewares=vaultwarden-admin-auth,vaultwarden-admin-ipallowlist"
        - "traefik.http.middlewares.vaultwarden-admin-auith.basicauth.users=<USER>:$$2y$$05$$aoC1Rdt0GN4DXWBx6ibzT.QjQ6eEXteRLvZVuaKs.Awu78QGR6zjK"
        ### Dans un réseau segmenté, vous pouvez déclarer ci-dessous la plage IP locale du service informatique par exemmple
        - "traefik.http.middlewares.vaultwarden-admin-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.0/24"
        - "traefik.http.routers.vaultwarden-admin.service=vaultwarden"
        #WS
        - "traefik.http.routers.vaultwarden-ws.rule=Host(`coffre.domaine.fr`) && PathPrefix(`/notifications/hub`)"
        - "traefik.http.routers.vaultwarden-ws.tls=true"
        - "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt"
        - "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012"
        - "traefik.http.routers.vaultwarden-ws.service=vaultwarden-ws"

Lancez la commande docker compose pull. Nous lancerons le conteneur après avoir configuré Traefik.

Traefik

Création arborescence et fichiers
mkdir -p traefik/{config/{dyn_traefik,secrets},logs}
touch config/acme.json
chmod 600 config/acme.json

traefik/
|-- config/
|   |-- acme.json
|   |-- dyn_traefik/
|   |-- secrets/
`-- logs/

Les fichiers contenant les secrets "ovh_*" seront contenus dans le répertoire traefik/config/secrets. Ils seront utilisés pour l'authentification sur l'API d'OVH dans le cadre de la génération du certificat TLS avec Let's Encrypt et le challenge DNS.

traefik/compose.yml
---
networks:
  traefik:
    external: true

secrets:
  ovh_endpoint:
    file: "./config/secrets/ovh_endpoint.secret"
  ovh_application_key:
    file: "./config/secrets/ovh_application_key.secret"
  ovh_application_secret:
    file: "./config/secrets/ovh_application_secret.secret"
  ovh_consumer_key:
    file: "./config/secrets/ovh_consumer_key.secret"

services:
  traefik:
    image: "traefik:v3"
    container_name: traefik
    networks:
      - traefik
    secrets:
      - "ovh_endpoint"
      - "ovh_application_key"
      - "ovh_application_secret"
      - "ovh_consumer_key"
    environment:
      - "TZ=Europe/Paris"
      - "OVH_ENDPOINT_FILE=/run/secrets/ovh_endpoint"
      - "OVH_APPLICATION_KEY_FILE=/run/secrets/ovh_application_key"
      - "OVH_APPLICATION_SECRET_FILE=/run/secrets/ovh_application_secret"
      - "OVH_CONSUMER_KEY_FILE=/run/secrets/ovh_consumer_key"
    volumes:
      # Mapping sur le socket interne de Docker
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      # Mapping du fichier de configuration statique
      - './config/traefik.yml:/traefik.yml'
      # Mapping du dossier contenant la configuration dynamique
      - './config/dyn_traefik/:/dyn_traefik/'
      # Mapping du fichier de stockage des certificats
      - './config/acme.json:/acme.json'
      - "./logs:/var/log"
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.domaine.fr`)"
      - "traefik.http.routers.traefik-dashboard.service=api@internal"
      - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
      - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik-dashboard.middlewares=traefik-dashboard-ipallowlist"
      - "traefik.http.middlewares.traefik-dashboard-ipallowlist.ipallowlist.sourcerange=127.0.0.1/32, 192.168.1.0/24"
      - "traefik.http.services.traefik-dashboard-service.loadbalancer.server.port=8080"
config/traefik.yml
---
api:
  dashboard: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    directory: /dyn_traefik/
    watch: true

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      email: "<EMAIL>"
      storage: "/acme.json"
      keyType: EC384
      dnsChallenge:
        provider: ovh
        delayBeforeCheck: 10
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

log:
  filePath: "/var/log/traefik.log"
  format: json
  level: INFO
  maxSize: 5
  maxBackups: 50
  maxAge: 10
  compress: true

accessLog:
  filePath: "/var/log/access.log"
  format: json
  fields:
    defaultMode: keep
    names:
      StartUTC: drop

Lancez la commande docker compose pull. Nous lancerons les conteneurs après avoir la rotation des logs.

Rotation des logs Traefik et Vaultwarden

Façon de ne pas se trouver avec des fichiers de logs de plusieurs gigaoctets, on déclare deux tâches de rotation des logs.

Configuration de la rotation des logs
# Traefik
vim /etc/logrotate.d/traefik

compress
/srv/docker/traefik/logs/*.log {
  size 20M
  daily
  rotate 14
  missingok
  notifempty postrotate
  docker kill --signal="USR1" traefik # préciser le nom du conteneur visé, ici "traefik"
  endscript
}

# Vaultwarden
vim /etc/logrotate.d/vaultwarden

compress
/srv/vaultwarden/datas/vaultwarden.log {
    daily
    size 5M
    rotate 14
    copytruncate
    missingok
    notifempty
    dateext
    dateformat -%Y-%m-%d-%s
    notifempty postrotate
    docker kill --signal="USR1" vaultwarden
    endscript
}

Lancement des conteneurs

Traefik

Lancement de Traefik
docker network create traefik
docker compose up -d

Patientez une minute environ, le temps que le certificat TLS soit généré, puis connectez-vous sur le dashboard de Traefik. Ex. : https://traefik.domaine.fr

N'hésitez pas à vérifier les logs contenus dans traefik/logs/ afin de vérifier si tout est OK.

Vaultwarden

Lancement de Vaultwarden
docker compose up -d

Vérifier les logs contenus dans vaultwarden/datas/vaultwarden.log et aussi avec la commande docker logs vaultwarden afin de vérifier si tout est OK.

Patientez une minute environ, le temps que le certificat TLS soit généré, puis connectez-vous sur le dashboard admin. Ex. : https://coffre.domaine.fr/admin

La première authentification sera celle basée sur basicauth :

authentification basicauth

La deuxième authentification sera celle basée sur le token de l'admin :

token admin

Saisissez le token (la version non chiffrée) et vous aurez alors accès au dashboard admin de Vaultwarden :

Dashboard admin

Vérifiez la configuration en cliquant sur Diagnostics :

Diagnostics

À partir de là, nous allons pouvoir gérer les paramètres de Vaultwarden ainsi que les utilisateurs et les organisations.

Conclusion

C'est terminé pour la première partie. Dans la deuxième partie, nous verrons quelques notions de base et nous aborderons la création des utilisateurs ainsi que les clients.