惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

PCI Perspectives
PCI Perspectives
J
Java Code Geeks
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
博客园 - 聂微东
雷峰网
雷峰网
The Cloudflare Blog
V
V2EX
Hacker News: Ask HN
Hacker News: Ask HN
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
爱范儿
爱范儿
NISL@THU
NISL@THU
T
The Exploit Database - CXSecurity.com
Project Zero
Project Zero
腾讯CDC
罗磊的独立博客
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 【当耐特】
Hugging Face - Blog
Hugging Face - Blog
S
Security Affairs
S
SegmentFault 最新的问题
月光博客
月光博客
P
Privacy International News Feed
Last Week in AI
Last Week in AI
博客园 - Franky
C
Cisco Blogs
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
博客园 - 叶小钗
L
LINUX DO - 最新话题
博客园_首页
Spread Privacy
Spread Privacy
大猫的无限游戏
大猫的无限游戏
S
Secure Thoughts
IT之家
IT之家
人人都是产品经理
人人都是产品经理
I
Intezer
W
WeLiveSecurity
C
CERT Recently Published Vulnerability Notes
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
S
Security @ Cisco Blogs
L
Lohrmann on Cybersecurity
美团技术团队

Alliot's blog

域名切换到blog.alliot.tech Docker 代理配置机制与作用域 Docker 代理配置机制与作用域 2025年底的安卓搞机备忘录 2025年底的安卓搞机备忘录 ArgoCD部署应用出现metadata.annotations过大问题 ArgoCD部署应用出现metadata.annotations过大问题 APC UPS更换电池校准容量 APC UPS更换电池校准容量 M1 Mac安装低版本Node.js M1 Mac安装低版本Node.js Ansible使用Bitwarden存储Vault密码 Ansible使用Bitwarden存储Vault密码 Cloudflare Tunnel前置代理支持 Cloudflare Tunnel前置代理支持 CDN场景下配置Vaultwarden启用fail2ban 中银香港丝滑开户总结 中银香港丝滑开户总结 从指定路径更新雷池WAF证书 从指定路径更新雷池WAF证书 AWS ECS使用EBS作为Volume AWS ECS使用EBS作为Volume 浅浅的调教一下国产智障电视 浅浅的调教一下国产智障电视 Nginx proxy_pass到AWS ALB的504问题 Nginx proxy_pass到AWS ALB的504问题 OpenV**手动指定路由规则 OpenV**手动指定路由规则 本地模拟CNAME解析 本地模拟CNAME解析 Nginx搭建WebDAV服务 Nginx搭建WebDAV服务 迎来船新版本的Hexo+NexT 迎来船新版本的Hexo+NexT 优雅的处理Git多帐号与代理问题 优雅的处理Git多帐号与代理问题 验光配镜扫盲 验光配镜扫盲 Prometheus relabel实现动态metrics path Prometheus relabel实现动态metrics path
CDN场景下配置Vaultwarden启用fail2ban
Alliot · 2024-06-25 · via Alliot's blog

  Vaulwarden 是一个开源自托管的密码管理工具,这个项目使用 Rust 实现了一套 Bitwarden Server API, 很多小伙伴都用它来管理密钥与凭证。 本文将利用 fail2ban 来实现在 CDN 场景下的防暴力破解。

前言

  作为一个密码凭证管理工具,首要关注的便是安全, Alliot 通过如下架构来部署 Vaultwarden:
Vaultwarden fail2ban 架构图
  通过上图可以看出,这里首先使用 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
12
services:
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
2
apt 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 次错误密码登录一下看看效果:
Vaultwarden fail2ban 封禁效果

  然后 fail2ban-client status vaultwarden 查看一下:

1
2
3
4
5
6
7
8
9
Status 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!