WAF開發之灰度轉發
VSole2021-07-02 17:01:02
簡介
突然心血來潮想寫一下基于lua實現灰度轉發的文章。
根據前文內容的openresty處理階段這一環節,假如要實現灰度流量的轉發,需要在balancer這個階段進行處理。這個階段類似于nginx的upstream作用域
upstream backend { server test.com test;}
原生的upstream其實是可以實現灰度流量轉發的,但是主要策略是基于權重比例來流量的轉發,無法實現顆粒度細的流量轉發,為此,我使用lua代碼實現了四種灰度流量轉發策略:
基于權重比例轉發;
基于IP地址轉發;
基于地區位置轉發;
基于HTTP字段轉發(通用);
首先需要了解在這個階段可以支持的操作有什么內容:
#告訴openresty流量要轉到什么后端服務器syntax:ok, err = balancer.set_current_peer(host, port) #設置嘗試錯誤次數syntax:ok, err = balancer.set_more_tries(count) #獲取上次失敗的原因,我用來剔除失效的后端服務器syntax:state_name, status_code = balancer.get_last_failure() #設置超時時間syntax:ok, err = balancer.set_timeouts(connect_timeout, send_timeout, read_timeout
接著根據上面的內容編寫一個轉發流量到后端的函數
--ip_lists是一個后端服務器列表,比如(192.168.1.2,192.168.1.3,192.168.1.4),port是固定的,local function forward_server(ip_lists, port)--設置錯誤嘗試失敗次數 if not ngx.ctx.tries then ngx.ctx.tries = 0 end#判斷后端服務器列表有多少個,確定重試次數 if ngx.ctx.tries < #ip_lists then local set_more_tries_ok, set_more_tries_err = balancer.set_more_tries(1) if not set_more_tries_ok then ngx.log(ngx.ERR, "failed to set the current peer: ", set_more_tries_err) elseif set_more_tries_err then ngx.log(ngx.ALERT, "set more tries: ", set_more_tries_err) end end
ngx.ctx.tries = ngx.ctx.tries + 1#確定有效的后端服務器列表 if not ngx.ctx.ip_lists then ngx.ctx.ip_lists = ip_lists end#確定客戶端IP指向一個后端服務器,用于保持會話 local first_count = {} table.insert(first_count, string.sub(ngx.var.remote_addr, 1, 1)) table.insert(first_count, string.sub(ngx.var.remote_addr, -1)) local ip_count = (tonumber(table.concat(first_count)) % #ngx.ctx.ip_lists) + 1
local _host = ngx.ctx.ip_lists[ip_count] local state_name, state_code = balancer.get_last_failure()#剔除失效后端服務器 if state_name == "failed" then for k, v in ipairs(ngx.ctx.ip_lists) do if v == _host then if not (#ngx.ctx.ip_lists == 1) then table.remove(ngx.ctx.ip_lists, k) ip_count = (string.sub(ngx.var.remote_addr, -1) % #ngx.ctx.ip_lists) + 1 _host = ngx.ctx.ip_lists[ip_count] end end end end#一切沒有問題之后,直接流量轉發 local ok, err = balancer.set_current_peer(_host, port)
if not ok then ngx.log(ngx.ERR, "failed to set the current peer: ", err) end
end
最后聊聊四個灰度轉發策略的編寫
基于權重比例轉發策略
基于權重比例比較簡單,就是使用隨機數落到那一個后端服務器里面。
比如{“192.168.1.20”:“20”,“192.168.1.30”:30}
local weight_list = {}local iplist = {}iplist['192.168.1.20']=20iplist['192.168.1.30']=30
for key,value in pairs(iplist) do for i=1, value do table.insert(weight_list,key) endend
local random_value = math.random(1, #weight_list)print(random_value)print(weight_list[random_value])

基于IP比例轉發策略
local iputils = require "resty.waf.iputils"
基于iputils 庫,用于處理客戶端IP的地址轉發(支持IP地址和網段)
local ip_forward_list={"192.168.1.2","192.168.20.0/24"}local ip_list = iputils.parse_cidrs(ip_forward_list)
if iputils.ip_in_cidrs(remote_ip, ip_list) then --如果IP段灰度策略符合,轉發到灰度服務器 return forward_server(gray_server, gray_port)end
基于地區灰度轉發策略
local geo = require 'resty.waf.maxminddb'
if not geo.initted() then geo.init("/opt/GeoLite2-City.mmdb") --需要在該目錄設置geo庫end
local res, err = geo.lookup(remote_ip)
if res then if res['city'] then local city_name = res['city']['names']['zh-CN'] --查看當前IP所屬的城市 --ngx.log(ngx.ERR,city_name) for _, _value in pairs(region_forward_data) do
local gray_server = _value['gray_server'] local gray_port = _value['gray_port']
local region = _value['content'][1]['content'] if region == city_name then return forward_server(gray_server, gray_port) --如果地區灰度策略符合,轉發到灰度服務器 end end endend
基于通用配置轉發策略
我是用jxwaf里面的處理HTTP字段代碼,就不造輪子,具體開發思路可以參考jxwaf的自定義規則功能
local waf = require "resty.waf.waf"local request = require "resty.waf.request"local operator = require "resty.waf.operator"local transform = require "resty.waf.transform"
PS:有人對CC防御的模塊開發有興趣嗎?
VSole
網絡安全專家