配置Fail2ban使用FreeBSD的pf或Cloudflare
起因
使用Fail2ban已经有一段时间了,效果还是不错,但是在FreeBSD上和/或在Cloudflare后面时会有一些问题。
Cloudflare的问题是很显然,因为请求过来的是CDN服务器的地址,而不是实际客户端的地址,虽然可以通过real_ip来取得实际客户端的地址,但无法ban掉它……因为它不并直接连到我们的服务器上,需要通过Cloudflare的API让CDN去ban它。
FreeBSD则是pf的问题。因为在FreeBSD上配置了Fail2ban以后发现似乎没有生效,看了日志才发现原来action调用pfctl失败了,所以研究了一下问题在哪里。
但是对pf还是不够熟,毕竟现在iptables用得多,所以有些命令不是太了解,先研究了一下:
pf的日常用法
最基本的就是启用pf,跟一般服务差不多,都是在/etc/rc.conf里加:
pf_enable="YES"
pflog_enable="YES"
gateway_enable="YES"
不过我实际上只加了pf_enable。
启动停止可以通过服务命令,也可以用pfctl:
pfctl -e # enabled
pfctl -d # disabled
pfctl -vnf /etc/pf.conf # 检查规则但不实际加载
pfctl -Fa -f /etc/pf.conf # 删除所有规则并重新加载(与iptables的重启效果不同,此操作SSH将中断)
pfctl -sr / -sn / -ss / -sA / -sT / -sS # 显示规则表,NAT表,状态表,锚表,地址表,源表
pfctl -t name -Tk / -Tf / -Ta / -Td / -Ts # 地址表操作,name表名,删除表,刷新表,表中增加、删除、显示地址
Fail2ban日志里显示的用法要复杂一些,用到了Anchor,然后通过在这个anchor下添加table来实现精细过滤。稍微研究了一下觉得没有必要搞这么精细,只要粗放地把IP封掉就可以了,所以自己作了一下改造。
pf配置
将自带的/usr/local/etc/fail2ban/action.d/pf.conf复制一个pf-all.conf,作了一些修改,去掉注释后的内容如下:
[Definition]
actionstart =
actionstart_on_demand = false
actionstop = <pfctl> -sr 2>/dev/null | grep -v <tablename> | <pfctl> -f-
%(actionflush)s
actionflush = <pfctl> -t <tablename> -T flush
actioncheck = <pfctl> -sr | grep -q <tablename>
actionban = <pfctl> -t <tablename> -T add <ip>
actionunban = <pfctl> -t <tablename> -T delete <ip>
pfctl = pfctl
[Init]
tablename = f2b
block = block in quick
protocol = tcp
actiontype = <allports>
allports = any
multiport = any port $port
主要修改了几个地方:
- actionstart去掉了
- actionstop去掉了删除表的操作
- 所有的<tablename>-<name>都改为统一的<tablename>
- pfctl去掉了anchor参数
- actiontype改为allports
原来是在启动时会对不同的过滤器创建不同的表,然后把要ban的IP放到对应的表里,停止的时候把相应的表删除。
现在改为只有一个f2b表,手工添加到/etc/pf.conf里:
table <f2b> persist
block in quick from <f2b> to any
然后重新加载pf,记得用pfctl -sr
检查一下。
动作时就简单地把IP添加进去,让pf把这个IP的所有请求全部ban掉。
实践后效果很好。
Cloudflare API配置
使用CDN需要处理的问题有两个:一个是真实IP获取,一个是调API封IP。
真实IP获取可以参见《Nginx增加基于地理位置的日志和限制》,使用Nginx的realip模块。
因为这台机器使用的是Apache,虽然也有相应的mod_cloudflare之类,但还是为了省事简单地使用自定义日志格式:
LogFormat "%{CF-Connecting-IP}i %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-Agent}i\" %I %O \"%{X-Forwarded-For}i\" %{Host}i %D" cfproxy
注意,走CloudFlare的网站日志必须单独记录,不要跟不走CloudFlare的网站混合,因为需要用不同的Jail来监控。
然后就是API的处理。
Fail2ban自带了一个action用于CloudFlare的API,只要登录CloudFlare的管理页面,即可取得一个全局的Token,加上你的登录用户邮箱,配置到etc/fail2ban/action.d/cloudflare.conf
里的cfuser
和cftoken
即可。
使用方法就是创建单独的Jail监控相应的日志文件,然后在这个Jail里使用cloudflare这个action。
不过个人觉得用全局token有点不太安全,所以我是单独创建了一个zone级别的token(可以选择授权给多个或全部的zones),然后用zone级别的API来调用,所以需要修改action配置。
复制cloudflare.conf
为cloudflare-zone.conf
,然后修改内容如下(已去除注释):
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -s -o /dev/null -X POST <_cf_api_prms> \
-d '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
<_cf_api_url>
actionunban = id=$(curl -s -X GET <_cf_api_prms> \
"<_cf_api_url>?mode=block&configuration_target=ip&configuration_value=<ip>&page=1&per_page=1¬es=Fail2Ban%%20<name>" \
| { jq -r '.result[0].id' 2>/dev/null || tr -d '\n' | sed -nE 's/^.*"result"\s*:\s*\[\s*\{\s*"id"\s*:\s*"([^"]+)".*$/\1/p'; })
if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
curl -s -o /dev/null -X DELETE <_cf_api_prms> "<_cf_api_url>/$id"
_cf_api_url = https://api.cloudflare.com/client/v4/zones/<zone_id>/firewall/access_rules/rules
_cf_api_prms = -H 'Authorization: Bearer <cftoken>' -H 'Content-Type: application/json'
[Init]
cftoken = >>>your_token<<<
其实就修改了几个地方:
- 去掉了cfuser,只需要cftoken即可
- 修改cftoken的请求头
_cf_api_prms
,因为全局token和zone级token用法不同 - 修改了
_cf_api_url
,使用zone级别的API路径
Jail里调用action时需要带上zone_id
参数,zone_id
显示在CloudFlare的zone管理页面右侧下部位置:
action = cloudflare-zone[zone_id=>>>your_zone_id<<<]
另外,复制jail时要注意同时修改jail名称(不只是文件名称)。
推送到[go4pro.org]