Bitwarden自建终极加固

安全很重要!

Posted by Pockies on June 14, 2024

转载 . 留档 . 自用:https://sharpgan.com/bitwarden-self-hosted-ultimate-hardening-backup-and-smtp-config/

前言

如果你的bitwarden只托管了一些不重要的密码的话,可以看一下本站之前写的一篇博客(保姆级免Nginx反代配置带SSL证书Bitwarden服务端),基本也够安全的了,因为Bitwarden_RS/Vaultwarden(下文简称Bitwarden)底层采用的rocket tls最低支持版本是1.2,而不是1.0或1.1,具体看这里的官方文档

但是如果你的自建bitwarden托管了你的支付宝微信、银行卡、信用卡等全部身家,甚至几百个比特币的钱包的私钥,那前面提到的博客就不行了,我们必须再提高安全性,毕竟我们的服务暴露在了公网,一旦被不怀好意的人渗透了可就真的麻烦了。

终极加固实战

首先声明一下,一定一定一定要首先优先配置两步验证,这是最基础的安全措施,本文不再赘述~

另外本人遵循 “eat your own dog food”原则,本文全篇介绍的东西在本人自建的Bitwarden服务器上全部有应用到。

本人作为一名渣渣Linux运维小学生,着实才疏学浅,本文不能涵盖所有加固措施,遗漏的地方还望大家在评论区留言指出来,本人再补充进正文。

我们的加固涉及到了以下方面:

  • 禁止注册和邀请用户
  • 禁止显示密码提示
  • 基于Nginx实现禁止ip访问 (防止类似于shedon的邪恶搜索引擎搜索到,以及脚本小子扫描ip段)
  • 基于腾讯云控制台和Nginx讲解如何配置ECC算法的ssl证书,舍弃掉RSA算法的证书(ECC算法的破解难度要更高,比RSA算法要更上一个台阶),网上搜到的博客试了好几个都不行,我这里的一步到位
  • 让docker进程以非root用户运行,这里踩了一个坑,折腾了个把小时
  • 最后一步几乎将自建Bitwarden的安全性更上了一个台阶,那就是给后台的URL加上随机字符串,使得使用默认的后台地址无法访问
  • 最最后,由于我们的前端流量由Nginx承载,所以Nginx也要加固一下,下述共涉及 屏蔽Nginx的版本号减少攻击面、对Http请求方法进行限制、防止点击劫持、启用HSTS强制https访问、防止跨站攻击和摒弃不安全的ssl cipher且server端主动选择cipher共计6个方面。

基于某些考虑我们没有涉及的:

  • 配置fail2ban,自动封锁暴力破解者,这个有时候很容易在自己连续输入错几次密码后把自己给封了,即使你可以在手机上访问vps解封也比较繁琐,如果你按照本篇博客设置下来的话,配置fail2ban的意义也不是很大了。
  • docker的ssl挂载目录设置只读权限,因为我们采用了ECC算法的证书,而Bitwarden底层采用的rocket tls实现是不支持解析的,所以我们要移除掉相关设置,不用担心,本篇博客将会介绍如何用Nginx来实现解析ECC算法的证书
  • 舍弃掉你的主域名来作为服务入口,而是配置一个含有随机字符串的二级域名,这也是一个很不错的加固方式,本文也不再赘述

上述所有的加固措施在Bitwarden_rs/vaultwarden的官方wiki中都有提到,大神没必要再付费看我下面的描述了,不过如果你对上述加固措施不太熟悉的话,亦或者自己踩了很多坑搞不定的话,还是墙裂建议看看下面的描述的。

Tips:下述命令和Nginx指令比较零星,对技术不太熟悉的朋友很容易弄混淆,本人贴心的把docker的完整启动命令和关键指令含有注释的Nginx的完整配置分别贴了出来。

[$]

1. 禁止注册和邀请用户

这个很简单,在docker启动命令中加入如下环境变量即可:

INVITATIONS_ALLOWED=false  -e SIGNUPS_ALLOWED=false

2. 禁止显示密码提示

这个也很简单,加入如下环境变量:

 -e SHOW_PASSWORD_HINT=false

3. 基于Nginx实现禁止ip访问

在你的NGINX的http块中加入如下配置:

if ($host !~* ^(www\.)?sharpgan.com$) {
        return 403;
}

这一步还有另一种写法,感觉不如上面的直观,我就不写出来了。

注意:上面的域名你自己替换一下

4. 基于腾讯云控制台和Nginx讲解如何配置ECC算法的ssl证书

进入腾讯云的SSL控制台,依次按照如下操作:

重新签发后验证一下域名所属权之后等待签发就行了,然后我们把Nginx格式的证书下载下来配置一下Nginx,如下:

    ssl_certificate /data/Bitwarden/cert.crt;
    ssl_certificate_key /data/Bitwarden/keys.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
    ssl_session_cache builtin:1000 shared:SSL:10m;
    client_max_body_size 128M;

注意:我们只要.key和.crt结尾的文件,上述是放到/data/Bitwarden下面,你可以自己指定一个位置

Tips:这里我们为了安全性考虑,ssl协议移除了1.0和1.1,直接1.2起步,即是 ssl_protocols TLSv1.2 TLSv1.3;

5. 让docker进程以非root用户运行

这个说来也简单,只是当初忽略了一个细节导致折腾了个把小时才搞定,只需要在docker的启动命令加入如下参数:

-u 1002:1002 -e ROCKET_PORT=8080

上面的1002代表了一个非root非sudo权限的普通用户的id,你可以用命令id + 空格 + 用户名来得到,具体细节不再赘述。

注意:上面的-e ROCKET_PORT=8080一定不能没有,我当初就是因为忽略了这个才折腾了好久。

