OpenResty动态添加黑名单记录实践

近期,因为某种缘由,让我这边再次关注到服务器身上。在咱们的服务器中,自己是使用 nginx 做为 web 服务器的,虽然 nginx 自己支持限流和 ip 限制设置,但在动态设置黑名单中,nginx 这边自己是作不了。这时候,就可使用到 OpenResty 了。html

OpenResty

关于 OpenResty,其实网上和自己中文主页已经有很是详细的介绍了,我这边就不一一阐述了,惟一要注意的是,由于 OpenResty 自己已经包含 nginx 了,若是以前服务器上已经在使用 nginx,必须先把当前版本的 nginx 卸载掉,而后再安装 OpenResty,因此须要先把原来的 nginx 服务停掉而且备份以前的配置文件,而后再进行安装,我这边是经过 yum 进行安装的,具体使用的指令nginx

# 删除本来的nginx,记得先备份配置
systemctl disable nginx.service
rm -rf /usr/lib/systemd/system/nginx.service
yum erase nginx 
 # 安装 yum-utils 
yum install yum-utils
# yum 安装 openresty
yum install openresty
 # 配置 nginx profile PATH
PATH=/usr/local/openresty/nginx/sbin:$PATH
export PATH
# 指定配置
nginx -c /usr/local/openresty/nginx/conf/nginx.conf 
复制代码

至此,关于 OpenResty 的简单准备就完成了,下面咱们来看下如何经过 lua 脚本实现动态黑名单配置。git

动态黑名单脚本

首先,由于实践上是使用 OpenResty 以及下面的 redis 组件,若是自己对 lua 和 redis 不太熟悉的话,须要先基本了解下相关的知识,这里能够去查阅下 OpenResty 中关于 redis 组件和 nginx 组件的相关说明。github

而后,我这边是参考起航天空[1]博主这篇文章的作法,而且作了某些小调整。web

在这个过程当中,博主给到我这边许多意见和见解,而且很耐心地听取我这边的一些建议和给到解答,对于本人在运维方面上,也给到一些其余的解决方法,在此表示十分的感谢。redis

下面就直接发下处理的代码,首先是配置代码:shell

set $redis_service "127.0.0.1";
set $redis_port 6380;
set $redis_db 0;
# 1 second 50 query
set $black_count 50;
set $black_rule_unit_time 1;
set $black_ttl 3600;
set $auto_blacklist_key blackkey;
复制代码

这里跟例子中的配置没什么明显的差异,分别来讲明一下各个配置的含义:apache

  • redis_service:redis 服务器 ip 地址
  • redis_port:redis 服务器端口
  • redis_db:所使用的redis db
  • black_count:拉黑限制的最大访问次数
  • black_rule_unit_time:拉黑限制次数的保存时间,即保存访问次数的 kv 的 ttl
  • black_ttl:黑名单的存活时间,由于我这里是永久存货,因此没使用到
  • auto_blacklist_key:kv 的部分 key

这个依据我的喜爱和需求来设定,通常状况下控制好 black_count 和 black_rule_unit_time 就行。bash

接着是这个具体的 lua 脚本代码,其中大部分也是按照例子中的来:服务器

local redis_service = ngx.var.redis_service
local redis_port = tonumber(ngx.var.redis_port)
local redis_db = tonumber(ngx.var.redis_db)
local black_count = tonumber(ngx.var.black_count)
local black_rule_unit_time = tonumber(ngx.var.black_rule_unit_time)
local cache_ttl = tonumber(ngx.var.black_ttl)
local remote_ip = ngx.var.remote_addr

-- 计数
function my_count(redis, status_key, count_key)
    local key = status_key
    local key_connect_count = count_key

    local Status = redis:get(key)
    local count = redis:get(key_connect_count)

    if Status ~= ngx.null then
        -- 状态为connect 且 count不为空 且 count <= 拉黑次数
        if (Status == "Connect" and count ~= ngx.null and tonumber(count) <= black_count) then
            -- 再读一次
            count = redis:incr(key_connect_count)
            ngx.log(ngx.ERR, "count:", count) 
            if count ~= ngx.null then
                if tonumber(count) > black_count then
                    redis:del(key_connect_count)
                    redis:set(key,"Black")
                    -- 永久封禁
                    -- Redis:expire(key,cache_ttl)
                else
                    redis:expire(key_connect_count,black_rule_unit_time)
                end
            end
        else
            ngx.log(ngx.ERR,"The visit is blocked by the blacklist because it is too frequent. Please visit later.")
            return ngx.exit(ngx.HTTP_FORBIDDEN)
        end
    else
        local count = redis:get(key)
        if count == ngx.null then
            redis:del(key_connect_count)
        end
        redis:set(key,"Connect")
        redis:set(key_connect_count,1)
        redis:expire(key,black_rule_unit_time)
        redis:expire(key_connect_count,black_rule_unit_time)
    end
end 

-- 读取token
local token
local header = ngx.req.get_headers()["Authorization"]
if header ~= nil then
    token = string.match(header, 'token (%x+)')
end

local redis_connect_timeout = 60
local redis = require "resty.redis"
local Redis = redis:new()
local auto_blacklist_key = ngx.var.auto_blacklist_key

