不知道大家在平时运维中,当我们需要更新白名单时候这个操作是否频繁,如果是大家是怎么做的,是手动登陆服务器新增白名单后reload nginx吗? 今天博主会分享一直比较通用的方法给大家,就是基于openresty + lua +redis实现。如果你有更好的方法可以联系博主交流学习。

如果还有小伙伴不知道openresty是啥来的,可以查看官网地址进行了解:http://openresty.org/cn/
同时需要了解lua-resty-redis ,参考链接 https://github.com/openresty/lua-resty-redis

简单分享下整个流程的流程图:

lua执行流程图

白名单动态判断流程图

简单粗暴直接上代码:

1
2
3
--- reids  key setting
HSET waf_status servername true/false ---> waf_status:set(ngx.var.server_name,true)
SADD servername..location:ip_whitelist || sadd www.abc.test/test:ip_whitelist 192.168.134.1 ---> redis_key:set(ip:true)

1
2
3
4
5
--- ngx lua_shared_dict
http{
lua_shared_dict ip_whitelist 10m;
lua_shared_dict waf_status 1m;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
location{
access_by_lua_file /opt/openresty/lua/ip_whitelist.lua;
}

--- redis 基础变量
local redis_host = "127.0.0.1"
local redis_port = 6379
local redis_auth = "password"
local redis_connection_timeout = 1000
local redis_key = ngx.var.server_name..ngx.var.location..":ip_whitelist"




local waf_status = ngx.shared.waf_status
redis_key = ngx.shared.ip_whitelist
--ngx.say(ngx.var.server_name..ngx.var.location..":ip_whitelist")
local ip_whitelist = ngx.shared.ip_whitelist

---update time
local ip_whitelist_cache_ttl = 1
local waf_status_cache_ttl = 1


local ip_whitelist_last_update_time = ip_whitelist:get("last_update_time");
local waf_status_update_time = waf_status:get("last_update_time");


if ip_whitelist_last_update_time == nil or ip_whitelist_last_update_time < ( ngx.now() - ip_whitelist_cache_ttl ) then
local redis = require "resty.redis";
local red = redis:new();
red:set_timeout(redis_connect_timeout);

local ok, err = red:connect(redis_host, redis_port);
if not ok then
ngx.log(ngx.DEBUG, "Redis connection error:" .. err);
return
end

local res, err = red:auth(redis_auth)
if not res then
ngx.say("failed to authenticate: ", err)
return
end


local ip_list_res, err = red:smembers(ngx.var.server_name..ngx.var.location..":ip_whitelist");
if err then
ngx.log(ngx.DEBUG, "Redis read error: " .. ip_list_res);
else
redis_key:flush_all();
for index, ip in ipairs(ip_list_res) do
redis_key:set(ip, true);
end
ip_whitelist:set("last_update_time", ngx.now());
end
end



if waf_status_update_time == nil or waf_status_update_time < ( ngx.now() - waf_status_update_time ) then

local redis = require "resty.redis";
local red = redis:new();

local ok, err = red:connect(redis_host, redis_port);
if not ok then
ngx.log(ngx.DEBUG, "Redis connection error:" .. err);
return
end

local res, err = red:auth(redis_auth)
if not res then
ngx.say("failed to authenticate: ", err)
return
end

local waf_statu, err = red:hget("waf",ngx.var.server_name);
if err then
ngx.log(ngx.DEBUG, "Redis read: " .. err);
else
if waf_statu == "true" then
waf_status:flush_all();
waf_status:set("waf_status_".. ngx.var.server_name,true);
else
waf_status:flush_all();
waf_status:set("waf_status_".. ngx.var.server_name,false);
end
waf_status:set("last_update_time", ngx.now());
end
end

local ipadd = ngx.var.remote_addr;

if waf_status:get(ngx.var.server_name) then
ipadd = ngx.var.x_forwarded_for;
end

---内网不拦截
local m, err = ngx.re.match(ipadd, "(^192\\.168\\.)|(^172\\.16\\.)")

if not m then
if not(redis_key:get(ipadd)) then
return ngx.exit(ngx.HTTP_FORBIDDEN);
end
end
简单测试

以上只是简单分享,具体还需要结合公司情况利用,当然还可以结合其他神器使用:
例如https://github.com/hamishforbes/lua-resty-iputils
除了神器,还可以基于redis上通过django做web界面,基于界面上更新白名单。

Comments

2018-03-10