Skip to content

Commit 3825674

Browse files
feat: extend chaitin-waf plugin functionalities #9945 (#12029)
Co-authored-by: AlinsRan <[email protected]>
1 parent 01e2b51 commit 3825674

File tree

4 files changed

+474
-198
lines changed

4 files changed

+474
-198
lines changed

apisix/plugins/chaitin-waf.lua

+94-46
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ local vars_schema = {
3535
type = "array",
3636
}
3737

38+
local lrucache = core.lrucache.new({
39+
ttl = 300, count = 1024
40+
})
41+
3842
local match_schema = {
3943
type = "array",
4044
items = {
@@ -48,8 +52,11 @@ local match_schema = {
4852
local plugin_schema = {
4953
type = "object",
5054
properties = {
51-
-- TODO: we should add a configuration "mode" here
52-
-- It can be one of off, block and monitor
55+
mode = {
56+
type = "string",
57+
enum = { "off", "monitor", "block", nil },
58+
default = nil,
59+
},
5360
match = match_schema,
5461
append_waf_resp_header = {
5562
type = "boolean",
@@ -79,6 +86,9 @@ local plugin_schema = {
7986
},
8087
keepalive_timeout = {
8188
type = "integer",
89+
},
90+
real_client_ip = {
91+
type = "boolean"
8292
}
8393
},
8494
},
@@ -88,6 +98,11 @@ local plugin_schema = {
8898
local metadata_schema = {
8999
type = "object",
90100
properties = {
101+
mode = {
102+
type = "string",
103+
enum = { "off", "monitor", "block", nil },
104+
default = nil,
105+
},
91106
nodes = {
92107
type = "array",
93108
items = {
@@ -136,8 +151,10 @@ local metadata_schema = {
136151
type = "integer",
137152
default = 60000 -- milliseconds
138153
},
139-
-- TODO: we need a configuration to enable/disable the real client ip
140-
-- the real client ip is calculated by APISIX
154+
real_client_ip = {
155+
type = "boolean",
156+
default = true
157+
}
141158
},
142159
default = {},
143160
},
@@ -163,6 +180,7 @@ local HEADER_CHAITIN_WAF_ACTION = "X-APISIX-CHAITIN-WAF-ACTION"
163180
local HEADER_CHAITIN_WAF_SERVER = "X-APISIX-CHAITIN-WAF-SERVER"
164181
local blocked_message = [[{"code": %s, "success":false, ]] ..
165182
[["message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "%s"}]]
183+
local warning_message = "chaitin-waf monitor mode: request would have been rejected, event_id: "
166184

167185

168186
function _M.check_schema(conf, schema_type)
@@ -224,33 +242,37 @@ end
224242

225243

226244
local function check_match(conf, ctx)
227-
local match_passed = true
245+
if not conf.match or #conf.match == 0 then
246+
return true
247+
end
228248

229-
if conf.match then
230-
for _, match in ipairs(conf.match) do
231-
-- todo: use lrucache to cache the result
232-
local exp, err = expr.new(match.vars)
233-
if err then
234-
local msg = "failed to create match expression for " ..
249+
for _, match in ipairs(conf.match) do
250+
local cache_key = tostring(match.vars)
251+
252+
local exp, err = lrucache(cache_key, nil, function(vars)
253+
return expr.new(vars)
254+
end, match.vars)
255+
256+
if not exp then
257+
local msg = "failed to create match expression for " ..
235258
tostring(match.vars) .. ", err: " .. tostring(err)
236-
core.log.error(msg)
237-
return false, msg
238-
end
259+
return false, msg
260+
end
239261

240-
match_passed = exp:eval(ctx.var)
241-
if match_passed then
242-
break
243-
end
262+
local matched = exp:eval(ctx.var)
263+
if matched then
264+
return true
244265
end
245266
end
246267

247-
return match_passed, nil
268+
return false
248269
end
249270

250271

251272
local function get_conf(conf, metadata)
252273
local t = {
253274
mode = "block",
275+
real_client_ip = true,
254276
}
255277

256278
if metadata.config then
@@ -260,6 +282,7 @@ local function get_conf(conf, metadata)
260282
t.req_body_size = metadata.config.req_body_size
261283
t.keepalive_size = metadata.config.keepalive_size
262284
t.keepalive_timeout = metadata.config.keepalive_timeout
285+
t.real_client_ip = metadata.config.real_client_ip or t.real_client_ip
263286
end
264287

265288
if conf.config then
@@ -269,27 +292,18 @@ local function get_conf(conf, metadata)
269292
t.req_body_size = conf.config.req_body_size
270293
t.keepalive_size = conf.config.keepalive_size
271294
t.keepalive_timeout = conf.config.keepalive_timeout
295+
t.real_client_ip = conf.config.real_client_ip or t.real_client_ip
272296
end
273297

298+
t.mode = conf.mode or metadata.mode or t.mode
299+
274300
return t
275301
end
276302

277303

278304
local function do_access(conf, ctx)
279305
local extra_headers = {}
280306

281-
local match, err = check_match(conf, ctx)
282-
if not match then
283-
if err then
284-
extra_headers[HEADER_CHAITIN_WAF] = "err"
285-
extra_headers[HEADER_CHAITIN_WAF_ERROR] = tostring(err)
286-
return 500, nil, extra_headers
287-
else
288-
extra_headers[HEADER_CHAITIN_WAF] = "no"
289-
return nil, nil, extra_headers
290-
end
291-
end
292-
293307
local metadata = plugin.plugin_metadata(plugin_name)
294308
if not core.table.try_read_attr(metadata, "value", "nodes") then
295309
extra_headers[HEADER_CHAITIN_WAF] = "err"
@@ -306,43 +320,77 @@ local function do_access(conf, ctx)
306320
end
307321

308322
core.log.info("picked chaitin-waf server: ", host, ":", port)
309-
310323
local t = get_conf(conf, metadata.value)
311324
t.host = host
312325
t.port = port
313326

314327
extra_headers[HEADER_CHAITIN_WAF_SERVER] = host
315-
extra_headers[HEADER_CHAITIN_WAF] = "yes"
328+
329+
local mode = t.mode or "block"
330+
if mode == "off" then
331+
extra_headers[HEADER_CHAITIN_WAF] = "off"
332+
return nil, nil, extra_headers
333+
end
334+
335+
local match, err = check_match(conf, ctx)
336+
if not match then
337+
if err then
338+
extra_headers[HEADER_CHAITIN_WAF] = "err"
339+
extra_headers[HEADER_CHAITIN_WAF_ERROR] = tostring(err)
340+
return 500, nil, extra_headers
341+
else
342+
extra_headers[HEADER_CHAITIN_WAF] = "no"
343+
return nil, nil, extra_headers
344+
end
345+
end
346+
347+
if t.real_client_ip then
348+
t.client_ip = ctx.var.http_x_forwarded_for or ctx.var.remote_addr
349+
else
350+
t.client_ip = ctx.var.remote_addr
351+
end
316352

317353
local start_time = ngx_now() * 1000
318354
local ok, err, result = t1k.do_access(t, false)
355+
356+
extra_headers[HEADER_CHAITIN_WAF_TIME] = ngx_now() * 1000 - start_time
357+
319358
if not ok then
320359
extra_headers[HEADER_CHAITIN_WAF] = "waf-err"
321360
local err_msg = tostring(err)
322361
if core.string.find(err_msg, "timeout") then
323362
extra_headers[HEADER_CHAITIN_WAF] = "timeout"
324363
end
325364
extra_headers[HEADER_CHAITIN_WAF_ERROR] = tostring(err)
365+
366+
if mode == "monitor" then
367+
core.log.warn("chaitin-waf monitor mode: detected waf error - ", err_msg)
368+
return nil, nil, extra_headers
369+
end
370+
371+
return 500, nil, extra_headers
326372
else
373+
extra_headers[HEADER_CHAITIN_WAF] = "yes"
327374
extra_headers[HEADER_CHAITIN_WAF_ACTION] = "pass"
328375
end
329-
extra_headers[HEADER_CHAITIN_WAF_TIME] = ngx_now() * 1000 - start_time
330376

331377
local code = 200
332378
extra_headers[HEADER_CHAITIN_WAF_STATUS] = code
333-
if result and result.status and result.status ~= 200 then
334-
if result.event_id then
335-
code = result.status
336-
extra_headers[HEADER_CHAITIN_WAF_STATUS] = code
337-
extra_headers[HEADER_CHAITIN_WAF_ACTION] = "reject"
338-
339-
core.log.error("request rejected by chaitin-waf, event_id: " .. result.event_id)
340-
return tonumber(code), fmt(blocked_message, code,
341-
result.event_id) .. "\n", extra_headers
379+
380+
if result and result.status and result.status ~= 200 and result.event_id then
381+
extra_headers[HEADER_CHAITIN_WAF_STATUS] = result.status
382+
extra_headers[HEADER_CHAITIN_WAF_ACTION] = "reject"
383+
384+
if mode == "monitor" then
385+
core.log.warn(warning_message, result.event_id)
386+
return nil, nil, extra_headers
342387
end
343-
end
344-
if not ok then
345-
extra_headers[HEADER_CHAITIN_WAF_STATUS] = nil
388+
389+
core.log.error("request rejected by chaitin-waf, event_id: " .. result.event_id)
390+
391+
return tonumber(result.status),
392+
fmt(blocked_message, result.status, result.event_id) .. "\n",
393+
extra_headers
346394
end
347395

348396
return nil, nil, extra_headers

0 commit comments

Comments
 (0)