Instal­ler un serveur Bitwar­den_rs

Avoir des mots de passe solides, c’est bien (et même indis­pen­sable), mais s’en souve­nir… c’est dur. On serait tenté de tout enre­gis­tré dans son navi­ga­teur, avec un mot de passe maître pour stocker les mots de passe de façon chif­frée mais… Fire­fox par exemple a un système de chif­fre­ment par mot de passe maître tout pourri.

Il existe un paquet de gestion­naire de mot de passe libre. Évacuons tous ceux basé sur KeePass ou ses succes­seurs : pour utili­ser ses mots de passe sur un autre PC ou sur son télé­phone, il faut synchro­ni­ser le fichier conte­nant les mots de passe et s’il y a un souci de synchro et que le fichier se retrouve corrom­pu… paf, pastèque !.

J’ai essayé Pass­bolt ou encore l’ap­pli­ca­tion Pass­man de Next­cloud mais quand j’uti­li­sais la sécu­rité maxi­male qu’ils propo­sent… mon Fire­fox plan­tait (oui, quand on me propose des réglages de parano, je les utilise). Sans comp­ter qu’ils ne proposent pas d’ap­pli Android.

Et puis, j’ai testé Bitwar­den. Et c’est pas mal du tout, mais… c’est du C# et ça utilise une base de données SQL Server. Deux trucs Micro­soft.

Bon, passant outre mon aver­sion, j’ai testé. Dans du Docker (quand je vous dis que je suis passé outre mon aver­sion !). C’était pas mal du tout.

Mais en voulant tester le partage de mot de passe (dans l’hy­po­thèse d’une utili­sa­tion chez Frama­soft), j’ai rencon­tré une limi­ta­tion : pour créer une orga­ni­sa­tion (un groupe de personnes qui se partagent des mots de passe) de plus de deux personnes, il faut payer, même quand on héberge soi-même le serveur Bitwar­den. Pourquoi pas. C’est un busi­ness model comme un autre (mais bon, grmpf).

Par contre, les tarifs, c’est un très gros grmpf : 3 dollars par utili­sa­teur et par mois. Pour Frama­soft, cela ferait 3$ * 35 personnes * 12 mois = 1 260$ par an. Y a un autre plan qui pour­rait conve­nir, qui nous revien­drait à 780$ par an. C’est quand même pas négli­geable (surtout pour une asso­cia­tion qui ne vit quasi­ment que de vos dons) alors qu’on héber­ge­rait nous-même le service.

J’avais laissé tombé quand ces derniers jours, une implé­men­ta­tion en Rust de Bitwar­den est passée dans ma time­line Masto­don : https://github.com/dani-garcia/bitwar­den_rs. Et celle-ci n’a pas la limi­ta­tion sur les équipes (mais il encou­rage à donner des sous au projet upstream).

J’ai d’abord testé avec le contai­ner Docker fourni par le déve­lop­peur puis j’ai tenté la compi­la­tion pour voir si ça passait, et ça passe crème !

La grosse diffé­rence, outre la non-limi­ta­tion des orga­ni­sa­tions, c’est que la base de données est, pour l’ins­tant, SQLite. Ce qui peut avoir des consé­quences sur les perfor­mances lorsque la base est très solli­ci­tée.

Ceci dit, Frama­drop tourne toujours avec une base SQLite et ça fonc­tionne bien, et mon instance Lutim, ainsi que Frama­pic ont long­temps utilisé SQLite.

Bref, voyons comment compi­ler et instal­ler cette version de Bitwar­den en Rust.

ATTENTION : allez plutôt voir le tuto­riel sur https://wiki.fiat-tux.fr/admin:logi­ciels:bitwar­den_rs, c’est plus simple pour moi de le main­te­nir sur mon wiki.

Compi­la­tion

On va avoir besoin des back­ports Debian pour instal­ler npm (pour compi­ler l’in­ter­face web) :

