Nginx+Lua+Redis实现自定义waf防火墙

《centos系统安装Nginx+Lua+Redis实现自定义web防火墙》 还没有写完,讲了几遍的安装方法,今天抽出一点时间把自定义waf防火墙完善。

#vi /data/conf/nginx/conf/nginx.conf
写以下配置
http {
    #其他配置
    lua_package_path "/data/conf/nginx/lua-resty-redis/lib/resty/redis.lua";
}
location /access_by_redis {
        default_type 'text/html';
        access_by_lua_file "/data/conf/lua/access_by_redis.lua";
    }

实验一(连接redis测试不通过): 

# vi /data/conf/lua/access_by_redis.lua

写入一下配置:

local red = redis:new()
function M:redis()
        red:set_timeout(1000)
        local ok, err = red:connect("127.0.0.1",6379)
        if not ok then
                ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
        end
end


访问:http://192.168.1.3/access_by_redis/ 报错,查看日志:
lua entry thread aborted: runtime error: /data/conf/lua/access_by_redis.lua:1: attempt to index global ‘redis’ (a nil value)
stack traceback:
coroutine 0:
        /data/conf/lua/access_by_redis.lua: in function </data/conf/lua/access_by_redis.lua:1>, client: 192.168.1.251, server: localhost, request: “GET /access_by_redis/ HTTP/1.1”, host: “192.168.1.3”

实验二、(连接redis测试通过): 

nginx配置:

location /access_by_redis {
        default_type 'text/html';
        access_by_lua_file "/data/conf/lua/access_by_redis2.lua";
    }

vi /data/conf/lua/access_by_redis2.lua


写入一下配置:

redis = require('resty.redis')
redis_init = redis.new()
redis_init:set_timeout(1000)
redis_init:connect('127.0.0.1','6379')
--redis_init:auth('123456')
resp = redis_init:set('funet', '888888')
resp = redis_init:get('funet')
ngx.say(resp)

访问:http://192.168.1.3/access_by_redis/ 页面显示“888888”

验证:
[root@centos-03]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> get funet
“888888”

实验三(限制IP访问,测试不通过):

nginx配置:

location /access_by_redis {
        default_type 'text/html';
        access_by_lua_file "/data/conf/lua/access_by_redis3.lua";
    }

vi /data/conf/lua/access_by_redis3.lua

写入以下:

ip_bind_time = 300  --封禁IP时间
ip_time_out = 60    --指定ip访问频率时间段
connect_count = 100 --指定ip访问频率计数最大值
--连接redis
local redis = require "resty.redis"
local cache = redis.new()
local ok , err = cache.connect(cache,"127.0.0.1","6379")
cache:set_timeout(60000)
--如果连接失败,跳转到脚本结尾
if not ok then
  goto A
end
--查询ip是否在封禁段内,若在则返回403错误代码
--因封禁时间会大于ip记录时间,故此处不对ip时间key和计数key做处理
is_bind , err = cache:get("bind_"..ngx.var.remote_addr)
if is_bind == 1 then
  ngx.exit(403)
  goto A
end
start_time , err = cache:get("time_"..ngx.var.remote_addr)
ip_count , err = cache:get("count_"..ngx.var.remote_addr)
--如果ip记录时间大于指定时间间隔或者记录时间或者不存在ip时间key则重置时间key和计数key
--如果ip时间key小于时间间隔,则ip计数+1,且如果ip计数大于ip频率计数,则设置ip的封禁key为1
--同时设置封禁key的过期时间为封禁ip的时间
if start_time == ngx.null or os.time() - start_time > ip_time_out then
  res , err = cache:set("time_"..ngx.var.remote_addr , os.time())
  res , err = cache:set("count_"..ngx.var.remote_addr , 1)
else
  ip_count = ip_count + 1
  res , err = cache:incr("count_"..ngx.var.remote_addr)
  if ip_count >= connect_count then
    res , err = cache:set("bind_"..ngx.var.remote_addr,1)
        res , err = cache:expire("bind_",ip_bind_time)
  end
end
--结尾标记
::A::
local ok, err = cache:close()

访问:http://192.168.1.3/access_by_redis/ 页面显示“funet8”
验证:

