CDN场景下配置Vaultwarden启用fail2ban
Vaulwarden 是一个开源自托管的密码管理工具,这个项目使用 Rust 实现了一套 Bitwarden Server API, 很多小伙伴都用它来管理密钥与凭证。 本文将利用 fail2ban 来实现在 CDN 场景下的防暴力破解。
前言
作为一个密码凭证管理工具,首要关注的便是安全, Alliot 通过如下架构来部署 Vaultwarden:
通过上图可以看出,这里首先使用 CDN 作为了第一道防线, CDN 除去能够分发静态资源提升访问速度之外,还能比较好的帮助我们隐藏源站,在一定程度上起到了保护源站的作用。 用户的请求通过 CDN 节点回源到 Nginx,最后才会到达 Vaultwarden 服务。
针对暴力破解,fail2ban 是中小项目应用很广的一个工具,大多数场景会利用 fail2ban 监听登录失败事件/日志,触发 iptables 封锁指定的 IP, Vaultwarden 官方也推荐使用 这种方式 来加固我们的 Vaultwarden。
然而,在使用 CDN 场景下, 所有用户的请求都是通过 CDN 节点做转发的(WAF 同理),用户并不会直接请求源站,这样在源站的 iptables 封锁用户的 IP 显然无法达到我们的目的, 因此我们需要配置自定义的规则实现从 Nginx 网关层面来阻断恶意的请求, 下文主要针对这部分来做讲解说明。
配置Vaulwarde
这里对于 Docker 部署 Vaultwarden 的过程就不过多赘述,直接给出我们的部署配置文件:docker-compose.yaml
:1
2
3
4
5
6
7
8
9
10
11
12services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: always
volumes:
- ./data:/data
- /var/log/vaultwarden/:/log/
env_file:
- config.env
ports:
- "127.0.0.1:8080:80"
同级目录下的 config.env
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 是否允许注册
SIGNUPS_ALLOWED=false
# 是否开启web UI
WEB_VAULT_ENABLED=true
WEBSOCKET_ENABLED=true
LOG_FILE=/log/vaultwarden.log
LOG_LEVEL=warn
EXTENDED_LOGGING=true
# 禁止显示密码提示
SHOW_PASSWORD_HINT=false
# 启用移动端推送
# https://github.com/dani-garcia/vaultwarden/wiki/Enabling-Mobile-Client-push-notification
# PUSH_ENABLED=true
# PUSH_INSTALLATION_ID=xxx
# PUSH_INSTALLATION_KEY=xxx
这里我们在 config.env
将 Vaultwarden 的日志等级变更为了 warn
, 同时指定了日志文件输出到容器内部的 /log/vaultwarden.log
, 然后在 docker-compose
中将其映射到了宿主机的 /var/log/vaultwarden/vaultwarden.log
, 一旦用户登录密码错误,就会输出日志到这个日志文件, 我们后面将利用 fail2ban 读取这个日志文件来实现防暴力破解。
配置 fail2ban
这里我们以 Ubuntu 为例,安装好 fail2ban
, 并配置开机启动:1
2apt install fail2ban -y
systemctl enable --now fail2ban
默认情况下,fail2ban 安装完成后会在 /etc/fail2ban
生成配置文件,这里我们按照如下配置,在对应的路径下新建 Vaulwarden 相关的配置:
新建 /etc/fail2ban/filter.d/vaultwarden.local
, 这个文件主要用于定义从 Vaulwarden 日志中筛选出登录失败用户的 IP:1
2
3
4
5
6
7[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =
同样的,针对 admin 页面,我们也创建一个配置 /etc/fail2ban/filter.d/vaultwarden-admin.local
:1
2
3
4
5
6
7[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
ignoreregex =
我们再来定义一下 action:
新建 /etc/fail2ban/action.d/vaultwarden.local
,这个主要是从 nginx-block-map.conf
这个 action 修改而来, 注意需要将 Nginx conf 路径改成我们自己的: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[Definition]
# 配置Nginx的conf路径
srv_cfg_path = /usr/local/nginx/conf/
# cmd-line arguments to supply to test/reload nginx:
#srv_cmd = nginx -c %(srv_cfg_path)s/nginx.conf
srv_cmd = nginx
# first test configuration is correct, hereafter send reload signal:
blck_lst_reload = %(srv_cmd)s -qt; if [ $? -eq 0 ]; then
%(srv_cmd)s -s reload; if [ $? -ne 0 ]; then echo 'reload failed.'; fi;
fi;
# map-file for nginx, can be redefined using `action = nginx-block-map[blck_lst_file="/path/file.map"]`:
blck_lst_file = %(srv_cfg_path)s/vaultwarden_blocked_ips.map
# Action definition:
actionstart_on_demand = false
actionstart = touch '%(blck_lst_file)s'
actionflush = truncate -s 0 '%(blck_lst_file)s'; %(blck_lst_reload)s
actionstop = %(actionflush)s
actioncheck =
_echo_blck_row = printf '\%%s 1;\n' "<fid>"
actionban = %(_echo_blck_row)s >> '%(blck_lst_file)s'; %(blck_lst_reload)s
actionunban = id=$(%(_echo_blck_row)s | sed -e 's/[]\/$*.^|[]/\\&/g'); sed -i "/^$id$/d" %(blck_lst_file)s; %(blck_lst_reload)s
这个文件主要定义了 fail2ban 在执行 ban 与 unban 操作时的动作,不难看出,主要是将目标 IP 以 Nginx map 格式写入到了 Nginx conf 路径下的 vaultwarden_blocked_ips.map
文件中, 然后执行了 Nginx reload 操作。
完成后,我们再来配置2个 jail, 简单定义一下规则,包括封禁时间等:
新建 /etc/fail2ban/jail.d/vaultwarden.local
:1
2
3
4
5
6
7
8
9[vaultwarden]
enabled = true
filter = vaultwarden
banaction = vaultwarden
logpath = /var/log/vaultwarden/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400
同样的,针对 admin 页面新建 /etc/fail2ban/jail.d/vaultwarden-admin.local
:1
2
3
4
5
6
7
8
9[vaultwarden-admin]
enabled = true
filter = vaultwarden-admin
banaction = vaultwarden
logpath = /var/log/vaultwarden/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400
最后我们需要执行一下 systemctl restart fail2ban
使得前面的配置生效。
配置Nginx
经过前面的配置,用户在尝试登录失败后,Vaultwarden 会将日志记录到 /var/log/vaultwarden/vaultwarden.log
, fail2ban 在匹配到日志后,会将用户的 IP 地址拿到,在尝试登录失败 3 次后,会触发 vaultwarden 的 action, 这个 action 会在 Nginx 的 conf 路径(/usr/local/nginx/conf
) 的 vaultwarden_blocked_ips.map
文件中记录用户日志,并 reload Nginx, 这个 vaultwarden_blocked_ips.map
文件格式为:1
\99.99.99.99 1;
要想 Nginx 能够根据这个列表来封禁请求,我们还需要配置一下 Vaultwarden 的 Nginx: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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
http {
....
# 定义一个fail2ban的日志格式
log_format f2b_log '[$time_local] fail2ban "$blck_lst_ses" - $remote_addr - "$http_referer" - $http_user_agent" "$request"';
# 使用RealIP模块 从CDN的X-Forwarded-For获取用户真实IP 配置为 $remote_addr
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
###### Vaultwarden ######
upstream vaultwarden-default {
zone vaultwarden-default 64k;
server 127.0.0.1:8080;
keepalive 2;
}
# 兼容websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
# 使用用户真实IP作为key
map $remote_addr $blck_lst_ses {
include vaultwarden_blocked_ips.map;
}
server {
listen 443 ssl http2;
server_name www.iots.vip; # 改成你自己的域名
# ... 省略ssl相关配置
# 定义access log
access_log logs/access.log;
location / {
# 定义403页面
error_page 403 = @f2b-banned;
proxy_pass http://vaultwarden-default;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 配置websocket相关
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect default;
# 使Vaulwarden能够正确获得用户真实IP
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;
# 禁止爬虫相关日志
if ($http_user_agent ~* "bot|spider" ) {
access_log off;
}
# 判断是否被BAN 如果是则直接返回403
if ( $blck_lst_ses != "" ) {
return 403;
}
}
location @f2b-banned {
# 定义fail2ban日志
access_log logs/f2b-auth-errors.log f2b_log;
# 直接内联一个简单的403页面,并且显示用户IP
default_type text/html;
return 403 "<br/><center>
<b style=\"color:red; font-size:18pt; border:1pt solid black; padding:2pt;\">
You are banned! </b><div>Your IP address: $remote_addr</div></center>";
}
}
}
测试效果
这里举例几个常用的 fail2ban 命令:1
2
3
4
5
6
7
8针对 vaultwarden jail 封禁指定IP
fail2ban-client set vaultwarden banip 192.168.1.1
解封
fail2ban-client set vaultwarden unbanip 192.168.1.1
查看 vaultwarden jail
fail2ban-client status vaultwarden
我们先直接通过 Web 界面输入 3 次错误密码登录一下看看效果:
然后 fail2ban-client status vaultwarden
查看一下:1
2
3
4
5
6
7
8
9Status for the jail: vaultwarden
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/vaultwarden/vaultwarden.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: x.x.x.x
可以看到 fail2ban 成功的帮助我们封禁了错误登录尝试 3 次以上的 IP,通过命令解封一下:1
fail2ban-client set vaultwarden unbanip 192.168.1.1
解封这个 IP 后,我们又能正常访问 Vaultwarden 了,大工告成,Enjoy it!