我是如何部署 Vaultwarden 的
建议结合 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 的服务,不保证大陆访问的稳定性,也不能保证对旧设备的兼容性 。
防火墙
防止著名爬虫引擎收录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 这样 …
建议参考 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
喵师傅亲自实践,帮我验证文章内容,更正了不少细节。