Duet G. Blog

Keep It Simple, Stupid

在ARM开发板上自托管Bitwarden

结论:如果经济条件允许且对数据托管于别处不敏感,请首先选择1Password,除了数据托管在云上之外,使用体验是最好的。但要是对经济性有要求的话,建议使用完全免费的Vaultwarden

背景介绍:
在Lastpass一再发生风险事故后,我决定将密码的管理迁移到1Password。但1Password较高的年费让我又产生了动摇。使用一年后,我决定再寻找一款合适自己的产品。对比过Enpass、KeePass等产品后,基础账号免费、具备多平台客户端且整个系统能够自托管的Bitwarden成为了我的选择。本文简明介绍在ARM开发板上部署Bitwarden服务的步骤。

技术要求:
本文需要有一定Docker以及Linux的使用经验,并假定使用root登录Debian/Ubuntu。另需要一条具备公网IP的宽带、一个域名以及一块ARM开发板。

Bitwarden官方主流安装方法虽然也是在Docker下运行,但镜像并不支持ARM架构。如果是想在ARM开发板上运行Bitwarden,需要使用官方提供的Unified镜像,也就是集成化镜像。通过单一镜像可以大幅降低对系统资源的要求(最少200MB内存,1GB储存空间),理论上非ARM架构主板也能运行Unified镜像,但由于此安装方式目前仍在Beta阶段,官方支持度也只能说一般,大多数遇到的问题还得要自己研究解决。在部署过程中遇到的坑后面文章中会给出解决方法。

准备SSL证书

作为密码管理软件,在客户端和服务器端通信的过程如果是明文,会大大降低使用的安全性。Bitwarden需要有SSL连接(即https访问),则首先要在系统内准备SSL证书。虽然Docker镜像内也可以部署SSL证书(可通过Volume参数映射到容器外),但官方建议尽量在容器外使用Nginx配置证书,并通过反向代理将https的端口指向容器开放的非加密访问端口(如80)。可以申请使用免费证书,如Let’s Encrypt

安装Nginx

apt update
apt install nginx

如果ARM开发板不能通过80端口来访问(国内家庭宽带绝大多数80、443、8080、8443都是被封锁的),而一般申请证书时要用80端口来验证。则需要使用DNS API来申请证书,这样就绕过了通过80端口验证的环节。Let’s Encrypt主推的Certbot是不具备这个验证功能的,这里我们使用acme.sh

安装acme.sh

#将[email protected]换成你自己的邮箱,如果没有curl的话先安装curl
curl https://get.acme.sh | sh -s [email protected]

安装过程中会有红色字样提示先安装socat,由于我们使用DNS API来申请证书,所以可以不用安装socat。acme.sh安装完成后重新登入一次账号,就可以直接使用acme.sh命令了。接下来我们通过域名托管的DNS服务来申请证书,acme.sh支持大量的DNS服务,我们以Cloudflare来举例。

先在Cloudflare的个人资料页面左侧选择API令牌,然后再下一个页面点击右侧蓝色的创建令牌按钮。在API令牌模板中选择编辑区域DNS模板会大幅减少你的工作量。点击蓝色使用模板按钮后,在创建令牌中区域资源段最右侧的下拉菜单里选择你先前绑定在Cloudflare上的域名,随后点击下方蓝色的继续以显示摘要按钮,可以看到类似如下的令牌摘要:

编辑区域 DNS API 令牌摘要

此 API 令牌将影响以下帐户和区域,以及它们各自的权限

确认无误后点击蓝色的创建令牌按钮,并在下个页面把虚线框中的字符串完整复制到桌面的文本文件里,这个就是你域名的DNS API令牌。接下来回到Cloudflare的账户主页,点击绑定的域名进入域名概述页面,在页面右侧下方找到区域ID和账户ID也复制到本地。接下来在ARM开发板的命令行中通过如下命令申请证书。

#先导入令牌和账户ID以及区域ID
export CF_Token="<token>"
export CF_Account_ID="<id>"
export CF_Zone_ID="<zone>"

#再使用命令申请证书
acme.sh --issue --dns dns_cf -d example.com

