View in English

建议结合 vaultwarden 的 wiki 食用,那边的解释很详细。本文仅为自己的部署历程,仅供参考。

前言

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

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

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

而且 LastPass 一天到晚出事。对此,隔壁的大狐狸苏卡卡表示:

见怪不怪(x)

对 LastPass 时隔数月才爆出安全事故感到震惊,因为 LastPass 本应该每两个月出现一次安全事故(o)


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

这玩意开源,虽然他们宣称是 zero knowledge 的,其实直接买他们的托管服务也可以,但我还是不想把底裤(付款信息)卖给 Bitwarden, 就打算亲自部署。

关于这玩意的教程很多,我第一次部署的时候是直接对着 ous50 的博客 照搬。

原版的 C# 后端有一堆起夜级功能,又笨又重。ous 在文章里提到了一个用 Rust 重写的实现,名叫 vaultwarden, 曾用名 bitwarden_rs. 这玩意轻了不止一点半点,还可以白嫖付费版功能,重要的二步验证手段 TOTP.

Disclaimer: 将 TOTP 与密码一起存储,会明显削弱二步验证的保护能力。一旦密码管理器遭到未授权访问,存有 TOTP 的登录凭证将直接沦陷。当然,每个人的威胁模型不同,各自斟酌就好。

前置需求

  • 一台可以分出 200MiB 运行内存和 1GiB 硬盘空间的 Linux 服务器。

    200MB 的 RAM 能让它长期稳定运行了。另外 1GiB 的硬盘空间是极其保守的估计,如果你不上传大文件,单人实例的数据可能连 10MB 的占用都没有。Docker 镜像自身大小在 250MB 以内。

  • 一个有效的域名。(这边用 bitwarden.example.com 来示例,记得换成你自己的。)

  • 带有这个域名的 SSL 证书。如果您不想买,可以 用 acme.sh 脚本免费申请

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

安装依赖

需要 nginx 和 docker.

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

Debian 11

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

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