echo "deb http://ftp.debian.org/debian stretch-backports main" | sudo tee /etc/apt/sources.list.d/backports.list
sudo apt update

Instal­la­tion des dépen­dances :

sudo apt install pkg-config libssl-dev
sudo apt install -t stretch-backports npm

Instal­la­tion de rustup, qui nous four­nira le compi­la­teur Rust :

curl https://sh.rustup.rs -sSf > rustup.sh

On n’exé­cute pas direct un script tiré du web ! On regarde d’abord s’il ne va pas faire de salo­pe­ries :

vi rustup.sh

On le rend exécu­table :

chmod +x rustup.sh

On installe le compi­la­teur Rust (il sera dans notre $HOME) :

./rustup.sh --default-host x86_64-unknown-linux-gnu --default-toolchain nightly

On source un fichier qui nous permet de l’ap­pe­ler

source $HOME/.cargo/env

Gagnons du temps en clonant le projet bitwar­den_rs et l’in­ter­face web (qu’il faut compi­ler aussi) en même temps .

git clone https://github.com/dani-garcia/bitwarden_rs
git clone https://github.com/bitwarden/web.git web-vault

Compi­la­tion de Bitwar­den_rs :

cd bitwarden_rs
cargo build --release

Le résul­tat de la compi­la­tion est dans bitwarden_rs/target/release/.

Compi­la­tion de l’in­ter­face web :

cd ../web-vault
# On se positionne sur le dernier tag en date
git checkout "$(git tag | tail -n1)"
# Un petit patch pour que ça fonctionne avec notre installation
wget https://raw.githubusercontent.com/dani-garcia/bw_web_builds/master/patches/v2.8.0.patch
# On vérifie le patch
cat v2.8.0.patch
git apply v2.8.0.patch
npm run sub:init
npm install
npm run dist

ATTENTION : on m’a dit que la compi­la­tion de l’in­ter­face web prenait 1,5Gio de RAM, assu­rez-vous que vous en avez assez de libre.

Et on copie l’in­ter­face web dans le dossier où attend le résul­tat de la compi­la­tion de bitwar­den_rs :

cp -a build/ ../bitwarden_rs/target/release/web-vault/

Instal­la­tion

On va instal­ler Bitwar­den_rs dans /opt/bitwarden et on le fera tour­ner avec l’uti­li­sa­teur www-data :

cd ..
sudo rsync -a --info=progress2 bitwarden_rs/target/release/ /opt/bitwarden/
chown -R www-data: /opt/bitwarden

Puis on va créer un service systemd, /etc/systemd/system/bitwarden.service :

[Unit]
Description=Bitwarden Server (Rust Edition)
Documentation=https://github.com/dani-garcia/bitwarden_rs
After=network.target

[Service]
# The user/group bitwarden_rs is run under. the working directory (see below) should allow write and read access to this user/group
User=www-data
Group=www-data
# The location of the .env file for configuration
EnvironmentFile=/etc/bitwarden_rs.env
# The location of the compiled binary
ExecStart=/opt/bitwarden/bitwarden_rs
# Set reasonable connection and process limits
LimitNOFILE=1048576
LimitNPROC=64
# Isolate bitwarden_rs from the rest of the system
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
# Only allow writes to the following directory and set it to the working directory (user and password data are stored here)
WorkingDirectory=/opt/bitwarden/
ReadWriteDirectories=/opt/bitwarden/

[Install]
WantedBy=multi-user.target

Pour l’in­ter­face d’ad­mi­nis­tra­tion, on va créer un token avec :

openssl rand -base64 48

La confi­gu­ra­tion se fait via des variables d’en­vi­ron­ne­ment qu’on va mettre dans /etc/bitwarden_rs.env :

SIGNUPS_ALLOWED=false
WEBSOCKET_ENABLED=true
ADMIN_TOKEN=Un token généré avec `openssl rand -base64 48`
ROCKET_ADDRESS=127.0.0.1
WEBSOCKET_ADDRESS=127.0.0.1
SMTP_HOST=127.0.0.1
SMTP_FROM=bitwarden@example.org
SMTP_PORT=25
SMTP_SSL=false

