Im Synology-NAS unter Docker Bitwarden/Vaultwarden einrichten

Welt seid mir gegrüßt.
Stellt euch folgendes vor: Ihr habt mehrere Geräte und würdet gern eure Passwörter synchronisieren.
Eure Kennwörter wollt ihr aber ungern auf einer Cloud bei einen externen Anbieter anvertrauen.
Die Lösung liegt nahe: man sollte die Kennwörter also selber hosten.
Eine Keepass-Datenbank hin und her zu synchronisieren erweist sich als unpraktisch. Etwas zentraleres muss her.
Meine Lösung dazu: Bitwarden (bzw jetzt Neu: Vaultwarden, das selbe in Grün)

Bitwarden/Vaultwarden kann für Privatanwender for free genutzt werden. Man kann auch deren Cloud Lösung nehmen, aber wozu gibt es NAS und Raspberry Pis die für sowas gut geeignet sind.
Ich hab meine Passwörter gern so wenig in einer externen Cloud wie möglich.
Der folgende Guide bezieht sich primär auf Synology DSM 7. Für Raspberry Pi wird ein eigener Beitrag folgen.

Docker

Zuerst einmal laden wir im Docker das folgende Paket: vaultwarden/server:latest

und starten den Container und legen folgende Einstellungen fest:

(Name kann natürlich geändert werden)

Wir brauchen ein data Ordner damit die anfallenden Daten auch gespeichert bleiben.
Der Mount-Pfad kann nach belieben geändert werden. Er muss aber auf jedenfall aber manuell (z.b. in der Filestation) erstellt werden.
Das Logfile werden wir später noch brauchen für fail2ban, aber auch so kann es interessant sein das lesen zu wollen.

Eure lokalen Ports sind natürlich je nach gusto auch anpassbar.
Der 3012er Port wird für den Websocket benötigt, damit Änderungen sofort gesynct werden.
Will man das nicht und es reicht einen das Bitwarden das beim Start nur synct ist dieser optional.
Der 80er Port ist, ihr habt es erraten, das Webinterface. Das bekommt gleich via Reverse Proxy auch ein Zertifikat.
Dazu gleich mehr. Auch hier könnt ihr einen Port nehmen den Ihr für richtig erachtet.

Last but not least die Umgebungsvariablen für den Docker:

Sie erklären sich eigentlich von selbst.
Signups_allowed können wir später auf false setzen nachdem wir uns erfolgreich angemeldet haben.

Soviel erstmal zum Docker zeug. Auf zum notwendigen Reverse Proxy (notwendig fürs Internet), den findet ihr hier:

!!! Anmerkung !!! Ich wurde von einigen Leuten hingewiesen, das sie gar kein Zugang aus dem Internet wollen. LAN / VPN reicht ihnen völlig. In diesenfall seit ihr hier schon Fertig und geht dann immer via HTTP(S)://IPoderDNSnameDESnas:35555 in den Vault.

Okay weiter im Text:

Der Reverse Proxy

Optional: DDNS mit Zertifikat erstellen (falls notwendig)

Ihr habt keine domain/ddns für eure Synology ? Ist nicht wild, Synology bringt alles mit was wir brauchen.


Synology selbst bietet ein DDNS an den ihr nutzen könnt. Klickt euch durchs menü und erstellt ggfs. ein DDNS.
Ihr solltet euch zur der DDNS auch noch ein Zertifikat ausstellen lassen:

Router Freigaben:

Okay, wir haben den Docker, wir haben den Reverse Proxy und sogar ein Zertifikat. Was jetzt ?

Der Port muss noch freigegeben werden in der Fritz.Box (oder ähnliches).

Wir melden uns also an der Fritz.Box an und gehen auf „Freigaben“:

sowie „Gerät für Freigaben hinzufügen“ und geben jetzt den öffentlichen Port des Reverse Proxy sowie den Websocket an.
Soweit so gut. Testen wir das ganze und rufen eure DDNS auf mit dem entsprechenden Port der auf den Reverse Proxy zeigt:

Den aufmerksamen unter uns wird aufgefallen sein, das ganze Verbindung ist durch das Synology Zertifikat geschützt:

Konto erstellen und fast Fertig.
Grundsätzlich kann man schon das Webinterface komplett nutzen.

