前言
从LastPass转入Bitwarden也有一段时间,综合来说体验还是不错的,基本功能免费、开源、全平台、好看的UI、中文面板、比较好用的自动保存和自动填充功能,让我喜欢上了这个密码管理器。
免费的普通账户其实已经能满足大多数人的日常需求了,而付费的高级会员功能主要包含大容量附件存储、2FA(两步验证)支持...
因为开源的原因,出现了使用Rust实现并兼容官方客户端的项目Vaultwarden(以前也叫Bitwarden_RS),其提供了Bitwarden基本功能的完整实现,还支持组织、2FA等付费功能,并且制作为Dokcer镜像,方便快速部署,也有比较完善的相关文档。
其实服务本身只需要简单的两条命令就能部署完毕。
docker pull vaultwarden/server:latest
docker run -d --name vaultwarden -v /vw-data/:/data/ -p 80:80 vaultwarden/server:latest
但是这么部署后并不能直接使用,我们需要将自己的域名解析到IP地址上,并申请和部署域名的SSL证书,因为主机上还有其他服务,因此还可以使用Nginx进行反向代理,让子域名成为访问页面,还可以进行强制HTTPS跳转和禁止IP访问等配置...
刚好手上有一台空闲的机子,正好搭个服务,虽然官方wiki已经写的比较详细了,但是部署时还是遇到了一些小问题,因此记录一下详细步骤。
我的这台主机配置是1C2G 60GB 6M
的轻量服务器,操作系统是Ubuntu Server 20.04 LTS 64bit
。
准备阶段
进行自建Bitwarden之前,需要准备以下必要条件:
- 用于运行服务的主机
- 域名
- SSL证书
- 以下命令多数以
root
用户或以sudo
提权进行执行- 超链接基本上为官方文档或说明的对应链接
- 假设我们的域名是
example.com
,以下均以该域名指代真实域名
注册域名
随便找一个域名注册商进行注册自己喜欢的域名即可,目前最便宜的域名基本上是6位以上纯数字的.xyz
域名,新入和续费价格基本上在$0.99
(5-6人民币)左右,有些注册商曾经还有活动,十年的xyz域名只需要二三十元,xyz的优点是续费价格便宜,缺点就是如果要享受低价折扣,只能用长位纯数字,并且xyz这个域名本身也不咋样,不过用于自用的服务倒是无所谓,反正在好记的情况下,越便宜越好。
申请SSL证书
Bitwarden Web vault使用了与HTTPS
上下文相关的网络加密API(SubtleCrypto),为了使其正常运行,同时保证自己的数据安全,必须启用HTTPS
,因此需要SSL证书
,可以使用付费证书,也可以使用Let's Encrypt
等机构颁发的免费证书。
如果能够自行进行申请配置或已配置完毕,可以跳到下一步。
申请免费的Let's Encrypt
证书有很多方式,一般使用certbot或acme.sh,这里我使用certbot
进行申请。
安装certbot
需要安装Nginx
和certbot
#更新apt源
sudo apt update
#安装certbot和相关包,如果没安装Nginx,这个步骤会自动安装Nginx
sudo apt install certbot python3-certbot-nginx
成功安装完毕后,可以输入命令进行确认。
root@VM-4-5-ubuntu:~# certbot --version
certbot 0.40.0
root@VM-4-5-ubuntu:~# nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
此时在浏览器中访问主机的IP地址,会出现Nginx的欢迎页面。
然后我们在域名提供商的管理页面,将域名解析到该IP上,因为一般都是用子域名来访问不同服务页面,比如博客页面通过blog.example.com
访问,RSS服务通过rss.example.com
访问 ...
因此,我们创建一条A Record
(A记录),Host的值使用vault
,Value设置为主机的IP地址
。
等待一段时间后,访问该网址http://vault.example.com
,成功显示Nginx的欢迎页面即可。
在运行certbot
之前,先修改一下Nginx的配置,默认的配置文件去掉注释的结构大致如下。
# /etc/nginx/sites-available/default
server {
listen 80;
listen [::]:80;
root /var/www/html;
server_name vault.example.com;
location / {
try_files $uri $uri/ =404;
}
}
我们需要将server_name
项修改为自己的域名。在保存后通过
sudo nginx -t
校验配置文件,若成功则输入
sudo nginx -s reload
重新加载配置文件。
# 执行结果
root@VM-4-5-ubuntu:/etc/nginx/sites-available# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@VM-4-5-ubuntu:/etc/nginx/sites-available# nginx -s reload
root@VM-4-5-ubuntu:/etc/nginx/sites-available#
之后就可以运行certbot
获取SSL证书了。
自动申请与续期
一般使用Nginx插件
的默认方案即可
sudo certbot --nginx -d vault.example.com
执行后会以交互式脚本询问一些问题,依次填写后,会自动申请证书并更新Nginx的相关配置,以该方式进行申请成功后,certbot
会自动添加一个定时任务来进行证书的续期。
手动申请
因为一些特殊的原因,我的主机无法进行HTTP challenge
,所以也无法使用最方便的方式进行SSL证书的自动申请和自动续期,因此我选择手动方式申请并进行DNS challenge
,这种方式并不能够直接自动续订(可以通过提供的Pre and Post Validation Hooks,结合服务商的Open API来进行自动化)。
指定以DNS challenge
方式手动申请证书
sudo certbot certonly --manual --preferred-challenges=dns
第一次使用同样以交互式脚本询问一些问题。
root@VM-4-5-ubuntu:/etc/nginx/sites-available# sudo certbot certonly --manual --preferred-challenges=dns
Saving debug log to /var/log/letsencrypt/letsencrypt.log
# 填写你的邮箱
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): your e-mail@xxx.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 同意服务条款
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: a
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 是否要订阅相关的邮件
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n
# 输入域名,我要为子域名申请通配符证书,所以用 `*.example.com`,也可以只申请`vault.example.com`.同时也申请`example.com`的证书
Please enter in your domain name(s) (comma and/or space separated) (Enter 'c'
to cancel): *.example.com example.com
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.
Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y
然后就会要求你在域名解析页面添加一条TXT record
,Host的值是_acme-challenge
,Value是下方的随机字符串。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:
# 随机字符串
9M4vHsQ9A1D2e5123efADSAdsdas-TpY-y8RU
Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Linux下可以通过dig
命令查询是否已经生效。
dig -t txt _acme-challenge.example.com
Windows则通过nslookup -qt 记录类型 域名
进行查询。
nslookup -q=txt _acme-challenge.example.com
若看到回应包含自己设置的字符串,则说明已生效,按下回车键继续申请证书,若成功,则会出现以下提示。
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2022-04-06. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
证书的默认位置在/etc/letsencrypt/live/
中。
重新配置Nginx:
# /etc/nginx/sites-available/default
server {
listen 80 default_server;
server_name _;
return 403;
}
server {
listen 80;
listen [::]:80;
# SSL
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy 'strict-origin-when-cross-origin';
server_name vault.example.com;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
try_files $uri $uri/ =404;
}
}
保存之后,通过nginx -s reload
重新加载Nginx配置。
访问https://vault.example.com
后会发现,原来的Nginx欢迎页面的地址栏多了一个锁形标志。
通过Docker部署Bitwarden_RS
在Ubuntu安装Docker的官方文档Install Docker Engine on Ubuntu,国内安装比较慢可以参照的清华镜像站的安装文档。
安装完毕后,输入docker -v
能够正常输出版本号,基本上说明安装完成了。
接下来根据Bitwarden_RS
官方文档进行拉取镜像并启动。
# 拉取镜像
docker pull Vaultwarden/server:latest
接下来,可以用任意方法生成一个长随机字符串,它最好足够的长和复杂,因为是用于容器环境变量中访问管理页面的token,记录并保管好它。
root@VM-4-5-ubuntu:~# openssl rand -base64 48
Aav/MXoLw3xHvFN6Rz8y6E495M9cqerDbt+gXyY5T6ld1TZV59IFJiBcBvoeM758
然后,用以下命令启动容器:
docker run -d --name bitwarden \
-v /bitwarden/data/:/data/ \
-e ADMIN_TOKEN=Aav/MXoLw3xHvFN6Rz8y6E495M9cqerDbt+gXyY5T6ld1TZV59IFJiBcBvoeM758 \
-e LOG_FILE=/data/vaultwarden.log \
-e LOG_LEVEL=warn \
-e EXTENDED_LOGGING=true \
-e "TZ=Asia/Shanghai" \
-p 12345:80 \
vaultwarden/server:latest
这个命令除了启动以外,还做了以下几件事:
- 将该容器命名为
bitwarden
; - 将主机的
/bitwarden/data
目录作为共享文件系统,挂载到容器的/data
路径; - 设置用于开启管理界面的环境变量
- 设置日志记录相关的环境变量
- 指定时区(如果不指定时区,容器内的时区和主机可能会不同,导致后面的fail2ban无法生效)
- 将容器的
80
端口映射到主机的12345
端口
执行命令后,没有报错,屏幕输出容器ID,执行docker ps
查看容器状态,如果显示在列表中,说明部署成功。
主机如果有开放防火墙的12345
端口,可以直接尝试通过主机IP:12345
访问。
管理页面则为主机IP:12345\admin
,注意端口号与路径间并无hash符号
(#),登录令牌为上面生成的长字符串。
因为没有通过HTTPS访问,所以在登录的时候会有相关提示,不允许直接使用,因此接下来配置Nginx
,进行反向代理,最终配置如下,可以进行一些修改。
# /etc/nginx/sites-available/default
server {
listen 80 default_server;
server_name _;
return 403;
}
server {
listen 80;
listen [::]:80;
# SSL
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy 'strict-origin-when-cross-origin';
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
server_name vault.example.com;
location / {
proxy_pass http://127.0.0.1:12345;
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;
}
}
保存之后,通过nginx -s reload
重新加载Nginx配置。
然后就可以访问之前解析的子域名vault.example.com
,根据配置文件,它会被反向代理到主机的12345端口上,就能通过子域名成功访问Bitwarden的登录页面。然后我们就可以把上面测试部署情况时开放的12345端口关掉,不需要直接暴露这个端口。
至此,通过Docker成功部署Bitwarden_RS,并申请和配置了SSL证书,通过Nginx反向代理至子域名。
安全相关
fail2ban
虽然登录密码足够的复杂,但是仍有被暴力破解的可能性,因此需要安装和配置fail2ban。
# 安装fail2ban
sudo apt-get install fail2ban -y
fail2ban在Ubuntu下一般被默认安装到/etc/fail2ban
网页保险库的设置
首先需要设置过滤规则,进入/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 =
然后再进入/etc/fail2ban/jail.d
目录,新建一个文件vaultwarden.local
,将以下内容复制到其中,并进行一些修改,其中logpath
项的值需要修改为日志文件的路径:
# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled = true
port = 80,443,8081
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /bitwarden/data/vaultwarden.log
maxretry = 3
bantime = 8640000
findtime = 86400
管理页面的设置
与网页保险库的设置类似。
进入/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 =
然后再进入/etc/fail2ban/jail.d
目录,新建一个文件vaultwarden-admin.local
,将以下内容复制到其中,并进行一些修改,其中logpath
项的值需要修改为日志文件的路径。
# /etc/fail2ban/jail.d/vaultwarden-admin.local
[vaultwarden-admin]
enabled = true
port = 80,443
filter = vaultwarden-admin
banaction = %(banaction_allports)s
logpath = /bitwarden/data/vaultwarden.log
maxretry = 3
bantime = 8640000
findtime = 86400
设置完毕后,重新加载fail2ban使配置生效。
sudo systemctl reload fail2ban
测试是否生效
会使当前IP无法登陆主机,最好使用移动数据或有其他能登陆主机的设备上进行测试。
访问网址的主页或管理页面,随意输入错误的账号和密码(无需真实存在),超过3次后,如果发现无法正常访问页面,则说明fail2ban设置成功。
查看暴力破解被ban掉的IP:
# 查看web vault是 vaultwarden,查看管理页面则为vaultwarden-admin
fail2ban-client status vaultwarden
解封IP:
fail2ban-client set vaultwarden unbanip XXX.XXX.XXX.XXX
虽然安装了fail2ban后默认配置可以同时保护ssh不被暴力破解,不过为了服务器安全,建议停用ssh密码登录,改为使用密钥登录,并禁用root用户登录。
备份与还原
下文的data
目录指之前设置的共享文件夹,在文中应该是/bitwarden/data/
。
备份
参照官方wiki的备份你的保险库说明。
必须备份的文件有:
data目录
中的db.sqlite3
,它包含了大部分的重要数据。data目录
中的attachments
目录,它包含了所有文件附件。如果没有包含附件的记录,这个目录不会被自动创建。
最好确保在没有正在使用(读写数据库)的情况下进行备份。
一个简单的备份db.sqlite3
的命令,将源路径和目标路径修改为自己想要的路径:
sqlite3 data/db.sqlite3 ".backup '/path/to/backups/db-$(date '+%Y%m%d-%H%M').sqlite3'"
还原
- 重新安装/部署Bitwarden_rs
- 将备份文件恢复到
data
目录中
遇到的问题
fail2ban不生效
配置好fail2ban后,能够正常重启服务,但是在页面输入任意账号密码多次错误后并不会ban掉ip。
最初启动的时候没有加时区参数,导致容器内时区和主机时区不同步,所以没有生效,加上一样的时区之后就正常了。
最后nginx配置文件location那边的$符号被吞了
感谢反馈,因为用WP Editor插件写的,所以没太注意.大概是因为$符号被编辑器插件当成LaTeX公式解析了…目前已禁用科学公式解析