部署兼容原版 Bitwarden 的后端

Last updated on 2022-02-11, Fri, 06:44 PM

View in English

Update:

vaultwardenwiki 最近基本完工了,内容更详细。

前言

从前,我都是在自己的设备上明文存储密码。

每次忘掉登录凭证的时候,我就得解锁加密的分区,然后复制粘贴密码。从各种意义上都很危险。

我也不知道怎样才能找到一个可信的托管。有些大厂,比如谷歌和苹果,提供了密码管理服务。可是你敢用吗?反正我不。我也不大敢用 1passwordLastPass 之类的付费/私有密码管理器。

然后咱从 ous50 那里听说了 Bitwarden 的存在。

这玩意开源,可以在自己的机器上部署。虽然他们也提供托管,但我还是想自己来。

关于这玩意的教程很多,我则是直接对着 ous50 的教程照搬。

附上他写的文章。我这篇文章的大部分内容,你都可以在他那里看到。

https://blog.ous50.moe/2021/03/12/vaultwarden%E6%90%AD%E5%BB%BA/

这一回,我不部署原版的后端,而是一个用 Rust 重写的实现,名叫 vaultwarden, 曾用名 bitwarden_rs. 这玩意可以白嫖大部分的付费版功能,比如重要的二步验证手段 TOTP.

前置需求

如果你的机器带不动 docker, 请直接去看 vaultwarden 在 GitHub 的 wiki . 那里有直接安装二进制程序的方法。

  • 一台可以分出 200MiB 运行内存和 1GiB 硬盘空间的 Linux 服务器。
  • 一个有效的域名。(这边我用 bitwarden.example.com 来示例,记得换成你自己的。)

若你想自行编译 docker 镜像,那需要 500MiB 的 RAM 和至少 5GiB 的硬盘空间。

安装依赖

要事先安装一些软件依赖。

这里你需要 nginxdocker.

当然,还有一个你喜欢的文本编辑器。

Debian 10

为了添加 stream_ssl_preread 模块,我们需要 nginx 的版本高于 1.19.2[1].

把 NGINX 自己维护的镜像源添加进去,以追上主线版本。

wget https://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key
sudo echo "deb https://nginx.org/packages/mainline/debian/ buster nginx" >> /etc/apt/sources.list
sudo echo "deb-src https://nginx.org/packages/mainline/debian/ buster nginx" >> /etc/apt/sources.list
sudo apt update
sudo apt install docker nginx -y

Arch Linux

sudo pacman -Syy
sudo pacman -S nginx docker

部署防火墙和 TLS 规则

注意:以下配置方法基于极易获得的 CloudFlare 和 Let’s Encrypt 的服务,不保证大陆访问的稳定性,也不能保证对旧设备的兼容性

先签好 证书

防火墙

挡住爬虫机器人。

Rule

Rule

TLS

把所有能开的优化都开出来。

(自行选择)把最低 TLS 版本设置成 1.3 以防止降级。

俺们觉得 HSTS 有点麻烦,就放最后做。

配置反代

使用专门的反向代理软件,可以更好地处理请求。

俺们只会 nginx 了。用 apache 或者 caddy 也没问题,选你最熟悉的。

可以用 stream 模块来复用端口。另外记得加一个 server.

# /etc/nginx/conf.d/stream.conf
stream {
    map $ssl_preread_server_name $name {
       sub1.example.com svc1;
       sub2.example.com svc2;
       bitwarden.example.com bw;
    }
    upstream svc1 {
        server 127.0.0.1:PORT_TO_USE_1; 
    }
    upstream svc2 {
        server 127.0.0.1:PORT_TO_USE_2; 
    }
    upstream bw {
        server 127.0.0.1:PORT_FOR_BW;
    }
    server {
        listen 443 reuseport;
        listen [::]:443 reuseport;
        proxy_pass	$name;
        ssl_preread on;
    }
}

或者直接加一个 server 监听 443 端口就成。

