WAF代码剖析之init*阶段
字数 1273 2025-08-15 21:32:26
JXWAF初始化阶段代码剖析教学文档
1. 概述
本文详细剖析JXWAF在init阶段的实现原理和行为,包括init_by_lua_file和init_worker_by_lua_file两个关键阶段。
2. OpenResty初始化阶段
2.1 init_by_lua_file
作用:当Nginx master进程加载配置文件时运行指定的Lua脚本,用于注册全局变量或预加载模块。
配置示例:
init_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/init.lua;
2.2 init_worker_by_lua_file
作用:在每个Nginx worker进程启动时调用指定的Lua代码,用于创建定时器、健康检查或重载Nginx。
配置示例:
init_worker_by_lua_file /opt/jxwaf/lualib/resty/jxwaf/init_worker.lua;
3. init.lua详细解析
3.1 lua-resty-core性能优化
原理:使用FFI模式重新实现lua-nginx-module的API,大幅提升性能。
性能对比:
- 旧版(1.13.6.2):10亿次base64加密耗时80秒
- 新版(使用lua-resty-core):10亿次base64加密耗时10秒,性能提升8倍
代码示例:
require 'resty.core'
local start = ngx.now()
for _ =1, 1000000000 do
ngx.encode_base64('123456')
end
ngx.update_time()
ngx.say(ngx.now() - start)
3.2 WAF初始化流程
主要步骤:
- 引入WAF模块
- 加载配置文件
- 初始化配置
代码结构:
local waf = require "resty.jxwaf.waf"
local config_path = "/opt/jxwaf/nginx/conf/jxwaf/jxwaf_config.json"
waf.init(config_path)
3.3 waf.lua核心实现
3.3.1 模块定义
local _M = {} -- 定义模块表
local _config_path = "/opt/jxwaf/nginx/conf/jxwaf/jxwaf_config.json"
3.3.2 初始化函数
function _M.init(config_path)
-- 读取配置文件
local init_config_path = config_path or _config_path
local read_config = assert(io.open(init_config_path,'r+'))
local raw_config_info = read_config:read('*all')
read_config:close()
-- 解析JSON配置
local config_info = cjson.decode(raw_config_info)
if config_info == nil then
ngx.log(ngx.ERR,"init fail,can not decode config file")
end
-- 生成节点UUID
if not config_info['waf_node_uuid'] then
local waf_node_uuid = uuid.generate_random()
config_info['waf_node_uuid'] = waf_node_uuid
local new_config_info = cjson.encode(config_info)
local write_config = assert(io.open(init_config_path,'w+'))
write_config:write(new_config_info)
write_config:close()
end
_config_info = config_info
-- IP处理工具初始化
iputils.enable_lrucache()
-- 启用特权代理进程
local ok, err = process.enable_privileged_agent()
if not ok then
ngx.log(ngx.ERR, "enables privileged agent failed error:", err)
end
ngx.log(ngx.ALERT,"jxwaf init success,waf node uuid is ".._config_info['waf_node_uuid'])
end
3.3.3 IP白名单实现
init_by_lua_block {
local iputils = require("resty.iputils")
iputils.enable_lrucache() -- 创建全局缓存空间
local whitelist_ips = {
"127.0.0.1",
"10.10.10.0/24",
}
whitelist = iputils.parse_cidrs(whitelist_ips)
}
access_by_lua_block {
local iputils = require("resty.iputils")
if not iputils.ip_in_cidrs(ngx.var.remote_addr, whitelist) then
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
4. init_worker.lua详细解析
4.1 工作进程初始化
local waf = require "resty.jxwaf.waf"
waf.init_worker()
4.2 工作进程处理逻辑
4.2.1 特权代理进程处理
if process.type() == "privileged agent" then
-- 节点监控
if _config_info.waf_node_monitor == "true" then
local monitor_ok,monitor_err = ngx.timer.at(0,_momitor_update)
if not monitor_ok and monitor_err ~= "process exiting" then
ngx.log(ngx.ERR, "failed to create the init timer: ", init_err)
end
end
-- 全局规则更新
local init_ok,init_err = ngx.timer.at(0,_global_update_rule)
if not init_ok and init_err ~= "process exiting" then
ngx.log(ngx.ERR, "failed to create the init timer: ", init_err)
end
else
-- 普通worker进程处理
local worker_init_ok,worker_init_err = ngx.timer.at(0,_worker_update_rule)
if not worker_init_ok and worker_init_err ~= "process exiting" then
ngx.log(ngx.ERR, "failed to create the init timer: ", worker_init_err)
end
-- 定时更新规则
local hdl, err = ngx.timer.every(5,_worker_update_rule)
if err then
ngx.log(ngx.ERR, "failed to create the worker update timer: ", err)
end
end
4.2.2 监控更新函数
local function _momitor_update()
local _update_website = _config_info.waf_monitor_website or "https://update2.jxwaf.com/waf_monitor"
local httpc = http.new()
httpc:set_timeouts(5000, 5000, 30000) -- 设置超时
-- 准备请求参数
local api_key = _config_info.waf_api_key or ""
local api_password = _config_info.waf_api_password or ""
local server_info = _config_info.server_info or ""
local waf_node_uuid = _config_info.waf_node_uuid or ""
-- 发送监控数据
local res, err = httpc:request_uri(_update_website, {
method = "POST",
body = "api_key="..api_key.."&api_password="..api_password.."&waf_node_uuid="..waf_node_uuid.."&server_info="..server_info,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
-- 错误处理
if not res then
ngx.log(ngx.ERR,"failed to request: ", err)
return _update_at(tonumber(_auto_update_period),_momitor_update)
end
-- 解析响应
local res_body = cjson.decode(res.body)
if not res_body then
ngx.log(ngx.ERR,"init fail,failed to decode resp body")
return _update_at(tonumber(_auto_update_period),_momitor_update)
end
if res_body['result'] == false then
ngx.log(ngx.ERR,"init fail,failed to request, ",res_body['message'])
return _update_at(tonumber(_auto_update_period),_momitor_update)
end
-- 更新监控状态
_waf_node_monitor = res_body['waf_node_monitor'] or _waf_node_monitor
if _waf_node_monitor == "true" then
local global_ok, global_err = ngx.timer.at(tonumber(_waf_node_monitor_period),_momitor_update)
if not global_ok and global_err ~= "process exiting" then
ngx.log(ngx.ERR, "failed to create the cycle timer: ", global_err)
end
end
ngx.log(ngx.ALERT,"monitor report success")
end
4.2.3 全局规则更新
local function _global_update_rule()
-- 获取规则更新
local _update_website = _config_info.waf_update_website or "https://update2.jxwaf.com/waf_update"
local httpc = http.new()
httpc:set_timeouts(5000, 5000, 30000)
-- 发送请求
local res, err = httpc:request_uri(_update_website, {
method = "POST",
body = "api_key=".._config_info.waf_api_key.."&api_password=".._config_info.waf_api_password.."&waf_node_uuid=".._config_info.waf_node_uuid,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
}
})
-- 错误处理
if not res then
ngx.log(ngx.ERR,"failed to request: ", err)
return _update_at(tonumber(_auto_update_period),_global_update_rule)
end
local res_body = cjson.decode(res.body)
if not res_body then
ngx.log(ngx.ERR,"init fail,failed to decode resp body")
return _update_at(tonumber(_auto_update_period),_global_update_rule)
end
-- 规则更新处理
if not res_body['no_update'] then
local tmp_waf_rule = res_body['waf_rule']
if tmp_waf_rule == nil then
ngx.log(ngx.ERR,"init fail,can not decode waf rule")
return _update_at(tonumber(_auto_update_period),_global_update_rule)
else
_update_waf_rule = tmp_waf_rule
end
-- 规则排序
local table_sort = table.sort
local function _sort_rules(a,b)
if a.rule_level == b.rule_level then
return tonumber(a.rule_id)<tonumber(b.rule_id)
else
return tonumber(a.rule_level)>tonumber(b.rule_level)
end
end
for k,v in pairs(_update_waf_rule) do
if type(v['custom_rule_set']) == "table" then
table_sort(v['custom_rule_set'],_sort_rules)
_update_waf_rule[k] = v
end
end
end
-- 加载语义分析防御
if res_body['jxcheck'] then
local load_jxcheck = loadstring(ngx.decode_base64(res_body['jxcheck']))()
if load_jxcheck then
_jx_check = load_jxcheck
end
end
-- 加载CC防护
if res_body['botcheck'] then
local load_botcheck = loadstring(ngx.decode_base64(res_body['botcheck']))()
if load_botcheck then
_bot_check = load_botcheck
end
end
-- 加载人机识别key
if res_body['bot_auth_key'] then
local bot_check_info = res_body['bot_auth_key']
bot_check_standard_info = bot_check_info['standard']
bot_check_image_info = bot_check_info['image']
bot_check_slipper_info = bot_check_info['slipper']
local standard_key = {}
local slipper_key = {}
local image_key = {}
for k,_ in pairs(bot_check_standard_info) do
table.insert(standard_key,k)
end
bot_check_standard_key = standard_key
for k,_ in pairs(bot_check_slipper_info) do
table.insert(slipper_key,k)
end
bot_check_slipper_key = slipper_key
for k,_ in pairs(bot_check_image_info) do
table.insert(image_key,k)
end
bot_check_image_key = image_key
end
-- 加载日志配置
if res_body['log_conf'] then
_log_conf = res_body['log_conf']
end
-- 保存到共享内存
local waf_common_conf = ngx.shared.waf_common_conf
local md5_succ, md5_err = waf_common_conf:set("md5",res_body['md5'])
if md5_err then
ngx.log(ngx.ERR,"init fail,can not set waf_common_conf md5")
return _update_at(tonumber(_auto_update_period),_global_update_rule)
end
local res_body_succ, res_body_err = waf_common_conf:set("res_body",res.body)
if res_body_err then
ngx.log(ngx.ERR,"init fail,can not set waf_common_conf res_body")
return _update_at(tonumber(_auto_update_period),_global_update_rule)
end
_md5 = res_body['md5']
ngx.log(ngx.ALERT,"global config info md5 is ".._md5..",update config info success")
end
5. 关键知识点总结
-
性能优化:使用lua-resty-core的FFI实现可以大幅提升性能(如base64加密性能提升8倍)
-
初始化流程:
- 读取并解析配置文件
- 生成节点UUID
- 初始化IP处理工具
- 启用特权代理进程
-
特权代理:
- 用于与WEB控制台通信
- 负责全局规则更新和节点监控
- 将规则保存在共享内存中供worker进程读取
-
规则处理:
- 自定义规则排序(按规则级别和ID)
- 动态加载语义分析和CC防护代码
- 人机识别key管理
-
错误处理:
- 使用
_update_at函数实现错误后的定时重试 - 详细的错误日志记录
- 使用
-
共享内存:
- 特权代理将规则保存在共享内存中
- worker进程从共享内存读取规则
- 使用MD5校验确保规则一致性
-
定时任务:
- 使用
ngx.timer.at和ngx.timer.every实现定时更新 - 支持周期性的健康检查和规则更新
- 使用
6. 最佳实践建议
-
配置检查:确保
jxwaf_config.json配置文件路径正确且可读写 -
性能调优:
- 启用lua-resty-core
- 根据实际负载调整定时器间隔
-
错误监控:
- 监控Nginx错误日志中的
ngx.ERR级别日志 - 定期检查规则更新是否成功
- 监控Nginx错误日志中的
-
安全建议:
- 保护WEB控制台的API key和密码
- 定期更新节点UUID
-
调试技巧:
- 使用
ngx.log输出调试信息 - 可结合Wireshark抓包分析与控制台的通信
- 使用