6. 给后台的URL加上随机字符串

首先,docker的启动命令加入如下环境变量:

-e DOMAIN='https://xxxxxx.sharpgan.com:23333/abcdefg'

上述/abcdefg就是我们的随机字符串示例,注意上述随机字符串后面不要有斜线/,否者访问会报错。

然后我们的Nginx中加入如下配置:

upstream vaultwarden-default { server 127.0.0.1:8080; }

location /abcdefg/ {
      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;
    }

7. 加固Nginx

屏蔽Nginx的版本号,在Nginx的http块中加入如下指令:

server_tokens off;

对Http请求方法进行限制,在Nginx的location块中加入如下指令:

limit_except GET POST { deny all; }

防止点击劫持,在Nginx的virtual server块中加入如下指令:

add_header X-Frame-Options "SAMEORIGIN";

启用HSTS强制https访问,在Nginx的virtual server块中加入如下指令:

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

防止跨站攻击,在Nginx的virtual server块中加入如下指令:

add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header X-XSS-Protection "1; mode=block";

摒弃不安全的ssl cipher即tls 1.0 1.1,另外加入server端主动选择cipher的指令,不过前文已经提到过,如下:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;

总结

由于上述命令和Nginx指令比较零星,对技术不太熟悉的朋友很容易弄混淆,所以下面分别把docker的完整启动命令和Nginx的完整配置贴出来。

docker的完整启动命令如下:

docker run -d --name bitwarden -u 1002:1002 -e ROCKET_PORT=8080 -e INVITATIONS_ALLOWED=false  -e SIGNUPS_ALLOWED=false -e SHOW_PASSWORD_HINT=false -e DOMAIN='https://xxxxx.sharpgan.com:23333/abcdefg' -e SMTP_HOST='smtp.qq.com' -e SMTP_FROM='233333333@qq.com' -e SMTP_PORT=587 -e SMTP_SSL=false -e SMTP_USERNAME='233333333@qq.com' -e SMTP_PASSWORD='xxxxxxxxxx'  -v /data/bw-data/:/data/   -p 127.0.0.1:8080:8080  vaultwarden/server:latest

Nginx的完整配置如下:

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 1024;
}

http {
    # 屏蔽Nginx版本号减少攻击面
    server_tokens off;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    client_body_buffer_size  1024k;
    client_max_body_size   100m; 
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;
    upstream vaultwarden-default { server 127.0.0.1:8080; }
    server {
    listen 23333 ssl http2;
    # 禁止ip访问,防止类似于shedon的邪恶搜索引擎搜索到,以及脚本小子扫描ip段
    server_name xxxxxx.sharpgan.com;
    if ($host != 'xxxxxx.sharpgan.com'){
    return 403;
    }
    ssl_certificate /data/bw-ssl/cert.crt;
    ssl_certificate_key /data/bw-ssl/keys.key;
    ssl_session_timeout 5m;
    # 摒弃不安全的ssl cipher且server端主动选择cipher
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    # 优先采用ECC算法而不是RSA,注意下述cipher的顺序很重要,如果RSA相关的在前的话,浏览器可能识别不了ECC算法的证书
    ssl_ciphers "TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5";
    ssl_session_cache builtin:1000 shared:SSL:10m;
    client_max_body_size 128M;
    # 防止点击劫持
    add_header X-Frame-Options "SAMEORIGIN";
    # 启用HSTS强制https访问
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";
    # 防止跨站攻击
    add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
    add_header X-XSS-Protection "1; mode=block";
    ## Using a Sub Path Config
    # Path to the root of your installation
    # Be sure to add the trailing /, else you could have issues
    # 后台url采用随机字符串
    location /abcdefg/ {
      limit_except GET POST { deny all; }
      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;
    }
}	
}

[/$]

最简单备份指南

其实就是新建一个git私有仓库,然后写一个每天打包并自动push到远程仓库的脚本,再配置成定时任务就行了,国内vps推荐使用的gitee,国外的可以用github,

注意!!! 仓库一定要建成私有的哦 注意!!!

脚本如下:

cd /data && tar czvf bitwarden_backup_$(date '+%Y%m%d_%H%M').tgz bw-data/ && mv bitwarden_backup_$(date '+%Y%m%d_%H%M').tgz bitwarden_backup/ && cd bitwarden_backup/ && git add . && git commit -m 'update' && git push -u origin master

上述脚本的前提是你的git控制台添加了你本机的公钥,这里不再赘述。

还有一些前提是:你的docker数据卷挂载在了根目录的/data下面,叫bw-data,以及在同级目录新建了一个叫bitwarden_backup的文件夹。

如果你满足了上述要求,就可以配置一个如下的定时任务了:

0 5 * * * bash /data/bitwarden_backup/backup.sh

效果如下:

为了减少对公共资源的浪费,建议改造一下上述脚本自动删除超过30天的备份。

SMTP邮箱配置指南

你可能好奇配置这个有啥用,当你需要更换你当前的邮箱账户的时候就知道了,配置了这个后只需要输入邮箱中的验证码就可以把账户分分钟换成任意邮箱了

这个其实很简单,我们以QQ邮箱为例,其实就是docker的启动命令中加入几个环境变量,如下:

-e SMTP_HOST='smtp.qq.com' -e SMTP_FROM='233333333@qq.com' -e SMTP_PORT=587 -e SMTP_SSL=false -e SMTP_USERNAME='233333333@qq.com' -e SMTP_PASSWORD='xxxxxxxxx'

由于这些环境变量的名字都比较语义化,我就不一一介绍都是啥意思了,完整的命令在上文付费阅读部分有提到。