Browser Plugin

Wir wollen aber noch die Browser Plugins und/oder den Mobiltelefon client installieren.
Das ermöglicht uns im Browser die neue Kennwörter direkt in den Tresor abzulegen, sowie gespeicherte Daten bequem abzurufen.
Am beispiel von Firefox sieht das so aus:

Wir installieren das folgende Browser Plugin:
https://addons.mozilla.org/de/firefox/addon/bitwarden-password-manager/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search

und das sieht dann so aus:

Bevor wir uns anmelden gehen wir zum zahnrad und geben als URL das Webinterface von oben ein.

Analog funktioniert das natürlich auch für Android/IOS. Einfach den Bitwarden Client installieren und als URL beim Zahnrad das Webinterface eingeben.

Der Websocket

Okay, soweit so gut. was jetzt ? Der Websocket funktioniert noch nicht so ganz.
Was ist das ? Wozu brauch ich das ? Nun ähnlich wie beim Handy es „Push Nachrichten“ gibt die einem sofort anzeigen das man z.B. eine mail hat geht das auch mit Webanwendungen. Die Nutzen websockets um sich zu benachrichtigen „Hier gibts eine Änderung, mach mal was. In unseren Fall: ein neuen Sync anstossen, sodass wir bei Änderungen immer SOFORT überall unsere neuen Passwörter haben und nicht erst nach dem nächsten Sync Intervall „


Aber das fixen wir schnell
Dazu hatte ich das hier gefunden:
https://gist.github.com/nstanke/3949ae1c4706854d8f166d1fb3dadc81
genauer gesagt das hier:
https://gist.github.com/nstanke/3949ae1c4706854d8f166d1fb3dadc81#gistcomment-3795730Synology Bitwarden_rs Websocket setup without SSH · GitHub

Ich habe mir die Freiheit genommen hier einige Anpassungen vorzunehmen. Die eigentliche Idee dazu kam aber von meinem Arbeitskollegen Torsten: Die Admin Page sollte nur aus dem vertrauenswürdigen VPN und Heimnetz erreichbar sein. Mit einer groben Vorstellung von Torsten wie es gehen müsste habe ich das ganze ausgebaut und am ende das Script erweitert.
In meinem Fall habe ich ein Heimnetz und ein VPN Netz. Habt ihr kein VPN Netz tragt ihr hier bitte auch das Heimnetz ein. (Zeile 47-50)

#!/bin/bash
##==============================================================================================
##                                                                                            ##
##                       Script vaultwarden__Enable_Websocket-DSM_7.sh                        ##
##                                                                                            ##
##          Source : https://gist.github.com/nstanke/3949ae1c4706854d8f166d1fb3dadc81         ##
##                                                                                            ##
##==============================================================================================
##                                                                                            ##
##   This script allows you to route what cannot be done with the reverse-proxy               ##
##   from DSM (Synology) to make Websocket notifications work                                 ##
##   Doc. vaultwarden :                                                                       ##
##        Route the /notifications/hub endpoint to the WebSocket server, by default           ##
##        at port 3012, making sure to pass the Connection and Upgrade headers.               ##
##        (Note the port can be changed with WEBSOCKET_PORT variable)                         ##
##        https://github.com/dani-garcia/vaultwarden/wiki/Enabling-WebSocket-notifications    ##
##                                                                                            ##
##==============================================================================================
##                                                                                            ##
##                            Principle of Task schedule to create                            ##
##                                                                                            ##
## It is necessary to run the script regularly because all changes made in the interface      ##
## DSM Reverse-Proxy graph will modify the configuration file. The same applies to            ##
## even when the NAS reboots.                                                                 ##
##                                                                                            ##
##==============================================================================================
##                                                                                            ##
##        /!\      The online IP address 47 must be changed to the NAS IP     /!\             ##
##                                                                                            ##
##==============================================================================================
##                                                                                            ##
## Script launch parameters :                                                                 ##
## bash /volume1/docker/bitwarden/enable_ws.sh vault.example.com 5555 5556                    ##
##                                                                                            ##
## -- vault.example.com = Vaultwarden domain name (that of DSM's Reverse Proxy)               ##
## -- 5555 = Port exposed ROCKET_PORT by Docker (Same as DSM Reverse Proxy)                   ##
## -- 5556 = Port exposed WEBSOCKET_PORT by Docker                                            ##
##                                                                                            ##
##==============================================================================================