sudo apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/debian `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list

echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | sudo tee /etc/apt/preferences.d/99nginx
    
sudo apt update
sudo apt install nginx

添加 docker 自有的镜像源

sudo apt-get update
# sudo apt-get install \
#     ca-certificates \
#     curl \
#     gnupg \
#     lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Arch Linux

sudo pacman -Syu nginx docker docker-compose

启动 Docker 容器

个人认为,使用 docker compose 部署更容易一些。

把作者在 docker hub 编译好的版本 pull 下来就能用了。

建议单开一个文件夹存放相关的数据。比如这样:

mkdir vaultwarden-docker
cd vaultwarden-docker

那么里面大概会有这些东西:

vaultwarden-docker
├── docker-compose.yml
└── vw-data
    ├── attachments
    ├── config.json
    ├── db.sqlite3
    ├── db.sqlite3-shm
    ├── db.sqlite3-wal
    ├── icon_cache
    ├── rsa_key.pem
    ├── rsa_key.pub.pem
    ├── sends
    └── tmp

docker compose 配置

其他不难,主要是环境变量麻烦。

vaultwarden 官方不建议使用 config.json 配置文件,而离谱的是它的的优先级偏偏又高于环境变量,请一定要注意,环境变量在 config.json 生成后(即进入 /admin 界面保存配置后)将被覆盖。 详见 wiki .

vaultwarden 的作者 dani-garcia 在 GitHub 上提供了一个 完整的 .env.template . 想要更改的环境变量直接添加到 compose file, 或者设定好外部 .env 文件,然后把示例拷贝过来,取消掉相应的注释。

我个人建议,在初次启动容器时就对照示例先设置好关键的环境变量,省得后面进 admin panel 去改。如果二者有互相冲突的配置,建议全部合并到环境变量中,并删除 config.json , 以免日后混淆。 以下列出几个我认为比较要命的环境变量。

  • Websocket 已经可以在 HTTP 端口上工作,即时通知 无需额外分配端口,该功能现已默认启用。

  • DOMAIN 实际上是你的 vaultwarden 所在的 URL. 至少要带有协议和完整的域名。URL path 是 optional 的,可以在一定程度上减少攻击面。参见 wiki 的解释。

  • ICON_SERVICE 默认为 internal, 为避免服务器从目标网站下载图标而被探测,使用 Bitwarden 的图标服务:所有对图标的请求会被服务器 302 到 bitwarden 的 icon server. 这会将你的隐私在一定程度上暴露给 Bitwarden(某个IP地址请求了哪些网站的图标,可能是在这些网站有凭证),但我认为还是比自己源站暴露要好多了。(参见 .env.template 中 ICON_SERVICE 的注释 和 Bitwarden 对网站图标问题的解释)

    • 同时设置 DISABLE_ICON_DOWNLOAD: "true" 禁止服务器自行下载图标。
  • ADMIN_TOKEN 存在时,能打开管理面板 /admin. 使用尽可能长而随机的 token 来抵抗暴力破解(比如用 openssl rand -base64 48 来生成)。

    • 现在甚至有 argon2id hash, 不需要明文 token 了。看看官方教你 怎么换成 hash . TL;DR: 使用 bitwarden 缺省设置,同时将 $ escape 掉,省得 compose 不认。

      echo -n "MySecretPassword" | argon2 "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4 | sed 's#\$#\$\$#g'
  • IP_HEADER 使用 X-Forwarded-For, 在 CDN 反代之后也可看到访客真实 IP 地址。

  • SIGNUPS_ALLOWED 设为 false 防止他人无端注册。

然后对照这个例子修改 docker-compose.yml 即可。

# docker-compose.yml
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"
      DOMAIN: "https://vw.domain.tld/vault/"
      ICON_SERVICE: "bitwarden"
      DISABLE_ICON_DOWNLOAD: "true"
      ADMIN_TOKEN: "$$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI"
      IP_HEADER: "X-Forwarded-For"
      SIGNUPS_ALLOWED: "false"

    volumes:
      - ./vw-data:/data

然后

docker compose up -d

就会自动拉取 image, 启动容器。

自行编译

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

配置反代

我不怎么喜欢使用 cloudflared, 虽然这样确实能不对外暴露端口。

如果您希望由服务器自身对外暴露端口,那么强烈建议您使用专门的反向代理,以提升安全性和响应性能。

可以直接参考 wiki 中的 反代示例 。

俺们只会 nginx 了。

为避免有人无端扫 443 探测域名,用 stream 模块来复用 443 端口,如果 SNI 不匹配就会被直接无视。

如果您不希望除 CDN 以外的任何访问者绕过 CDN 直接访问您的服务,非常建议参考这篇 隐藏源站 IP 的教程。(TL;DR: 使用 IP 白名单和 authenticated pull)

# /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;
    }
}

判断一下 host, 防止有人直接填 IP 访问。这还可以避免证书不匹配的问题(虽然用 stream 挡住以后没有什么必要了)。

注意 SSL 证书和密钥的路径。记得先签证书 。

屏蔽爬虫 bot.

注意 location 中的 subdirectory.

vaultwarden-default 的 server 指向容器监听的地址。

另外针对 admin 管理页面做了额外配置,添加了 basic auth. 不需要使用管理页面时也可以直接 return 502 之类的,或者添加环境变量 DISABLE_ADMIN_TOKEN: "true"

# /etc/nginx/conf.d/bitwarden.example.com.conf

# The `upstream` directives ensure that you have a http/1.1 connection
# This enables the keepalive option and better performance
#
# Define the server IP and ports here.
upstream vaultwarden-default {
    zone vaultwarden-default 64k;
    server 127.0.0.1:8080;
    keepalive 2;
}

server {
    listen 127.0.0.1:PORT_FOR_BW; ssl http2;
    server_name  bitwarden.example.com;
    # block bots
    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;
    }
    # Specify SSL Config when needed
    ssl_certificate /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/fullchain.pem;
    ssl_certificate_key /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/privkey.pem;
    ssl_trusted_certificate /path/to/certificate/letsencrypt/live/vaultwarden.example.tld/fullchain.pem;

    client_max_body_size 128M;

    location /vault/ {
        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://vaultwarden-default;
    }

    # Optionally add extra authentication besides the ADMIN_TOKEN
    # Remove the comments below `#` and create the htpasswd_file to have it active
    #
    #location /vault/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;
    #
    #    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://vaultwarden-default;
    #}
}

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

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

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

如果没报错的话应该就能用了。

在 CDN 部署防火墙和 TLS 规则

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

防火墙

防止著名爬虫引擎收录

Rule

Rule

TLS

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

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

配置域名解析

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

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

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

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

…

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

但据 Bitwarden 宣称,他们的方案是 zero knowledge 的,密码库的数据都是先加密后上传,TLS 是额外保护。对于 Send 同理。我过段时间去审一审代码,看看是不是真的

如果真是这样,那么 Cloudflare 就看不到你的密码,放心吧。

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