# /etc/nginx/conf.d/bitwarden.example.com.conf
    server {
        listen      443 ssl http2;
        listen [::]:443 ssl http2;
        server_name  bitwarden.example.com;

判断一下 host, 防止有人直接填 IP 访问。这还可以避免证书不匹配的问题。另外,注意一下 SSL 证书和密钥的路径。

记得屏蔽 bot.

分流了一下 websocket 连接,可以用来做 即时通知proxy_pass 的端口随你设置。

另外针对 /admin 管理页面做了额外配置,添加了 basic auth. 不需要使用管理页面时也可以直接 return XXX, 怎么乱来都行。

# /etc/nginx/conf.d/bitwarden.example.com.conf
    server {
        listen 127.0.0.1:PORT_FOR_BW; ssl http2;
        server_name  bitwarden.example.com;
        if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|^$") {  
            return 404;
        }

        # Block Direct Access via IP
        if ($host != "bitwarden.example.com") {
            return 404;
        }

        # HSTS    
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; 
  
        ssl_certificate /etc/nginx/ssl/bitwarden.example.com_ecc/fullchain.cer;
        ssl_certificate_key /etc/nginx/ssl/bitwarden.example.com_ecc/bitwarden.example.com.key;

        keepalive_timeout   70;

        # OCSP stapling
        ssl_stapling        on;
        ssl_stapling_verify on;
        resolver 1.0.0.1; ## DNS

        ssl_protocols TLSv1.3;
        ssl_prefer_server_ciphers on; 

        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://localhost:8080; 
        }

        location /notifications/hub {
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $http_connection;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://localhost:3012;
        }

        location /notifications/hub/negotiate {
            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://localhost:8080;
        }

        # Optionally add extra authentication besides the ADMIN_TOKEN
        # If you don't want this, leave this part out
        location /admin {
            # Return 404 when /admin is not needed, if you have safety concern. 
            
            # return 404;
        
            # Basic Auth for /admin See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
            auth_basic "Private";
            auth_basic_user_file /etc/nginx/bitwarden_admin_basic_auth;

            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://localhost:8080;
        }
    }

然后在 /etc/nginx/nginx.conf 里面 include 一下这两个文件就行。

配置好以后,记得(重新)起一下服务。

nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx --force

准备 Docker

把编译好的镜像 pull 下来就能用了。

config.json 或环境变量存在 ADMIN_TOKEN 时,能打开管理面板 https://bitwarden.example.com/admin

主程序启动时会从配置文件 config.json 中读取 ADMIN_TOKEN , 而如果同时存在环境变量,会优先使用环境变量的赋值。

使用尽可能长而随机的 token 来抵抗暴力破解。

当且仅当环境变量有 WEBSOCKET_ENABLED=true 时,可以启用 即时通知

这里假设你将 vaultwarden 的数据保存在当前工作目录下的 vw-data 文件夹中。

sudo docker pull vaultwarden/server:latest

sudo docker run -d \
  --name=vaultwarden \
   -e WEBSOCKET_ENABLED=true \
   -e ADMIN_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
   -e LOG_FILE/data/bitwarden.log \
   -p 127.0.0.1:8080:80 \
   -p 127.0.0.1:3012:3012 \
   -v ./vw-data/:/data/ \
  --restart=always  \
vaultwarden/server:latest

参数

给出一些参数的解释。

  • -p 完整写作 --publish, 将容器的一个端口暴露到主机的一个端口上。

    HOST_PORT:CONTAINER_PORT

    绑定到 localhost 确保不会直接暴露到公网。

  • -v, 也就是 --volume, 则是把主机的一个目录挂载到容器内的某个路径。

    HOST_DIR:CONTAINER_DIR

    主机和容器都可以读写这个卷,而且删除容器不影响外置卷。

  • --restart=always 保证服务在掉线后自动重启,类似进程守护。

  • --name 是容器的名字,随便取。

  • -e 引入一些环境变量。

使用 docker-compose

一目了然。

version: '3'

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped 
    ports:
      - 127.0.0.1:8080:80
      - 127.0.0.1:3012:3012
    environment:
      - WEBSOCKET_ENABLED: "true"
      # - ADMIN_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    volumes:
      - ./vw-data:/data

