当初在群晖上自建bitwarden_rs服务器的时候,使用Google搜索出来的结果一概都是使用DSM界面的简单配置,那样倒是能用,但WebSocket不通会导致实时同步不能生效。趁着这次重新规划NAS的机会,我记录一下完整的配置流程。

Bitwarden介绍

Bitwarden本身就不多说了,是个类似于1Password的全平台密码管理器。优势在于代码开源,可以自建服务进一步保证敏感数据私有;缺点呢主要服务端使用.NET和MS SQL,成本对于个人使用无疑太大了。所以社区上有很多活跃人士造轮子来搭配官方客户端,比较活跃的项目有Ruby写的rubywarden和我这里用的Rust版bitwarden_rs

bitwarden_rs实现了绝大部分官方版本的功能(免费的和收费的),资源占用很低,使用Docker易于部署,数据存储又可以选择SQLite/MySQL/PostgreSQL,非常灵活。

准备工作

群晖的DSM系统基本上就是个魔改的Debian,界面上还好说,想在命令行操作有时候特别麻烦。

  1. 在DNS服务商(CloudFlare)配置域名指向。

  2. 安装Docker套件

    DSM的Package Center里面直接搜索安装,略。

  3. 安装Docker Compose

    SSH登录群晖终端,执行:

1
2
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
  1. 生成域名SSL证书

    方案一: 使用群晖的DDNS域名+自定义端口

    略。

    方案二: 使用Certbot获取Let’s Encrypt免费证书

    我用的CloudFlare DNS, Certbot直接支持,很方便。

  • 创建独立目录,比如 /volume1/docker/certbot,进去后创建几个子目录: mkdir -p {etc,secrets,log},将CloudFlare API Token填入secrets/cloudflare.ini
1
2
dns_cloudflare_email = [email protected]
dns_cloudflare_api_key = $token

这里还需要收敛文件权限,不然Certbot运行时会给warning:

1
$ chmod 600 secrets/cloudflare.ini
  • 好了,现在获取证书:
1
2
3
4
5
6
7
8
9
$ sudo docker run -it --rm --name certbot \
            -v /volume1/docker/certbot/etc:/etc/letsencrypt \
            -v /volume1/docker/certbot/log:/var/log/letsencrypt \
            -v /volume1/docker/certbot/secrets:/.secrets \
            certbot/dns-cloudflare certonly \
            --dns-cloudflare-credentials /.secrets/cloudflare.ini \
            --dns-cloudflare-propagation-seconds 60 \
            --server https://acme-v02.api.letsencrypt.org/directory \
            -d 'josta.me,*.josta.me'

回答几个交互式问题,就可以看到恭喜证书生成的提示了:

IMPORTANT NOTES:

  • Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/josta.me/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/josta.me/privkey.pem Your cert will expire on 2020-08-23.

对应的实际位置在 /volume1/docker/certbot/etc/live/josta.me/,校验下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ openssl x509 -in /volume1/docker/certbot/etc/live/josta.me/fullchain.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:94:9f:69:37:e6:d7:7a:78:dd:0d:51:93:98:72:a3:54:c6
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
        Validity
            Not Before: May 25 02:18:35 2020 GMT
            Not After : Aug 23 02:18:35 2020 GMT
        Subject: CN=josta.me
        Subject Public Key Info:
            ...
  1. 定期更新证书

按照上面的提示,拿到的证书会在2020-08-23过期,所以还需要弄个定时任务去刷新,这里就直接用群晖的UI方便些。

  • 更新命令,新建个脚本方便使用 /volume1/docker/certbot/certbot_renew.sh
1
2
3
4
5
6
7
#!/bin/bash

sudo docker run -it --rm --name certbot \
            -v /volume1/docker/certbot/etc:/etc/letsencrypt \
            -v /volume1/docker/certbot/log:/var/log/letsencrypt \
            -v /volume1/docker/certbot/secrets:/.secrets \
            certbot/dns-cloudflare renew --quiet
  • DSM桌面 -> Control Panel -> Task Scheduler -> Create -> Scheduled Task -> User-defined script,定义好定时执行计划,script填刚才创建的文件 bash /volume1/docker/certbot/certbot_renew.sh,确认即可。

Bitwarden服务

  1. Database

    • SQLite 默认

      略。

    • PostgreSQL

      我这还有其它项目要用,所以直接共享使用同一个pg实例1

      docker-compose.yml2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: '3'

services:
  database.postgres:
    image: groonga/pgroonga:latest
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - TZ="Asia/Singapore"
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:
- 注意: 应用并不会去pg自动创建用户和db,需要先手动操作,否则启动时会失败。
1
2
3
4
5
6
7
8
$ sudo docker run -it --rm -e POSTGRES_PASSWORD=${passwd} -e PGPASSWORD=${passwd} --network postgres_default groonga/pgroonga psql -h postgres -U postgres -c "CREATE USER bwuser WITH PASSWORD 'foobar'"