配置密码仓库

为避免保存配置导致环境变量被覆盖,admin 面板的配置仅供参考用,如需修改,请尽量使用环境变量,尽可能不要在面板上修改并保存,否则会带来 config.json 与环境变量冲突的问题。当然,如果您理解您在做什么,请无视我的这些话。

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

限制注册

若你不希望别人白嫖你自建的服务,那最好就在管理面板设置禁止注册。若您想拉人进来(包括你自己),使用邀请功能。

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

在环境变量中添加 SIGNUPS_ALLOWED: "false" (虽然如你所见,新版已经是默认 false 了)

(可选)设置 SMTP 服务

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

请注意:密码提示在数据库中是明文存储的,不要将密码的任何部分填写在其中!

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

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

若仅使用 SMTP 发件服务,我们推荐使用 zoho , 它可以免费绑定一个自定义域名,无需添加任何付款信息。

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

在先前的 docker-compose.yml 的 env 添加以下环境变量,并重新创建容器。

以 zoho 的 SMTP 服务为例:

SMTP_HOST: "smtp.zoho.com"
SMTP_FROM: "vaultwarden@domain.tld"
SMTP_FROM_NAME: "Vaultwarden"
SMTP_SECURITY: "force_tls"
SMTP_PORT: "465"         
SMTP_USERNAME: "vaultwarden@domain.tld"
SMTP_PASSWORD: "password"
SMTP_AUTH_MECHANISM: "Login"
SMTP_TIMEOUT: "15"

填入完整的 SMTP 配置后,会 自动允许使用 Email 二步验证 。

邀请

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

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

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

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

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

注册新用户

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

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

请注意:密码提示在数据库中是明文存储的,不要将密码的任何部分填写在其中!

修改用户 KDF 设置

upd: vaultwarden 已在 2023-05-27 添加了对 Argon2id 的支持。在 account settings > security > keys 调整对应设置,work factor 配置顺应 Bitwarden 官方默认值 即可。

目前 Bitwarden 使用 PBKDF2-HMAC-SHA256 作为主要的 KDF, 服务端和客户端的默认循环次数均为 100000 次。

而在 2022 年 12 月,OWASP 建议 将该算法的循环次数提升到 600000 次,以确保面对离线攻击时的安全性。

现在 Bitwarden 的客户端默认 hash 循环次数也已经达到建议值。

vaultwarden 大概会在下一个版本将服务器一侧的 hash iteration 的默认值提升到 600000. 但这仅能保护你的 master password hash.

但客户端一侧的 PBKDF2 目前还只有 100000 次 hash. 如果想要提升 master key 的循环次数,必须在账户设置中修改 KDF 设置。

请直接按照 Bitwarden 的文档来 修改 KDF 循环次数 。同时建议您阅读 Bitwarden 的 安全白皮书 。

启用二步验证

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

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

本地用 keepass 应该够了,如果您还想要其他选择:

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

Linux 可以用 OTPClient. 垃圾玩意一新增就崩溃 已经修好了

备份和还原

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

XD

建议参考 wiki 中关于备份的建议。

备份

存放 vaultwarden 数据的文件夹里,大概会有这些东西:

vaultwarden-docker
├── docker-compose.yml
└── vw-data
    ├── attachments
    ├── config.json
    ├── db.sqlite3
    ├── db.sqlite3-shm
    ├── db.sqlite3-wal
    ├── icon_cache
    ├── rsa_key.pem
    ├── rsa_key.pub.pem
    ├── sends
    └── tmp

备份用的指令可以用 cron 做成定时任务。

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

也可以 调用 sqlite3 命令行 。

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

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

Sends 则被存在 /sends 中。

压缩

可以先打包成一个文件。zstd 的压缩效果还不错,比较建议使用。比如这样:

tar --zstd -cvf vw-backup.tar.zst vaultwarden-docker

或者在本地使用 rsync 同步整个文件夹

rsync -avP -e "ssh -p$PORT" user@hostname:/path/to/your/vaultwarden-docker vaultwarden-docker

保存

存放在安全的地方。你能控制的地方。

最好事先再加密一次。

恢复

把你之前备份的文件送上去解压就行。

总结

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

至于其他的玩法,你不妨自己探索。经过两年多我也折腾了不少,但是真的懒得写啊

祝玩得开心。

致谢

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

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

感谢可爱的 miaotony 喵师傅亲自实践,帮我验证文章内容,更正了不少细节。

参考