证书申请成功后需要安装才可以使用,这里我们就借用Nginx内置的snakeoil文件所包含的证书位置和名称来安装证书。

acme.sh --install-cert -d example.com \
--key-file       /etc/ssl/private/ssl-cert-snakeoil.key  \
--fullchain-file /etc/ssl/certs/ssl-cert-snakeoil.pem \
--reloadcmd     "service nginx force-reload"

证书完成安装后,开始写Nginx的配置文件

#系统中如果没有vim的话可以先安装vim
apt install vim
#编辑Nginx默认配置文件
vim /etc/nginx/sites-available/default

请参考以下内容修改default文件,完成后按 :wq 保存并退出

server {
        listen 443 ssl default_server;
        listen [::]:443 ssl ipv6only=on default_server;
 
        include snippets/snakeoil.conf;

        ssl_session_cache shared:le_nginx_SSL:10m;
        ssl_session_timeout 1440m;
        ssl_session_tickets off;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers off;

        ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

        server_name _;

        location / {
                ### Force timeouts if one of backend hosts is dead ###
                proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

                ### Set headers ###
                proxy_set_header          X-Real-IP $remote_addr;
                proxy_set_header          Accept-Encoding "";
                proxy_set_header          Host $http_host;
                proxy_set_header          X-Forwarded-For $proxy_add_x_forwarded_for;

                ### Don't timeout waiting for long queries - timeout is 1 hr ###
                proxy_read_timeout        3600;
                proxy_set_header          X-Forwarded-Proto $scheme;

                ### By default we don't want to redirect ###
                proxy_redirect            off;
               
                ### Add Websocket proxy support ###
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";

                proxy_pass http://127.0.0.1:80;
        }
}

保存并退出后使用以下命令重启Nginx

service nginx force-reload

以上是证书及Nginx反向代理的设置步骤。接下来开始为Bitwarden容器的运行做准备。首先是安装Docker,我们使用Docker官方的安装脚本来安装。

#首先回到Home文件夹
cd ~
#下载安装脚本
curl -fsSL https://get.docker.com -o install-docker.sh
#浏览并测试脚本(也可以跳过)
cat install-docker.sh
sh install-docker.sh --dry-run
#执行脚本安装Docker,如果从官方服务器下载速度很差,可以使用阿里云的镜像,安装操作请务必在root权限下进行
sh install-docker.sh --mirror Aliyun

安装完成后在Home中生成Bitwarden工作文件夹并进入并添加docker-compose.yml文件

cd ~
mkdir bitwarden
cd bitwarden
vim docker-compose.yml

将下面内容按实际编辑后粘贴进vim并用 :wq 保存退出

version: "3.8"

services:
  bitwarden:
    depends_on:
      - db
    env_file:
      - settings.env
    image: ${REGISTRY:-bitwarden}/self-host:${TAG:-beta}
    restart: always
    ports:
      - "80:8080"
  #   - "443:8443"
    volumes:
      - bitwarden:/etc/bitwarden
      - logs:/var/log/bitwarden

  # MariaDB Example
  db:
    environment:
      MARIADB_USER: "bitwarden"
      MARIADB_PASSWORD: "super_strong_password" #建议修改password
      MARIADB_DATABASE: "bitwarden_vault"
      MARIADB_RANDOM_ROOT_PASSWORD: "true"
    image: mariadb:10
    restart: always
    volumes:
      - data:/var/lib/mysql

volumes:
  bitwarden:
  logs:
  data:

接下来添加Bitwarden的设置文件

vim settings.env

将下列内容编辑后粘贴进vim并用 :wq 保存退出

#####################
# Required Settings #
#####################

# Server hostname
BW_DOMAIN=example.com

# Database
# Available providers are sqlserver, postgresql, mysql/mariadb, or sqlite
BW_DB_PROVIDER=mysql
BW_DB_SERVER=db
BW_DB_DATABASE=bitwarden_vault
BW_DB_USERNAME=bitwarden
BW_DB_PASSWORD=super_strong_password  #和docker-compose.yml中一致

# Installation information
# Get your ID and key from https://bitwarden.com/host/
# 请到 https://bitwarden.com/host/ 中输入邮箱并提交后免费获取
BW_INSTALLATION_ID=xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BW_INSTALLATION_KEY=xxxxxxxxxxxxxxxxxxxx