LOC_DIR="/etc/nginx"
part1=0
part2=0
MY_DOMAIN=$1
PORT_ACCES=$2
PORT_CONT=$3
IP_NAS="192.168.178.254"
## Folgende Netzwerkbereiche dürfen auf /admin zugreifen
IP_LAN="192.168.178.0/24"
IP_VPN="192.168.177.0/24"

echo -e "\n$(date "+%R:%S - ") Script vaultwarden__Enable_Websocket.sh to enable Websockets Notifications"

f_affiche_parametre() {
  echo "          bash /volume1/docker/_Scripts-DOCKER/vaultwarden__Enable_Websocket.sh vault.example.com 5555 5556 "
  echo "                           -- vault.example.com = Vaultwarden domain name (that of DSM's Reverse Proxy) "
  echo "                           -- 5555 = Port exposed ROCKET_PORT by Docker (Same as DSM Reverse Proxy)"
  echo "                           -- 5556 = Port exposed WEBSOCKET_PORT by Docker"
}

if [ ! $# -eq 3 ]; then
  if [ $# -eq 0 ]; then
    # No parameters were provided. We will display the list of what can be used.
    echo "$(date "+%R:%S - ") No parameters provided! Review the script call :"
    f_affiche_parametre
  else
    echo "$(date "+%R:%S - ") The number of parameters provided is not correct! Review the script call :"
    f_affiche_parametre
  fi
  echo -e "$(date "+%R:%S - ") Failed to launch !!!!!!!!! script\n"
  exit 1
fi

echo "$(date "+%R:%S - ") Executing commands..."


#############################################################################################################
## Start of file creation/editing part
##
if [ -f $LOC_DIR/websocket.locations.vaultwarden ]; then
  rm $LOC_DIR/websocket.locations.vaultwarden
  part1=1
fi
echo """
location /notifications/hub/negotiate {
    proxy_http_version 1.1;
    proxy_set_header \"Connection\" \"\";
    
    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-Proto \$scheme;
    proxy_pass http://$IP_NAS:$PORT_ACCES;
}

location /notifications/hub {
    proxy_http_version 1.1;
    proxy_set_header Upgrade \$http_upgrade;
    proxy_set_header Connection \"upgrade\";

    proxy_set_header Host \$host;
    proxy_set_header X-Real-IP \$remote_addr;
    proxy_set_header Forwarded \$remote_addr;
    proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto \$scheme;
    proxy_pass http://$IP_NAS:$PORT_CONT;
}

location /admin {
    # See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
    # auth_basic Private;
    # auth_basic_user_file /path/to/htpasswd_file;

    # Restrict access to only some IP (LAN IP & VPN)
    allow $IP_LAN;
    allow $IP_VPN;
    allow $IP_NAS;
    deny all;

    proxy_http_version 1.1;
    proxy_set_header \"Connection\" \"\";
    
    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-Proto \$scheme;
    proxy_pass http://$IP_NAS:$PORT_ACCES/admin;
}



""" >>$LOC_DIR/websocket.locations.vaultwarden


# Note : with DSM7, the path of the server file. ReverseProxy.conf has changed 
#         DSM6.2  = /etc/nginx/app.d/server.ReverseProxy.conf
#         DSM7    = /etc/nginx/sites-enabled/server.ReverseProxy.conf
if ! grep -q "websocket.locations.vaultwarden" /etc/nginx/sites-enabled/server.ReverseProxy.conf; then

  # Functional commands with DSM6.2.x, but no longer with DSM 7.0 (RC)
  #sed -i "/$1;/ a\ include $LOC_DIR/websocket.locations.vaultwarden;" /etc/nginx/app.d/server.ReverseProxy.conf
  #if nginx -t 2>/dev/null; then synoservicecfg --reload nginx; else exit 1; fi

  # Functional commands with DSM 7 (RC)
  sed -r "s#^([[:blank:]]*server_name[[:blank:]]*${MY_DOMAIN}[[:blank:]]*;[[:blank:]]*)\$#\1\n\n\tinclude ${LOC_DIR}/websocket.locations.vaultwarden;#" /etc/nginx/sites-enabled/server.ReverseProxy.conf > /etc/nginx/sites-enabled/server.ReverseProxy.conf.new
  mv /etc/nginx/sites-enabled/server.ReverseProxy.conf.new /etc/nginx/sites-enabled/server.ReverseProxy.conf

  if nginx -t 2>/dev/null; then synosystemctl reload nginx; else exit 1; fi

  part2=1 # Variable to indicate that this part has been executed

fi
##
## End of file creation/editing part
#############################################################################################################

if [ $part1 -eq 1 ]; then
  echo "$(date "+%R:%S - ")    -- The file $LOC_DIR/websocket.locations.vaultwarden already existed, it was deleted and then recreated."
else
  echo "$(date "+%R:%S - ")    -- The file $LOC_DIR/websocket.locations.vaultwarden did not exist, it was created."
fi
if [ $part2 -eq 1 ]; then
  echo "$(date "+%R:%S - ")    -- !!!!!! --->  The change in the /etc/nginx/sites-enabled/server file. ReverseProxy.conf did not exist. It was written."
  echo "$(date "+%R:%S - ")    -- !!!!!! --->  The /etc/nginx/sites-enabled/server file. ReverseProxy.conf had to be reset after a reboot or when changing the reverse-proxy in DSM."
else
  echo "$(date "+%R:%S - ")    -- Editing the /etc/nginx/sites-enabled/server file. ReverseProxy.conf was already performed during a previous run. No changes are therefore necessary."
fi

echo "$(date "+%R:%S - ") Script vaultwarden__Enable_Websocket.sh Finished"

exit


Hier muss im Script die IP geändert werden, dann legen wir es im bitwarden docker verzeichnis ab. Dummerweise überschreibt sich bei Änderungen seitens DSM die Anpassung. Wir sollten das Script also einmal am tag in den „Geplante Aufgaben“ legen.
Kann so aussehen:


bash /volume1/docker/vaultwardenvaultwarden__enable_websocket.sh euredomain.de 35554 35556
Ihr müsst die Ports die ihr ganz oben definiert habt als parameter mitangeben.

Vieleicht noch eine kleine Warnung: Skripte mit „root“ durchlaufen zu lassen und am Webserver zu spielen könnte theoretisch dafür sorgen das euer Webserver an der Diskstation nicht mehr funktioniert. Ist unwahrscheinlich und funkionierte bisher immer sehr gut bei mir, ich wollte jedoch auf die theoretische Gefahr hinweisen die vor allem bei größeren Updates der DSM bestehen könnten.

Absichern mit Fail2Ban

Gut. An sich haben wir hier eigentlich alles fertig, jetzt kommt der versprochende „Fail2Ban“.
Was ist das und wozu brauch ich es:

Fail2Ban liest die log files zyklisch vom bitwarden und schaut ob es fehlerhafte loginversuche gab.
Und wenn man es zuoft versucht wird man für eine Zeit X gesperrt.

Der Schritt ist optional, ich empfehle es aber.

Was brauchen wir? Zuersteinmal das Docker image:

und wir brauchen folgende Mount-Pfade (ggfs. anpassen falls andere Pfade als oben gewählt wurden)

Sowie folgende Umgebungsvariablen:

Im /docker/fail2ban/filter.d/ legen wir eine „vaultwarden.local“ an mit folgenden Inhalt:

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.Username or password is incorrect. Try again. IP: . Username:.$
ignoreregex =

und im /docker/fail2ban/jail.d/ legen wir auch eine „vaultwarden.local“ an mit folgenden Inhalt:

[vaultwarden]
enabled = true
port = 80,443,8081,35554,35555
filter = vaultwarden
action = iptables-allports[name=vaultwarden]
logpath = /vaultwarden/bitwarden_log.log
maxretry = 3
bantime = 1 days
findtime = 10m
ignoreip = 127.0.0.1 192.168.178.0/24 8.8.8.8

Docker durchstarten und das ist es auch schon. Der Bitwarden schreibt seine logs wie ganz oben angegeben in /docker/vaultwarden/bitwarden_log.log und fail2ban liest besagte log file und sperrt ggfs die IP bei mehr als 3 Fehlversuchen binnen 10 Minuten für 1 Tag. Fühlt euch frei die Werte nach euren gusto anzupassen.