一直以来为了方便都是用的 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_ALLOWEDINVITATIONS_ALLOWED 请在第一次登录创建用户后再添加重启服务。

更新 vaultwarden:

docker compose down
docker compose pull
docker compose up -d

安全设置

常规的安全设置如关闭注册/邀请、关闭密码提示已经在上面的环境变量中设置。

Nginx 设置

1. 开启 HTTPS,你可以 使用 acme.sh 自动签发和更新证书

2. 禁止 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;
}

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 配置。

1. 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

2. 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>

1. 备份

其中需要备份的有:

  • 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

2. 还原

还原操作很简单,只需要在另一台服务器上重新安装 vaultwarden 并确保未运行,然后简单地将 data 文件夹中的每个文件或目录替换为它的备份版本即可。