[root@centos-03 redis]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> get count_192.168.1.251
"2"
127.0.0.1:6379> get time_192.168.1.251
"1495523038"

刷新页面很多次,并没有实现403效果,把配置ip_time_out,connect_count改小也无效果。

实验四(防刷代码测试通过):

nginx配置:

location /access_by_redis {
        default_type 'text/html';
        access_by_lua_file "/data/conf/lua/access_by_redis4.lua";
    }

vi /data/conf/lua/access_by_redis4.lua


参考:http://blog.csdn.net/fenglvming/article/details/51996406
写入以下配置:

--防刷代码
local function close_redis(red)
    if not red then
        return
    end
    --释放连接(连接池实现)
    local pool_max_idle_time = 10000 --毫秒
    local pool_size = 100 --连接池大小
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)
    end
end
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ip = "127.0.0.1"	---修改变量
local port = "6379"	---修改变量
local ok, err = red:connect(ip,port)
if not ok then
    return close_redis(red)
end
local clientIP = ngx.req.get_headers()["X-Real-IP"]
if clientIP == nil then
   clientIP = ngx.req.get_headers()["x_forwarded_for"]
end
if clientIP == nil then
   clientIP = ngx.var.remote_addr
end
local incrKey = "user:"..clientIP..":freq"
local blockKey = "user:"..clientIP..":block"
local is_block,err = red:get(blockKey) -- check if ip is blocked
if tonumber(is_block) == 1 then
   ngx.exit(ngx.HTTP_FORBIDDEN)
   return close_redis(red)
end
res, err = red:incr(incrKey)
if res == 1 then
   res, err = red:expire(incrKey,1)
end
if res > 200 then	--每秒200次访问即视为非法,会阻止10分钟的访问
    res, err = red:set(blockKey,1)
    res, err = red:expire(blockKey,600)
end
close_redis(red)

打开:http://192.168.1.3/access_by_redis/
正常访问redis数据库中无记录值。
将if res > 200 then 改为 “if res > 2 then”多次刷新页面出现“403 Forbidden”
查看redis数据库:
[root@centos-03 rule-config]# redis-cli -h 127.0.0.1
127.0.0.1:6379> get user:192.168.1.251:block
“1”

实验五(限流,测试通过):

限流的目的是在大促或者流量突增期间,我们的后端服务假设某个接口能够扛住的的QPS为10000,这时候同时有20000个请求进来,经过限流模块,会先放10000个请求,其余的请求会阻塞一段时间。不简单粗暴的返回404,让客户端重试,同时又能起到流量销峰的作用。

nginx配置:

location /access_by_redis {
        access_by_lua_file "/data/conf/lua/access_by_redis5.lua";
    }

vi /data/conf/lua/access_by_redis5.lua


限流代码:

local function close_redis(red)
    if not red then
        return
    end
    --释放连接(连接池实现)
    local pool_max_idle_time = 10000 --毫秒
    local pool_size = 100 --连接池大小
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)
    end
end
local function wait()
   ngx.sleep(1)
end
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ip = "127.0.0.1"	---修改变量
local port = "6379"	---修改变量
local ok, err = red:connect(ip,port)
if not ok then
    return close_redis(red)
end
local uri = ngx.var.uri -- 获取当前请求的uri
local uriKey = "req:uri:"..uri
res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)
while (res > 10)
do
   local twait, err = ngx.thread.spawn(wait)
   ok, threadres = ngx.thread.wait(twait)
   if not ok then
      ngx_log(ngx_ERR, "wait sleep error: ", err)
      break;
   end
   res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)
end
close_redis(red)

ab压力测试:

# ab -n 1000 -c 100 http://192.168.1.3/access_by_redis/
[root@centos-03 rule-config]# redis-cli -h 127.0.0.1
127.0.0.1:6379> get req:uri:/access_by_redis/
"98"
127.0.0.1:6379> get req:uri:/access_by_redis/index.html
"3"
127.0.0.1:6379> get req:uri:/access_by_redis/index.html
"11"
127.0.0.1:6379> get req:uri:/access_by_redis/
"66"
127.0.0.1:6379> get req:uri:/access_by_redis/
"54"

以上为配置自定义防火墙。

上一篇
下一篇