LXC : un conteneur pour vaultwarden
Rédigé par Alexandre le 2023-03-07
Récemment je me suis mis en tête d'équiper toute la famille d'un gestionnaire de mot de passe, mais ma solution de coeur, KeepassXC n'est pas des plus pratique. En effet, cette solution nécessite un outil tier pour synchroniser plusieurs appareils. Du coup, je me suis tourné vers Vaultwarden, le fork hébergeable de Bitwarden.
Dans cet article, je documente les étapes pour installer Vaultwarden dans un conteneur LXC via Podman.
Le conteneur LXC
Créer le conteneur en mode embarqué (nesting) :
$ lxc launch \
images:debian/bullseye \
ykn-vaultwarden-2310 \
--config=security.nesting=true
Se connecter en SSH au conteneur ou via LXC :
$ lxc exec ykn-vaultwarden-2310 -- bash
Déployer Vaultwarden
Installer les prérequis :
# apt install ca-certificates
Installer podman :
# apt install podman --install-recommends
NB : je force l'installation des paquets recommandés pour contourner ma configuration d'apt qui désactive leur installation.
Créer un utilisateur dédié :
# adduser vaultwarden \
--shell /bin/sh \
--disabled-password
Créer un dossier pour les données :
# mkdir /srv/vaultwarden && \
chown -R vaultwarden: /srv/vaultwarden
Utiliser l'utilisateur nouvellement créé :
# sudo -iu vaultwarden
Déployer le conteneur :
$ podman run \
-v /srv/vaultwarden:/data \
-e ADMIN_TOKEN="<jeton>" \
-p 8080:80 -p 3012:3012 \
--name vaultwarden \
"docker.io/vaultwarden/server:latest"
NB : penser à remplacer <jeton>
par un mot de passe complexe tout en le sauvegardant quelque part.
Sortir du conteneur en appuyant simultanément sur les touches Ctrl
et C
.
Créer le service :
$ podman generate systemd \
--new --name vaultwarden \
> /tmp/container-vaultwarden.service
Repasser root :
$ exit
Déployer le service :
# mv /tmp/container-vaultwarden.service /etc/systemd/system/
Définir l'utilisateur du service :
# mkdir /etc/systemd/system/container-vaultwarden.service.d; \
tee /etc/systemd/system/container-vaultwarden.service.d/override.conf <<EOF
[Service]
User=vaultwarden
Group=vaultwarden
RuntimeDirectory=user/%U
EOF
Changer l'emplacement du fichier de PID :
sed -i 's#\%t#/run/user/\%U#g' /etc/systemd/system/container-vaultwarden.service
Recharger systemd et démarrer le service :
# systemctl daemon-reload && \
systemctl start container-vaultwarden.service
Si le service à correctement démarré, activer le service :
# systemctl enable container-vaultwarden.service
Pare-feu
Dans mon cas j'utilise nftables avec la configuration suivante :
# cat /etc/nftables.conf
#!/usr/sbin/nft -f
# Ansible managed
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
# accept any localhost traffic
iif lo accept
# accept traffic originated from us
ct state established,related accept
# accept neighbour discovery otherwise IPv6 connectivity breaks.
ip6 nexthdr icmpv6 icmpv6 type {nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert} accept
# include specifics rules
include "/srv/nftables/rules_*.conf"
# count and drop any other traffic
counter drop
}
}
Du coup, pour ouvrir les ports pour Vaultwarden :
# tee /srv/nftables/rules_vaultwarden.conf <<EOF
tcp dport 8080 accept
tcp dport 3012 accept
EOF
Recharger le pare-feu :
# systemctl reload nftables
Reverse proxy
Sur mon infrastructure, c'est nginx qui est en amont de Vaultwarden. Le vhost est le suivant :
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vaultwarden.ykn.local;
access_log /var/log/nginx/vaultwarden.ykn.local_access.log anonymize;
error_log /var/log/nginx/vaultwarden.ykn.local_error.log;
ssl_certificate /etc/ykn/rsync/letsencrypt/vaultwarden.ykn.local/fullchain.pem;
ssl_certificate_key /etc/ykn/rsync/letsencrypt/vaultwarden.ykn.local/privkey.pem;
ssl_trusted_certificate /etc/ykn/rsync/letsencrypt/vaultwarden.ykn.local/chain.pem;
include /etc/nginx/snippets/ssl.conf;
include /etc/nginx/snippets/header.conf;
include /etc/nginx/snippets/maintenance.conf;
include /etc/nginx/snippets/acme-challenge.conf;
location / {
include /etc/nginx/snippets/proxy.conf;
proxy_pass http://ykn-vaultwarden-2310.nyx.ykn.local:8080;
}
location /notifications/hub {
include /etc/nginx/snippets/proxy.conf;
proxy_pass http://ykn-vaultwarden-2310.nyx.ykn.local:3012;
}
}
Le contenu des différents snippets que j'utilise :
$ cat /etc/nginx/snippets/ssl.conf
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# replace with the IP address of your resolver
resolver 80.67.169.12 80.67.169.40 [2001:910:800::40] [2001:910:800::12] valid=60s;
resolver_timeout 2s;
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
$ cat /etc/nginx/snippets/header.conf
add_header Referrer-Policy "strict-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Xss-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Front-End-Https on;
add_header X-Download-Options "noopen" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
#add_header X-Robots-Tag "none" always;
$ cat /etc/nginx/snippets/maintenance.conf
proxy_intercept_errors on;
error_page 500 502 503 504 @maintenance;
location @maintenance {
# Désactiver la journalisation
access_log off;
error_log off;
root /var/www/maintenance;
index /index.html;
try_files /$uri /index.html =503;
}
$ cat /etc/nginx/snippets/acme-challenge.conf
location /.well-known/acme-challenge {
proxy_pass http://letsencrypt.ykn.local/;
proxy_set_header Host infra-letsencrypt-2230.nyx.ykn.local;
}
$ cat /etc/nginx/snippets/proxy.conf
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
#add_header Front-End-Https on;
# Security
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=Strict";
Sauvegarde
Sur mon infrastructure, c'est borgmatic qui sauvegarde l'intérieur d'un conteneur. La configuration utilisée pour Vaultwarden est la suivante :
---
consistency:
checks:
- frequency: 4 weeks
name: repository
- frequency: 2 weeks
name: archives
hooks:
healthchecks:
ping_url: <url_healthcheck>
send_logs: false
location:
exclude_patterns:
- /srv/vaultwarden/icon_cache
- /srv/vaultwarden/tmp
repositories:
- <dépot>
source_directories:
- /srv/vaultwarden
retention:
keep_daily: 7
keep_monthly: 0
keep_weekly: 4
storage:
archive_name_format: ykn-vaultwarden-2310.nyx.ykn.local_{now}
compression: lz4
encryption_passphrase: <passphrase>
ssh_command: ssh -i /etc/borgmatic/id_ed25519
NB: penser à modifier tout ce qui est entre <...>
.
Mots de la fin
Afin de conclure cet article, je rappelle simplement que l'interface d'administration est accessible via /admin
; ce qui donne avec mon vhost : https://vaultwarden.ykn.local/admin
. Je recommande de sécuriser cet emplacement via une authentification basique type htpasswd.