#####################
# Optional Settings #
#####################
# Learn more here: https://bitwarden.com/help/environment-variables/

# Container user ID/group ID
#PUID=1000
#PGID=1000

# Webserver ports
#BW_PORT_HTTP=8080
#BW_PORT_HTTPS=8443

# SSL
#BW_ENABLE_SSL=true
#BW_ENABLE_SSL_CA=true
#BW_SSL_CERT=ssl.crt
#BW_SSL_KEY=ssl.key
#BW_SSL_CA_CERT=ca.crt

# Services
# Some services, namely for enterprise use cases, are disabled by default. Defaults shown below.
BW_ENABLE_ADMIN=true
BW_ENABLE_API=true
BW_ENABLE_EVENTS=false
BW_ENABLE_ICONS=true
BW_ENABLE_IDENTITY=true
BW_ENABLE_NOTIFICATIONS=true
BW_ENABLE_SCIM=false
BW_ENABLE_SSO=false

#BW_ICONS_PROXY_TO_CLOUD=false

# Mail
# 使用Gmail的SMTP服务发信
globalSettings__mail__replyToEmail=noreply@$BW_DOMAIN
globalSettings__mail__smtp__host=smtp.gmail.com
globalSettings__mail__smtp__port=587
globalSettings__mail__smtp__ssl=false
[email protected]   #你的Gmail地址
globalSettings__mail__smtp__password=xxxxxxxxxxxxxxxx   #你的Google账户的应用专用密码

# Yubikey
#globalSettings__yubico__clientId=REPLACE
#globalSettings__yubico__key=REPLACE

# Other
#globalSettings__disableUserRegistration=true    #如想禁止注册请取消本行开头的注释
#globalSettings__hibpApiKey=REPLACE
adminSettings__admins="[email protected]"         #使用你的邮箱作为管理员登录的凭据
#globalSettings__baseServiceUri__vault=https://example.com:1234     #如果使用了非标准端口,请取消本行注释并将完整带端口号的地址填在后面
#下方BW_REAL_IPS是反向代理过程中可能会影响到获取真实IP的内网IP地址,以及后半段的是Cloudflare的IP地址,请按实际情况进行修改
BW_REAL_IPS=172.0.0.0/8,127.0.0.1,192.168.2.0/24,173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32

录入完成并保存退出后就可以使用Docker Compose启动Bitwarden

docker compose up -d

稍等一会儿,在浏览器中访问 https://{ARM开发板IP} ,应该会先看到证书错误的提示,选择继续访问就可以看到Bitwarden的登录页面。

如果有公网IP的话,在路由器上利用DDNS服务把公网IP更新到域名A记录上,再用端口映射功能将ARM开发板内网IP的443端口映射到路由器上Bitwarden设置文件里自定义的端口(本例是1234)上,就可以直接用域名加端口号来访问Bitwarden了。在上方的例子中是 https://example.com:1234

至此Bitwarden在ARM开发板的自托管便设置完成了。但是作为一款密码管理软件,如果不能备份数据库的话,使用风险是很高的。在Bitwarden Unified镜像的官方反馈帖中,官方明确表述了鉴于是自托管运行,所以目前不提供任何备份数据的功能,需要靠自己来完成备份。于是我们需要编写一个脚本,让机器定时完成备份。

首先我们进入Bitwarden的工作文档新建一个备份文件夹,再新建一个备份脚本

cd ~/bitwarden
mkdir backups
vim backup.sh

将以下代码按实际情况修改后粘贴进 vim 并用 :wq 保存并退出

#!/bin/bash
# Backup database.

docker exec bitwarden-db-1 sh -c 'exec mysqldump --all-databases -ubitwarden "super_strong_password"' > /root/bitwarden/backups/backup.sql
#将上行命令中的密码修改为之前设置的密码

now=$(date +"%Y-%m-%d-%H_%M")
new_name=db_backup-${now}