然后直接

docker-compose up -d

容器就起来了。

自行编译镜像

如果想要自行编译镜像,不妨看看 这里 。但我不推荐这样做。

自行编译非常吃资源。但还是放出示例。

git clone https://github.com/dani-garcia/vaultwarden.git
cd vaultwarden
sudo docker build -t vaultwarden .

sudo docker run -d \
  --name=vaultwarden_built \
   -e WEBSOCKET_ENABLED=true \
   -e ADMIN_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
   -e LOG_FILE/data/bitwarden.log \
   -p 127.0.0.1:8080:80 \
   -v /vw-data/:/data/ \
  --restart=always  \
vaultwarden:latest

应该就能跑起来了。

配置域名解析

服务起来了,就可以暴露到公网了。

bitwarden.example.com 解析到你的机器上。

打开云朵是个好主意,可以加快载入,抵抗攻击并隐藏源站。

如果你没有固定的 IP, 那也可以 CNAME 到你的 DDNS 上面。估计你也会的。

我知道有人担心安全问题。毕竟 Cloudflare 的 CDN 可以看到 payload.

但别担心。按照 Bitwarden 的 文档 ,所有的数据都是先加密后上传,TLS 只是额外保护罢了。

对于 Send 同理

所有的数据来回都是加密的,Cloudflare 看不到你的密码们,放心吧。

看到原数据的唯一方式是掌握你的主密码。要确保它足够强大,能扛过暴力攻击,这才是最重要的。

配置密码仓库

当然也可以用配置文件 config.json 和环境变量去修改设置。vaultwarden 的作者 dani-garcia 在 GitHub 上 提供了一个完整的 .env.template.

打开 https://bitwarden.example.com/admin 用之前设定的 ADMIN_TOKEN 登录。

限制注册

若你不希望别人白嫖你自建的服务,那最好就在管理面板设置禁止注册。

拿掉 Allow new signups 的钩子,防止他人注册。

(可选)设置 SMTP 服务

设置一个 SMTP 服务器,可以用来发发验证邮件和密码提示。

Yandex 360 就很不错。它是有免费版的,完全够用

We stand with Ukraine. 鉴于 Yandex 所处的境地,我们不再建议使用它的服务。

若仅使用 SMTP 发件服务,我们推荐使用 zoho , 它可以免费绑定一个自定义域名。

当然,注册的事情就不放在这里说了。

您也可以开启 Email 二步验证。

切记!务必保存!

修改了设置以后

务必保存!

务必保存!

务必保存!

否则修改的内容将会丢失!

邀请

若您想拉人进来,大可使用邀请功能。

切换到 Users 然后在 “Invite User” 下输入对方的邮箱地址。这样就可以在数据库增加新的账户,并且是 Verified 状态。然后这个新用户就可以无视 Allow new signups 的限制了。

顺手鲨了所有认不得的申必用户。

等他们创建完帐号,就可以进入自己的仓库,管理面板上的 Invited 标签也会消失。

这样就可以安全地新增用户,包括你自己。

注册新用户

邀请过自己以后,去 bitwarden.example.com 注册一个账户。

牢记你的密码!这玩意可没法找回或者重置! 万一你忘掉了,就只能建立新账户了。

启用二步验证

然后去 Settings -> Two-step Login 设置 Authenticator App 启用二步验证。基于 TOTP 的六位验证码有很多玩意都支持。很多平台都有很优秀的应用,别用 Authy.

首先强烈推荐用 keepass 来存储 TOTP 凭证。各个平台均有适配的客户端,格式统一,加密强度高,单文件密码库备份简易,自动填充功能强大,字段齐全并且还能拓展,又能支持 TOTP/HOTP/STEAM OTP, 是一款优秀的本地密码管理器。

Android 有 andOTPAegis, Google Play 和 F-Droid 上都能下到。

Linux 可以用一个 GTK 写的过气玩意 OTPClient.

备份和还原

你大可完整备份一下自己的数据库。保不准哪天你的机器可能就完蛋,从而丢失数据,就像 OVH 这样