$ sudo docker run -it --rm -e POSTGRES_PASSWORD=${passwd} -e PGPASSWORD=${passwd} --network postgres_default groonga/pgroonga psql -h postgres -U postgres -c "CREATE DATABASE bitwarden"

$ sudo docker run -it --rm -e POSTGRES_PASSWORD=${passwd} -e PGPASSWORD=${passwd} --network postgres_default groonga/pgroonga psql -h postgres -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE bitwarden TO bwuser"

# Chinese full-text search support
$ sudo docker run -it --rm -e POSTGRES_PASSWORD=${passwd} -e PGPASSWORD=${passwd} --network postgres_default groonga/pgroonga psql -h postgres -U postgres bitwarden -c "create extension pgroonga"
  1. bitwarden_rs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
version: '3'

services:
  application:
    image: bitwardenrs/server-postgresql
    container_name: bitwarden
    restart: unless-stopped
    ports:
      - 8080:80
      - 3012:3012
    volumes:
      - ./data:/data
    environment:
      - TZ="Asia/Singapore"
      - DATABASE_URL=postgresql://bwuser:[email protected]:5432/bitwarden?sslmode=disable
      - ENABLE_DB_WAL=false
      - ADMIN_TOKEN=${ADMINTOKEN}
      - WEBSOCKET_ENABLED=true
      - WEBSOCKET_ADDRESS=0.0.0.0
      - WEBSOCKET_PORT=3012
      - INVITATIONS_ALLOWED=true
      - SIGNUPS_ALLOWED=true
      - SIGNUPS_VERIFY=true
      - SIGNUPS_VERIFY_RESEND_TIME=3600
      - SIGNUPS_VERIFY_RESEND_LIMIT=6
      - DOMAIN=https://barbaz.josta.me
      - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      - ROCKET_ENV=production
      - ROCKET_PORT=80
      - ROCKET_WORKERS=10
      - SMTP_HOST=smtp.gmail.com # gmail smtp
      - SMTP_FROM=${FROM_MAIL}
      - SMTP_FROM_NAME=${FROM_USER}
      - SMTP_PORT=587
      - SMTP_SSL=true
      - SMTP_USERNAME=${FROM_MAIL}
      - SMTP_PASSWORD=${SMTP_PASS}
      - SMTP_AUTH_MECHANISM="Plain"
      - SMTP_TIMEOUT=15
    dns:
      - 1.1.1.1
      - 1.0.0.1
    networks:
      - postgres_default

networks:
  postgres_default:
      external: true
  1. Nginx反向代理

    /usr/local/etc/nginx/sites-enabled/目录新建配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name barbaz.josta.me;

    # Certbot申请的证书路径
    ssl_certificate /volume1/docker/certbot/etc/live/josta.me/fullchain.pem;
    ssl_certificate_key /volume1/docker/certbot/etc/live/josta.me/privkey.pem;

    location / {
        proxy_connect_timeout 60;
        proxy_read_timeout 60;
        proxy_send_timeout 60;
        proxy_intercept_errors off;
        proxy_http_version 1.1;
        proxy_set_header Host $http_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://127.0.0.1:8080;
    }

    # Live sync websocket
    location /notifications/hub {
        proxy_pass http://127.0.0.1:3012;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8080;
    }

    error_page 403 404 500 502 503 504 @error_page;

    location @error_page {
        root /usr/syno/share/nginx;
        rewrite (.*) /error.html break;
        allow all;
    }
}

重启Nginx sudo synoservice -restart nginx,最后在Bitwarden客户端上配置好服务器地址,大功告成。

Alfred workflow

Bitwarden不比1Password,有Alfred官方提供的原生集成,需要安装社区制作的workflow才能配合使用。

  1. brew install bitwarden-cli 安装官方的命令行工具
  2. 下载并安装最新版插件
  3. 配置:
    • bwsetemail $email 帐号
    • bwsetserver https://$domain 设置服务地址
    • 如果帐号有设置2FA验证需要 bwset2fa on 开启,如果是使用YubiKey的话还要 bwset2famethod 33
    • bwlogin 完成登录后最好再 bwsync 同步一次
    • 完成,之后就可以在Alfred launcher里使用 bw <query> 命令查询Bitwarden中保存的数据了

信息更新

  1. 使用非root用户控制权限,提升安全性
  • 设置运行容器的用户uid/gid(比如user: 1029:100)
  • 设置 ROCKET_PORT ,普通用户需要大于1024

History

VersionActionTime
1.0Initial commitMay 25, 2020
1.1Run as non-root userJun 11, 2020
1.2Alfred integrationJul 08, 2020

  1. 因为需要使用中文检索支持,这里使用了 PGroonga 扩展的Docker镜像,而不是官方提供的版本。 ↩︎

  2. 如果需要本地备份数据的话可以加上 prodrigestivill/postgres-backup-local↩︎

  3. Two Step Login Methods ↩︎