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 . 那里有直接安装二进制程序的方法。

安装依赖

需要 nginxdocker.

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

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-defaultserver 指向容器监听的地址。

另外针对 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.ymlenv 添加以下环境变量,并重新创建容器。

以 zoho 的 SMTP 服务为例:

SMTP_HOST: "smtp.zoho.com"
SMTP_FROM: "[email protected]"
SMTP_FROM_NAME: "Vaultwarden"
SMTP_SECURITY: "force_tls"
SMTP_PORT: "465"         
SMTP_USERNAME: "[email protected]"
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 有 andOTPAegis, 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 喵师傅亲自实践,帮我验证文章内容,更正了不少细节。

参考