XD

备份

这里假设你将 vaultwarden 的数据保存在当前工作目录下的 vw-data 文件夹中。

可以用管理面板来备份数据。备份的数据库会被命名为 db_YYYYMMDD_HHMMSS.sqlite3.

也可以调用 sqlite3 命令行。 Reference

sqlite3 ./vw-data/db.sqlite3 "VACUUM INTO './vw-data/db_$(date '+%Y%m%d-%H%M').sqlite3'"

这个数据库是不包括密码管理器配置文件以及网站图标的,只有保存的用户密码和笔记。附件在 /attachments, 如果你在密码仓库里加了附件,那必须备份这个文件夹。

Sends 则被存在 /sends 中。

压缩

可以先打包成一个文件。比如这样:

tar --zstd -cvf backup.tar.zst ./vw-data

若要尽量减小体积,可以这样做:

tar --zstd -cvf 'vw-data.tar.zst' \
    ./vw-data/attachments \
    ./vw-data/config.json \
    ./vw-data/db*.sqlite3 \
    ./vw-data/rsa* 

单独备份 /sends 文件夹。

tar --zstd -cvf 'vw-data-sends.tar.zst' ./vw-data/sends

然后把这个传输到本地。 强烈建议使用安全的协议来传输,直接下载到已经加密的目录下。比如说,你可以用 veracrypt 起一个加密卷然后挂载上去。用 cd 或者直接在那里打开,把终端的路径切过去。然后用 scp 或者 rsync 通过 SSH 传输。

scp -P$PORT username@host:~/backup.tar backup.tar 

不压缩

不想压缩就直接跳过这步。

记得把 $PORT 换成你自己的。

rsync -av --progress \
    --include "attachments" \
    --include "config.json" \
    --include "db*.sqlite3" \
    --include "rsa*" \
    --exclude "*" \
    -e "ssh -p$PORT" user@hostname:/path/to/your/vw-data vw-data

仍然单独备份 /sends 文件夹。

rsync -av --progress -e "ssh -p$PORT" user@hostname:/path/to/your/vw-data/sends  vw-data/

或者全量备份,直接同步整个文件夹:

rsync -av --progress -e "ssh -p$PORT" user@hostname:/path/to/your/vw-data vw-data

保存

卸载你的设备,存放在安全的地方。当然,也可以把加密卷复制到你能控制的地方。

恢复

这个也很简单。

把你之前备份的文件安全地送上去就行。

scp -P$PORT backup.tar username@host:~/backup.tar 

然后解压。

tar -C /path/to/your/ --zstd -xvf backup.tar.zst

你也可以选择把 db_YYYYMMDD_HHMMSS.sqlite3 改名叫 db.sqlite3, 以恢复到某个时间的备份。

想少传一点:

rsync -av --progress \
    --include "attachments" \
    --include "config.json" \
    --include "db.sqlite3" \
    --include "rsa*" \
    --exclude "*" \
    -e "ssh -p$PORT" vw-data/ user@hostname:/path/to/your/vw-data/

/sends 还是单独对待:

rsync -av --progress -e "ssh -p$PORT" vw-data/sends user@hostname:/path/to/your/vw-data/ 

直接全部上传:

rsync -av --progress -e "ssh -p$PORT" vw-data/ user@hostname:/path/to/your/vw-data/

要求重新同步

当然,最好还是让用户(说白了也就你自己)重新同步一下数据。

到管理界面选择 Users 然后设定一次强制同步。

总结

这款强大的密码仓库,我暂时也就学了这些基本的部署和维护。

至于其他的玩法,你不妨自己探索。

祝玩得开心。

致谢

再次感谢 ous50 的教程。我在自建过程中遇见许多问题。仰仗了他的帮助,我才得以成功部署。

同时感谢 billchenchina 对本文结构的改进。

参考


部署兼容原版 Bitwarden 的后端
https://blog.h3a.moe/src/d07395/
Author
H3arn
Posted on
2021-10-13, Wed, 11:53 PM
Updated on
2022-02-11, Fri, 06:44 PM
Licensed under