一直以来为了方便都是用的 Chrome 自带的密码管理器,虽然很早以前就知道 Chrome 密码没有本地加密,似乎谷歌的逻辑是箱子(操作系统)是安全的,箱子里(Chrome 保存的密码)就是安全的,看起来没啥大毛病,但现实是总会被迫安装各种喜欢扫盘的软件,迫于懒,一直没折腾,现在还是下定决心,研究下使用 vaultwarden 自建密码管理器。
想必愿意折腾自建密码管理器的朋友都已经对 vaultwarden (bitwarden_rs) 有所了解,这里不再介绍,直接进入正题。
搭建 vaultwarden
所有操作来自于官方文档,系统环境为 Debian 11 (Bullseye)。
启动 vaultwarden 服务
运行 vaultwarden 一个最简单的方法是使用 Docker 启动一个容器:
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
Docker 的安装方法可以参考文章: Debian 系统安装 Docker 教程
vaultwarden 常用环境变量
显然,要自建一个安全可靠的密码管理器没有这么简单,还需要进行一系列的配置,你可以通过设置环境变量或在 admin 管理页面(会将设置写入 data
目录下的 config.json
文件)来配置 vaultwarden。注意,config.json
中的设置会覆盖相应的环境变量设置,因此推荐两种方式二选一。完整的环境变量参数可以参考 .env.example,通过 docker run --env-file <env-file>
或者 docker run -v /path/to/.env:/.env
来使用自定义环境变量,下面是一些我使用到的参数,仅供参考:
SIGNUPS_ALLOWED=false
:禁止注册新用户INVITATIONS_ALLOWED=false
:禁止邀请用户,即使设置了禁止注册/邀请用户,在 admin 页面仍然可以邀请用户ADMIN_TOKEN=some_long_long_random_token
:开启 admin 管理页面,推荐使用openssl rand -base64 48
命令来生成一个长 token 来保护 admin 页面,开启 admin 页面后会在DATA_FOLDER
目录下生成一个config.json
文件,如果后期需要关闭 admin 页面,需要同时删除环境变量和config.json
文件中的admin_token
参数LOG_FILE=/data/vaultwarden.log
:输出日志,默认使用 stdout 输出日志,可以通过设置该参数输出到文件SHOW_PASSWORD_HINT=false
:关闭密码提示
为了方便,这里使用 docker-compose.yml
启动服务:
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: always
environment:
- TZ=Asia/Shanghai
- SIGNUPS_ALLOWED=false
- INVITATIONS_ALLOWED=false
- ADMIN_TOKEN=some_long_long_random_token
- LOG_FILE=/data/vaultwarden.log
- LOG_LEVEL=warn
- SHOW_PASSWORD_HINT=false
- DOMAIN=https://vw.domain.tld
volumes:
- ./vw_data:/data
ports:
- 127.0.0.1:8080:80
启动服务:docker compose up -d
,其中 SIGNUPS_ALLOWED
和 INVITATIONS_ALLOWED
请在第一次登录创建用户后再添加重启服务。
更新 vaultwarden:
docker compose down
docker compose pull
docker compose up -d
安全设置
常规的安全设置如关闭注册/邀请、关闭密码提示已经在上面的环境变量中设置。
Nginx 设置
-
开启 HTTPS,你可以 使用 acme.sh 自动签发和更新证书 。
-
禁止 IP 访问,并设置
ssl_reject_handshake
(Nginx ≥ 1.19.4) 参数防止通过 HTTPS 访问你的 IP 时暴露证书,配置 nginx.conf 中的默认设置:
server {
listen 80 default_server;
server_name _;
return 444;
}
server {
listen 443 ssl default_server;
ssl_reject_handshake on;
ssl_protocols TLSv1.2 TLSv1.3;
}
- Nginx 反代设置:
upstream vaultwarden-default {
zone vaultwarden-default 64k;
server 127.0.0.1:8080;
keepalive 2;
}
server {
listen 80;
listen [::]:80;
server_name vw.domain.tld;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vw.domain.tld;
ssl_certificate /path/to/certificate/fullchain.pem;
ssl_certificate_key /path/to/certificate/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;
client_max_body_size 128M;
location / {
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;
}
}
如果你开启了 Websocket 通知或者需要使用 htpasswd 来为 admin 页面添加额外的保护,请参考完整的配置文件。
fail2ban 设置
你可以通过搭配 fail2ban 来防止暴力破解密码,进一步增加安全性,关于 fail2ban,可以参考: Debian 下 Nginx 配合 Fail2Ban 减少恶意扫描和攻击 ,这里只列出 filter 和 jail 配置。
- web 页面配置
Filter,进入 /etc/fail2ban/filter.d
目录,新建 vaultwarden.local
:
# /etc/fail2ban/filter.d/vaultwarden.local
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =
Jail,进入 /etc/fail2ban/jail.d
目录,新建 vaultwarden.local
,注意修改日志文件路径:
# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled = true
port = 80,443,8081
filter = vaultwarden
logpath = /path/to/vaultwarden.log
maxretry = 3
bantime = 30d
findtime = 3h
- admin 页面
Filter,进入 /etc/fail2ban/filter.d
目录,新建 vaultwarden-admin.local
:
# /etc/fail2ban/filter.d/vaultwarden-admin.local
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
ignoreregex =
Jail,进入 /etc/fail2ban/jail.d
目录,新建 vaultwarden-admin.local
,同样注意修改日志文件路径:
# /etc/fail2ban/jail.d/vaultwarden-admin.local
[vaultwarden-admin]
enabled = true
port = 80,443
filter = vaultwarden-admin
logpath = /bitwarden/data/vaultwarden.log
maxretry = 3
bantime = 30d
findtime = 3h
重新加载 fail2ban:
systemctl reload fail2ban
备份与还原
vaultwarden 默认将所有数据存储在 data
目录下,使用 SQLite 数据库,文件结构如下:
data
├── attachments # 每一个附件都作为单独的文件存储在此目录下。
│ └── <uuid> # (如果未创建过附件,则此 attachments 目录将不存在)
│ └── <random_id>
├── config.json # 存储管理页面配置;仅在之前已启用管理页面的情况下存在。
├── db.sqlite3 # 主 SQLite 数据库文件。
├── db.sqlite3-shm # SQLite 共享内存文件(并非始终存在)。
├── db.sqlite3-wal # SQLite 预写日志文件(并非始终存在)。
├── icon_cache # 站点图标 (favicon) 缓存在此目录下。
│ ├── <domain>.png
│ ├── example.com.png
│ ├── example.net.png
│ └── example.org.png
├── rsa_key.der # ‘rsa_key.*’ 文件用于签署验证令牌。
├── rsa_key.pem
├── rsa_key.pub.der
└── sends # 每一个 Send 的附件都作为单独的文件存储在此目录下。
└── <uuid> # (如果未创建过 Send 附件,则此 sends 目录将不存在)
└── <random_id>
- 备份
其中需要备份的有:
db.sqlite3
文件:主数据库,包含所有重要的数据attachments
目录:文件附件,唯一不存储在数据库表中的重要数据
建议备份的有
config.json
文件:配置文件rsa_key*
文件:这些文件用于签署当前登录用户的验证令牌,删除这些文件将注销每个用户,迫使他们重新登录
可选备份的有:
sends
目录:文件附件,用于暂时存储icon_cache
目录:图标缓存,用于存储网站图标
数据库的备份推荐使用下面的命令进行备份:
sqlite3 data/db.sqlite3 ".backup '/path/to/backups/db-$(date '+%Y%m%d-%H%M').sqlite3'"
如果在 2022 年 10 月 6 日 22:26 运行此命令,这将备份 SQLite 数据库文件到 /path/to/backups/db-20221006-2226.sqlite3
备份的文件可以打包加密后通过 rclone 备份到 OneDrive/Google Drive,可以参考文章: Linux 定时自动备份数据到 OneDrive/Google Drive
- 还原
还原操作很简单,只需要在另一台服务器上重新安装 vaultwarden 并确保未运行,然后简单地将 data 文件夹中的每个文件或目录替换为它的备份版本即可。
如果你认为这篇文章还不错,可以考虑为我充电 ⚡️