openresty动态封禁异常访问IP
起因
公司的官网访问异常,排查发现单个ip发送恶意流量,造成每秒300Mb的访问流量,于是有了技术问题:如何对这种ip流量进行动态封禁。
简介
openresty是基于nginx+lua的高性能web平台, 可以使用Lua脚本语言对nginx的功能进行扩展,特点是可以灵活实现需要的复杂功能,而lua本身性能也很高(网上这么说,待验证)。
当然如果你觉得lua性能不够好,可以自己开发一个nginx模块,然后重新编译nginx,但是这个技术方案需要多久就不清楚了,而openresty对于一个只了解nginx的我来说,只需要半天就能实现该功能(感谢腾讯元宝,技术问题检索可以完全替代搜索引擎了)。
openresty编译安装
# 下载openresty-1.27.1.2.tar.gz, 解压进入到目录
# 查看编译选项
./configure --help
# 我这里下下载了openssl源码一起编译,否则会报关于ssl的报错,应该是系统自带的openssl版本太低
./configure --prefix=/usr/local/openresty --with-openssl=../openssl-1.1.1k
# 编译安装
gmake -j8 && gmake install
# 启动
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -c conf/nginx.conf
# 重载配置文件
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/openresty/nginx/ -c conf/nginx.conf -s reload
动态黑名单配置
http {
# 黑名单列表和计数器缓存大小,需要根据实际访问量设定
# 缓存不足时使用lru算法清理缓存
lua_shared_dict ip_blacklist 10m;
lua_shared_dict ip_counters 20m;
server {
listen 12345;
access_by_lua_block {
# 通过ngx.var.xx获取客户端ip,这个需要抓包确认
# tcpdump -A -i ens192 tcp port 80
local client_ip = ngx.var.http_x_forwarded_for
local blacklist = ngx.shared.ip_blacklist
local counters = ngx.shared.ip_counters
# 如果客户端ip已经在黑名单中了, 就返回403
if blacklist:get(client_ip) then
ngx.log(ngx.WARN, "IP blocked: ", client_ip)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
# 增加客户端访问计数
# 这里和redis缓存有点类似,client_ip是键值,
# 1表示加1,0表示不存在时的初始值为0
# 60表示有效时间为60秒, 只有第一次设置的时候有效
local newval, err = counters:incr(client_ip,1,0,60)
# 为了测试方便,这里设置为1分钟内访问超过2次就会进黑名单
if newval > 2 then
# 拉黑10分钟
blacklist:set(client_ip, true, 600)
ngx.log(ngx.ALERT, "IP blocked for high rate: ", client_ip)
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
location / {
default_type text/html;
content_by_lua_block {
ngx.say("hello world")
}
}
}
}
测试命令
curl -H "X-Forwarded-For:1.1.1.1" https://127.0.0.1:12345/
测试结果
连续访问第三次就会报错
[root@dsqtest conf]# curl -H "X-Forwarded-For:1.1.1.1" https://127.0.0.1:12345/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty/1.27.1.2</center>
</body>
</html>
经验
- 编译的时候可以考虑下载openssl, 然后编译的时候使用参数指定openssl目录;
- lua脚本中的函数引用是通过‘:'连接的,而不是常规的'.';
- http请求头X-Forwarded-For对应的nginx变量名是http_x_forwarded_for,而不是x_forwarded_for;
- 演示中我用的X-Forwarded-For提取客户端ip,正常的做法应该是在最外层的代理服务器上把客户端ip记录到X-Real-Ip中,然后通过ngx.var.x_real_ip提取客户端ip。具体情况可以抓包看一下哪里可以取到客户端ip;
- lua报错会在error.log中记录,可以定位到配置文件具体的行号;
- lua_shared_dict是在共享内存中的,所以所有的worker进程都能访问到,其内存管理算法为lru,不需要使用定时任务去清理内存,定时任务清理可能带来很大的开销并且阻塞进程。
- ngx.shared.DICT.get_keys(0)并不会返回过期的key,而且当key很多的时候,会产生较大的性能消耗

浙公网安备 33010602011771号