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很多的时候,会产生较大的性能消耗
posted @ 2025-06-05 16:29  董少奇  阅读(128)  评论(0)    收藏  举报