《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"
以上为配置自定义防火墙。