Vous remarque­rez que je dis à Bitwar­den d’en­voyer les mails via le serveur SMTP local. À vous de faire en sorte qu’il fonc­tionne. Allez voir le wiki du projet pour voir quelles variables vous pour­riez ajou­ter, enle­ver, modi­fier…

Puis :

sudo systemctl daemon-reload
sudo systemctl enable bitwarden
sudo systemctl start bitwarden
sudo systemctl status bitwarden

Nginx

On installe Nginx s’il n’est pas déjà installé :

sudo apt install nginx

Confi­gu­ra­tion du virtual­host :

server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name bitwarden.example.org;

    access_log /var/log/nginx/bitwarden.access.log;
    error_log /var/log/nginx/bitwarden.error.log;

    ssl_certificate      /etc/letsencrypt/live/bitwarden.example.org/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/bitwarden.example.org/privkey.pem;

    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:5m;

    ssl_prefer_server_ciphers On;
    ssl_protocols TLSv1.2;
    ssl_ciphers 'EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA';

    ssl_dhparam /etc/ssl/private/dhparam4096.pem;
    add_header Strict-Transport-Security max-age=15768000; # six months
    gzip off;

    if ($https != 'on') {
        rewrite ^/(.*)$ https://bitwarden.example.org/$1 permanent;
    }

    root /var/www/html;

    # Allow large attachments
    client_max_body_size 128M;

    location ^~ '/.well-known/acme-challenge' {
        default_type "text/plain";
        root /var/www/certbot;
    }

    location / {
        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://127.0.0.1:8000;
    }

    location /notifications/hub {
        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 Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:3012;
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8000;
    }
}

Pour créer /etc/ssl/private/dhparam4096.pem :

sudo openssl dhparam -out /etc/ssl/private/dhparam4096.pem 4096

Pour le certi­fi­cat Let’s Encrypt, on commente le brol rela­tif à ssl puis :

sudo nginx -t && sudo nginx -s reload
sudo apt install certbot
sudo mkdir /var/www/certbot/
certbot certonly --rsa-key-size 4096 --webroot -w /var/www/certbot/ --agree-tos --text --renew-hook "/usr/sbin/nginx -s reload" -d bitwarden.example.org

Une fois qu’on a le certi­fi­cat, on décom­mente le brol ssl puis :

sudo nginx -t && sudo nginx -s reload

Sauve­garde

Créer le script de sauve­garde /opt/backup_bitwarden.sh :