echo "Prepare backup folder from backup.sql and docker volume bitwarden_bitwarden"
mkdir -p /root/bitwarden/backups/${new_name}
mv /root/bitwarden/backups/backup.sql /root/bitwarden/backups/${new_name}/backup.sql
cp -a /var/lib/docker/volumes/bitwarden_bitwarden /root/bitwarden/backups/${new_name}/
cp -a /root/bitwarden/backup.sh /root/bitwarden/backups/${new_name}/
cp -a /root/bitwarden/docker-compose.yml /root/bitwarden/backups/${new_name}/
cp -a /root/bitwarden/restore.sh /root/bitwarden/backups/${new_name}/
cp -a /root/bitwarden/settings.env /root/bitwarden/backups/${new_name}/

echo "Compressing ${new_name} folder"
tar -zcpf /root/bitwarden/backups/${new_name}.tar.gz -C /root/bitwarden/backups/${new_name} .
rm -r /root/bitwarden/backups/${new_name}
cd /root/bitwarden/backups/
ls -t | sed -n '151,$p' | xargs -I {} rm -rf {}

备份脚本会将所有和Bitwarden相关的数据全部备份并打包,以备份时间为文件名将压缩包保存在Bitwarden工作目录下的 backups 文件夹中。如果有支持 samba 协议的网络存储设备(如群晖NAS),还可以通过 smbclient 命令将备份的压缩包传送到别处异地备份。另外脚本最后一行命令确保备份文件夹中始终保持最新的150个备份,这个数字可以按需求自行调整。这里给一下 smbclient 的使用方法,可以添加到备份脚本的末尾。如果备份到群晖等NAS上,还可以使用类似Hyper Backup之类的程序继续将其备份到网络存储,但注意文件安全。

smbclient -N //{NAS的IP}/home -U {NAS用户名}%{NAS密码} -t 120 -c 'cd /BitwardenBackup/; put /root/bitwarden/backups/'${new_name}'.tar.gz '${new_name}'.tar.gz'

通过系统 cron 定期执行备份脚本,以达到自动备份的效果

#编辑crontab
crontab -e
#将下行代码粘贴至crontab最后一行并保存退出,即可每6小时执行一次脚本
0 */6 * * * sh /root/bitwarden/backup.sh > /dev/null

如果重新部署Bitwarden需要还原数据可以用以下代码

#!/bin/bash
# Restore database.

echo "Extract files from archive file"
mkdir -p /root/bitwarden/backups/temp
tar -zxpf $1 -C /root/bitwarden/backups/temp

echo "Restore database"
docker exec -i bitwarden-db-1 sh -c 'exec mysql -ubitwarden "-super_strong_password"' < /root/bitwarden/backups/temp/backup.sql
#将上行命令中的密码修改为之前设置的密码

echo "Restore docker volume bitwarden_bitwarden"
rm -rf /var/lib/docker/volumes/bitwarden_bitwarden
cp -af /root/bitwarden/backups/temp/bitwarden_bitwarden /var/lib/docker/volumes/

echo "Clean work folder"
rm -rf /root/bitwarden/backups/temp

echo "Stop the running container, please restart manually"
cd /root/bitwarden
docker compose down

将上述代码修改后保存为 restore.sh。先按照本文初的方式部署并运行Bitwarden(包括工作目录及备份文件夹的设置),将备份文件和 restore.sh 一同放入工作目录,保持Bitwarden运行中,执行

sh restore.sh db_backup-xxxx-xx-xx-xx_xx.tar.gz

完成还原数据后再执行一次启动Bitwarden的命令即可

docker compose up -d

至此Bitwarden Unified的备份还原工作也完成。

另,acme.sh目前默认的证书服务由ZeroSSL提供,如果还想使用Let’s Encrypt,请在申请证书前先用下方命令切换默认证书服务到Let’s Encrypt,再操作证书申请

acme.sh --set-default-ca --server letsencrypt

再另,本文所提方法适用于家庭宽带有公网IP的情形,如家庭宽带不具备公网IP条件,可以使用Cloudflare Tunnel来完成内网穿透,实现通过标准端口访问内网ARM开发板,具体方法之后会再写一篇blog详述。

Update: Cloudflare Tunnel相关的内容请参见这里


订阅评论
提醒
guest

0 评论
内联反馈
查看所有评论