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.

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.
apt install mariadb-server mariadb-client
mysql_secure_installation
# Connexion à MariaDB
mysql -u root
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;
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
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.
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.
sudo apt install apache2-utils -y
echo $(htpasswd -nbB <VOTRE_USER> "UnAutreSuperMotDePasse") | sed -e s/\\$/\\$\\$/g
<VOTRE_USER>:$$2y$$05$$aoC1Rdt0GN4DXWBx6ibzT.QjQ6eEXteRLvZVuaKs.Awu78QGR6zjK
---
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
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.
---
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"
---
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.
# 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
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
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
:

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

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

Vérifiez la configuration en cliquant sur 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.