#!/bin/bash
function bwbackup {
    DATE=$(date '+%a%H')

    # Database
    if [[ ! -d /opt/backup_bitwarden/sqlite-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/sqlite-backup/
    fi
    echo ".backup /opt/backup_bitwarden/sqlite-backup/db.${DATE}.sqlite3" | sqlite3 /opt/bitwarden/data/db.sqlite3 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden database backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden database backup" youraddress@mail.example.org
        bwbackup
    fi

    # Files
    if [[ ! -d /opt/backup_bitwarden/files-backup/ ]]
    then
        mkdir -p /opt/backup_bitwarden/files-backup/
    fi
    rsync -a --delete --exclude db.sqlite3 /opt/bitwarden/data/ /opt/backup_bitwarden/files-backup/$DATE/ 2>> /opt/backup_bitwarden/backup.log
    if [[ "$?" -ne "0" ]]
    then
        echo "Something went wrong with bitwarden files backup, please see /opt/backup_bitwarden/backup.log on verity" | mail -s "Bitwarden files backup" youraddress@mail.example.org
        bwbackup
    fi
}
bwbackup

Puis :

sudo chmod +x /opt/backup_bitwarden.sh
sudo mkdir /opt/backup_bitwarden
sudo chown www-data: /opt/backup_bitwarden
sudo apt install sqlite3

Puis, dans le cron de l’uti­li­sa­teur www-data :

42 4 * * * /opt/backup_bitwarden.sh

Logs

J’aime bien avoir mes logs dans un dossier dédié pour ce genre de service.

Dans /etc/rsyslog.d/bitwarden.conf :

if $programname == 'bitwarden_rs' then /var/log/bitwarden/bitwarden.log
if $programname == 'bitwarden_rs' then ~

Dans /etc/logrotate.d/bitwarden :

/var/log/bitwarden/bitwarden.log
{
        rotate 52
        dateext
        weekly
        missingok
        notifempty
        compress
        sharedscripts
        postrotate
                invoke-rc.d rsyslog rotate > /dev/null
        endscript
}

Puis :

sudo mkdir /var/log/bitwarden
sudo chown root:adm /var/log/bitwarden
sudo service rsyslog restart

Fail2­ban

Un fail2­ban qui surveille les logs, ça permet de bloquer les petits malins qui font du brute­force

Dans /etc/fail2ban/filter.d/bitwarden.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <HOST>\. Username:.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden.local :

[bitwarden]
enabled = true
port = 80,443
filter = bitwarden
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Pour la page d’ad­min, dans /etc/fail2ban/filter.d/bitwarden-admin.conf :

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*Unauthorized Error: Invalid admin token\. IP: <HOST>.*$
ignoreregex =

Dans /etc/fail2ban/jail.d/bitwarden-admin.local :

[bitwarden-admin]
enabled = true
port = 80,443
filter = bitwarden-admin
action = iptables-allports[name=bitwarden]
logpath = /var/log/bitwarden/bitwarden.log
maxretry = 3
bantime = 14400
findtime = 14400

Fina­le­ment :

sudo service fail2ban restart

Conclu­sion

Voilà, vous devriez avoir un serveur Bitwar­den_rs fonc­tion­nel. Plus qu’à aller sur l’in­ter­face web que vous venez de mettre en place ou télé­char­ger les clients et à les utili­ser !

Pour impor­ter vos mots de passe de Fire­fox, il faut passer par une appli­ca­tion pour les expor­ter, puis aller dans les outils de votre client (ou de l’in­ter­face web).

EDIT 15/01/2019 Rempla­ce­ment de la mention de l’ap­pli­ca­tion Next­cloud Pass­words par l’ap­pli­ca­tion Pass­man (je me suis trompé).

EDIT 17/01/2019 Rempla­ce­ment de npm run dist par npm run dist:selfhost dans la compi­la­tion de l’in­ter­face web. Cela désac­tive Google Analy­tics (voir https://github.com/bitwar­den/web/issues/243#issue­comment-412852726)

EDIT 18/01/2019 Rempla­ce­ment de npm run dist:selfhost par npm run dist dans la compi­la­tion de l’in­ter­face web. Google Analy­tics est déjà désac­tivé par le patch. Il semble­rait que j’ai vu passer des requêtes vers Google Analy­tics à cause du cache de mon navi­ga­teur qui gardait une version non patchée.

EDIT 18/01/2019 Ajout d’un aver­tis­se­ment sur la consom­ma­tion de RAM lors de la compi­la­tion de l’in­ter­face web.

Crédits : Photo par Matt Artz sur Unsplash

Chan­ger ses mots de passe rapi­de­ment avec Salt

Préam­bule

Salt est un logi­ciel de gestion de confi­gu­ra­tion comme Puppet ou Ansible.

Je l’uti­lise chez Frama­soft et sur mon infra person­nelle parce que je l’aime bien :

  • rapide ;
  • très bien docu­menté ;
  • syntaxe claire, acces­sible mais néan­moins flexible et puis­sante.

Je change mes mots de passe régu­liè­re­ment (une fois par an envi­ron). C’est toujours galère à faire quand on gère une tripo­tée de serveurs (entre les serveurs physiques et les VMs, on en est à 80 serveurs chez Frama­soft).

Avant, je faisais ça à la main : je lançais mssh sur 4, 6 ou 8 serveurs à la fois, et je modi­fiais mon mot de passe à la main. Mais ça, c’était avant.

Salt à la rescousse

Pour chan­ger le mot de passe de l’uti­li­sa­teur bar sur le serveur foo avec salt, on fait :

salt foo shadow.set_password bar "$6$saltSALT$HASHEDPASSWORD"

$6$saltSALT$HASHEDPASSWORD corres­pond à votre mot de passe salé et hashé. Vous retrou­vez un brol du genre dans votre /etc/shadow (Oh ! Vous avez remarqué ? C’est le nom du module salt qui permet de modi­fier votre mot de passe ! C’est bien fait quand même 😁)

Pour créer un sel (le saltSALT, rien à voir avec le logi­ciel), j’uti­lise mkpasswd.pl, fourni par le paquet Debian libstring-mkpasswd-perl :

mkpasswd.pl -l 8 -s 0

Pour créer l’en­semble $6$saltSALT$HASHEDPASSWORD, vous pouvez utili­ser python :

python -c "import crypt; print crypt.crypt('PASSWORD', '\$6\$saltSALT')"

Bon, on sait comment faire, mais on ne va pas s’amu­ser à taper 80 fois ces commandes !

Salt permet de véri­fier que les minions (les agents Salt) répondent bien avec cette commande :

salt foo test.ping

Ce qui donne :

foo:
    True

On va chan­ger le format de sortie :

salt foo --out text test.ping

Ce qui nous donne :

foo: True

Bien ! On peut pinguer d’un coup tous les minions avec :

salt \* --out text test.ping

On a donc la liste des minions, la commande pour chan­ger le mot de passe… on va mixer tout ça :

salt \* --out text test.ping | \
 sed -e "s@\([^:]*\):.*@echo salt \1 shadow.set_password bar \\\\\"\$(python -c \"import crypt; print crypt.crypt('PASSWORD', '\\\\\$6\\\\$\$(mkpasswd.pl -l 8 -s 0)')\")\\\\\"@"

1ère regex : on dégage les : True, et la deuxième, on enrobe le nom du minion pour que ça nous donne un truc comme :

echo salt foo shadow.set_password bar \"$(python -c "import crypt; print crypt.crypt('PASSWORD', '\$6\$$(mkpasswd.pl -l 8 -s 0)')")\"

Quand on exécute ça, ça donne un truc genre :

salt foo shadow.set_password bar "$6$8qJhzAi6$.O8bOisJaM9fH05aXx7xnKXOVFoI9CRzjORFWDqoPR/TBOiYVZUEJKtUKirNMyaZJvJMYPVUMhnNry9QPJgHK/"

Bien évidem­ment, on va mettre ça dans un fichier qu’on va éditer pour modi­fier le mot de passe (bah oui, on va quand même pas mettre le même mot de passe sur tous les serveurs).

salt \* --out text test.ping | \
 sed -e "s@\([^:]*\):.*@echo salt \1 shadow.set_password bar \\\\\"\$(python -c \"import crypt; print crypt.crypt('PASSWORD', '\\\\\$6\\\\$\$(mkpasswd.pl -l 8 -s 0)')\")\\\\\"@" > /tmp/chpasswd.txt

On édite /tmp/chpasswd.txt pour mettre ses mots de passe bien comme il faut puis :

bash /tmp/chpasswd.txt | sed -e "s@[$]@\\\\\$@g" | bash

Le echo va nous sortir la commande kiva­bien qui sera inter­pré­tée par bash. Le $(mkpasswd.pl -l 8 -s 0)') sera remplacé par un sel diffé­rent à chaque fois et le bout de python trans­for­mera le sel et le mot de passe en hash dans le format kiva­bien pour le fichier /etc/shadow, les $ seront échap­pés (\$) grâce à sed et la commande salt sera lancée sur chaque minion.

Et voilà 🙂

Crédits : photo par Nikita Andreev