Redis:set_timeout(redis_connect_timeout)

local RedisConnectOk,ReidsConnectErr = Redis:connect(redis_service,redis_port)
local res = Redis:auth("password");

if not RedisConnectOk then
    ngx.log(ngx.ERR,"ip_blacklist connect Redis Error :" .. ReidsConnectErr)
else
    -- 链接成功
    Redis:select(redis_db)

    local key = auto_blacklist_key..":"..remote_ip
    local key_connect_count = auto_blacklist_key..":key_connect_count:"..remote_ip

    my_count(Redis, key, key_connect_count)

    if token ~= nil then
        local token_key, token_key_connect_count
        token_key = auto_blacklist_key..":"..token
        token_key_connect_count = auto_blacklist_key..":key_connect_count:"..token
        my_count(Redis, token_key, token_key_connect_count)
    end
end
复制代码

由于在 lua 的实践上本人也是属于一个新手的级别,因此在结构性上都有很明显的问题,这里先留一个坑。

先解释下这段代码,由于我这边是从 ip 及 token(访问凭证) 入手来控制,因此先将参考例子中的计数整合在一个 function 当中。function 里头本来例子中是使用 set 方法来作加一操做的,因此在大量请求进入的时候,会产生一个同步问题,因此我这边稍微改造一下,使用 incr 来作一个自增操做,而且在进入方法时就获取计数值并判断计数值大小是否超过阈值 black_count,一次来规避大量请求时产生的问题。

接着是下面获取 token 中,我是根据应用中使用的凭证作法,从头部得到 Authorization,而且从中截取来拿到 token,若是 token 为空,就证实不须要通过 token 的计数处理。

最后是链接并调用函数了,这里没什么要说明的地方,主要说明在定义 function 和使用 function 的顺序须要注意一下。

而后是实配到 nginx 的 conf 当中了:

server {
  listen 80;
  server_name blog.mintrumpet.fun;
  root  /~/public;
  # 加载配置文件
  include /etc/nginx/conf.d/blacklist_params;
  # 指定请求中须要执行的 lua 脚本
  access_by_lua_file /etc/nginx/conf.d/ip_blacklist.lua;
  location / {
  }
  error_log /etc/nginx/conf.d/log/error.log;
  access_log /etc/nginx/conf.d/log/access.log;
}
复制代码

以上,配置就完成了,在 console 中重启下 nginx nginx -s reload,就能够实现动态添加黑名单的须要了。至于对于添加到黑名单的 ip 及 token,须要怎么作下一步的处理,这边就给服务器下的具体应用来处理,在这里不阐述。

测试

本人在过程当中是使用我的的服务器里的博客,以及 apache bench 工具来作测试的。

先测试一个不带 token(游客) 的例子,访问一个静态文件,

我以10秒50次做为限制,首先是4个并发访问40次:

ab -n 40 -c 4 http://blog.mintrumpet.fun/dist/music.js
复制代码

在执行结果中,能够看到40个请求都顺利完成。

再看下 redis 下的值,

还行,还没超过限制的大小。

接着4个并发访问100次:

ab -n 100 -c 4 http://blog.mintrumpet.fun/dist/music.js
复制代码

从结果能够看到,里面有49条请求访问失败,显然都被转到403了。

再看下 redis 下的值,

很显然,个人ip被屏蔽了,接着去访问的时候,提示403错误,OK,目的达到了。

接着来测试一个 token 的例子,一样也是访问一个静态文件,也是4个并发访问100次:

ab -n 100 -c 4 -H "Authorization:token 87BF813C6DDB9C01D4525F47908D4C9F" http://blog.mintrumpet.fun/dist/music.js
复制代码

结果也是同样,这个token被屏蔽了,后续的访问也转发到403。

至此,整个测试就到此结束了,能够看到不管是游客访问,仍是身份访问,都能起到一样的效果。

小结

其实这种作法只能用做通常的状况,并且在配置及编写的脚本文件中,还有不少须要改善的地方,在此我也只是有样学样,各位读者若是有更好的作法和方案,能够在下面的讨论中告诉读者我。同时在跟起航天空的博主交流的时候,他也告诉我其余作限流和拦截的处理来抵制攻击和监控,例如 fail2ban、OSSEC等。而 nginx 自身也提供一系列限流措施,有兴趣的各位能够自行学习。

至此,关于 OpenResty 的实践就到这里结束了,你们能够关注下个人博客[2] blog.mintrumpet.fun/ 或者 芦苇科技 来跟我作关于技术上的交流,那么今天就到此结束吧。Enjoy Coding!


小喇叭

广州芦苇科技Java开发团队

芦苇科技-广州专业互联网软件服务公司

抓住每一处细节 ,创造每个美好

关注咱们的公众号,了解更多

想和咱们一块儿奋斗吗?lagou搜索“ 芦苇科技 ”或者投放简历到 server@talkmoney.cn 加入咱们吧

关注咱们,你的评论和点赞对咱们最大的支持


  1. 起航天空,www.qhjack.cn/ ↩︎

  2. 小喇叭的小屋,blog.mintrumpet.fun/ ↩︎

相关文章
相关标签/搜索