From 4db2cda499610b9b8ab793cfb154d11b18e4d57a Mon Sep 17 00:00:00 2001 From: he2ss Date: Thu, 13 Jan 2022 09:48:05 +0100 Subject: [PATCH] range support method 1 --- nginx/access.lua | 5 ++- nginx/crowdsec.lua | 107 ++++++++++++++++++++++++++++++++++++++------- nginx/iputils.lua | 61 ++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 nginx/iputils.lua diff --git a/nginx/access.lua b/nginx/access.lua index 4f153f0..33edb08 100644 --- a/nginx/access.lua +++ b/nginx/access.lua @@ -1,8 +1,9 @@ -ok, err = require "crowdsec".allowIp(ngx.var.remote_addr) +local cs = require "crowdsec" +ok, remediation, err = cs.allowIp(ngx.var.remote_addr) if err ~= nil then ngx.log(ngx.ERR, "[Crowdsec] bouncer error: " .. err) end if not ok then - ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "'") + ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ngx.var.remote_addr .. "' with '"..remediation.."'") ngx.exit(ngx.HTTP_FORBIDDEN) end diff --git a/nginx/crowdsec.lua b/nginx/crowdsec.lua index 5445a86..a4a63e1 100644 --- a/nginx/crowdsec.lua +++ b/nginx/crowdsec.lua @@ -1,13 +1,20 @@ package.path = package.path .. ";./?.lua" local config = require "plugins.crowdsec.config" +local iputils = require "plugins.crowdsec.iputils" local http = require "resty.http" local cjson = require "cjson" +local cidr = require "libcidr-ffi" +local ipmatcher = require "resty.ipmatcher" +local bit = require 'bitop.funcs' cjson.decode_array_with_array_mt(true) -- contain runtime = {} local runtime = {} +runtime.remediations = {} +runtime.remediations["1"] = "ban" +runtime.remediations["2"] = "captcha" function ipToInt( str ) @@ -56,7 +63,7 @@ function http_request(link) end function parse_duration(duration) - local match, err = ngx.re.match(duration, "((?[0-9]+)h)?((?[0-9]+)m)?(?[0-9]+)") + local match, err = ngx.re.match(duration, "^((?[0-9]+)h)?((?[0-9]+)m)?(?[0-9]+)") local ttl = 0 if not match then if err then @@ -78,11 +85,52 @@ function parse_duration(duration) return ttl, nil end +function get_remediation_id(remediation) + for key, value in pairs(runtime.remediations) do + if value == remediation then + return tonumber(key) + end + end + return nil +end + +function item_to_string(item) + local ip, err = cidr.from_str(item) + if ip == nil then + return "normal_"..item + end + + local ip_version, ip_netmask, ip_network_address + local res = ipmatcher.parse_ipv4(cidr.to_str(ip, cidr.flags.ONLYADDR)) + if res ~= false then + ip_version = "ipv4" + ip_network_address = res + end + local res = ipmatcher.parse_ipv6(cidr.to_str(ip, cidr.flags.ONLYADDR)) + if res ~= false then + ip_version = "ipv6" + ip_network_address = res + end + + local netmask_str = cidr.to_str(ip, cidr.flags.NETMASK) + for i in netmask_str.gmatch(netmask_str, "([^\\/]+)") do + netmask_str = i + end + if ip_version == "ipv4" then + ip_netmask = ipmatcher.parse_ipv4(netmask_str) + else + ip_netmask = ipmatcher.parse_ipv6(netmask_str) + end + + return ip_version.."_"..ip_netmask.."_"..ip_network_address +end + function stream_query() -- As this function is running inside coroutine (with ngx.timer.every), -- we need to raise error instead of returning them - ngx.log(ngx.DEBUG, "Stream Query from worker : " .. tostring(ngx.worker.id()) .. " with startup "..tostring(runtime.cache:get("startup"))) - local link = runtime.conf["API_URL"] .. "/v1/decisions/stream?startup=" .. tostring(runtime.cache:get("startup")) + local is_startup = runtime.cache:get("startup") + ngx.log(ngx.DEBUG, "Stream Query from worker : " .. tostring(ngx.worker.id()) .. " with startup "..tostring(is_startup)) + local link = runtime.conf["API_URL"] .. "/v1/decisions/stream?startup=" .. tostring(is_startup) local res, err = http_request(link) if not res then error("request failed: ".. err) @@ -97,9 +145,12 @@ function stream_query() local decisions = cjson.decode(body) -- process deleted decisions if type(decisions.deleted) == "table" then - for i, decision in pairs(decisions.deleted) do - runtime.cache:delete(decision.value) - ngx.log(ngx.DEBUG, "Deleting '" .. decision.value .. "'") + if not is_startup then + for i, decision in pairs(decisions.deleted) do + local key = item_to_string(decision.value) + runtime.cache:delete(key) + ngx.log(ngx.DEBUG, "Deleting '" .. key .. "'") + end end end @@ -111,14 +162,19 @@ function stream_query() if err ~= nil then ngx.log(ngx.ERR, "[Crowdsec] failed to parse ban duration '" .. decision.duration .. "' : " .. err) end - local succ, err, forcible = runtime.cache:set(decision.value, false, ttl) + local remediation_id = get_remediation_id(decision.type) + if remediation_id == nil then + remediation_id = 1 + end + local key = item_to_string(decision.value) + local succ, err, forcible = runtime.cache:set(key, false, ttl, remediation_id) if not succ then ngx.log(ngx.ERR, "failed to add ".. decision.value .." : "..err) end if forcible then ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config") end - ngx.log(ngx.DEBUG, "Adding '" .. decision.value .. "' in cache for '" .. ttl .. "' seconds") + ngx.log(ngx.DEBUG, "Adding '" .. key .. "' in cache for '" .. ttl .. "' seconds") end end end @@ -167,7 +223,7 @@ end function csmod.allowIp(ip) if runtime.conf == nil then - return true, "Configuration is bad, cannot run properly" + return true, runtime.conf["BOUNCING_ON_TYPE"], "Configuration is bad, cannot run properly" end -- if it stream mode and startup start timer @@ -180,24 +236,43 @@ function csmod.allowIp(ip) end if not ok then runtime.cache:set("first_run", true) - return true, "Failed to create the timer: " .. (err or "unknown") + return true, runtime.conf["BOUNCING_ON_TYPE"], "Failed to create the timer: " .. (err or "unknown") end runtime.cache:set("first_run", false) ngx.log(ngx.DEBUG, "Timer launched") end - local data = runtime.cache:get(ip) - if data ~= nil then -- we have it in cache - ngx.log(ngx.DEBUG, "'" .. ip .. "' is in cache") - return data, nil + local key = item_to_string(ip) + local key_parts = {} + for i in key.gmatch(key, "([^_]+)") do + table.insert(key_parts, i) + end + local key_type = key_parts[1] + if key_type == "normal" then + local in_cache, remediation_id = runtime.cache:get(key) + if in_cache ~= nil then -- we have it in cache + ngx.log(ngx.DEBUG, "'" .. key .. "' is in cache") + return in_cache, runtime.remediations[tostring(remediation_id)], nil + end + end + + local ip_network_address = tonumber(key_parts[3]) + local netmasks = iputils.netmasks_by_key_type[key_type] + for _, netmask in pairs(netmasks) do + local item = key_type.."_"..netmask.."_"..tostring(bit.band(ip_network_address, netmask)) + in_cache, remediation_id = runtime.cache:get(item) + if in_cache ~= nil then -- we have it in cache + ngx.log(ngx.DEBUG, "'" .. key .. "' is in cache") + return in_cache, runtime.remediations[tostring(remediation_id)], nil + end end -- if live mode, query lapi if runtime.conf["MODE"] == "live" then local ok, err = live_query(ip) - return ok, err + return ok, runtime.conf["BOUNCING_ON_TYPE"], err end - return true, nil + return true, runtime.conf["BOUNCING_ON_TYPE"], nil end diff --git a/nginx/iputils.lua b/nginx/iputils.lua new file mode 100644 index 0000000..34f0f6a --- /dev/null +++ b/nginx/iputils.lua @@ -0,0 +1,61 @@ +local _M = {} + +local ipv4_netmasks = { + 4294967295, 4294967294, 4294967292, 4294967288, 4294967280, 4294967264, 4294967232, 4294967168, 4294967040, 4294966784, + 4294966272, 4294965248, 4294963200, 4294959104, 4294950912, 4294934528, 4294901760, 4294836224, 4294705152, 4294443008, + 4293918720, 4292870144, 4290772992, 4286578688, 4278190080, 4261412864, 4227858432, 4160749568, 4026531840, 3758096384, 3221225472, 2147483648, 0 +} + +local ipv6_netmasks = { + 340282366920938463463374607431768211455, 340282366920938463463374607431768211454, 340282366920938463463374607431768211452, + 340282366920938463463374607431768211448, 340282366920938463463374607431768211440, 340282366920938463463374607431768211424, + 340282366920938463463374607431768211392, 340282366920938463463374607431768211328, 340282366920938463463374607431768211200, + 340282366920938463463374607431768210944, 340282366920938463463374607431768210432, 340282366920938463463374607431768209408, + 340282366920938463463374607431768207360, 340282366920938463463374607431768203264, 340282366920938463463374607431768195072, + 340282366920938463463374607431768178688, 340282366920938463463374607431768145920, 340282366920938463463374607431768080384, + 340282366920938463463374607431767949312, 340282366920938463463374607431767687168, 340282366920938463463374607431767162880, + 340282366920938463463374607431766114304, 340282366920938463463374607431764017152, 340282366920938463463374607431759822848, + 340282366920938463463374607431751434240, 340282366920938463463374607431734657024, 340282366920938463463374607431701102592, + 340282366920938463463374607431633993728, 340282366920938463463374607431499776000, 340282366920938463463374607431231340544, + 340282366920938463463374607430694469632, 340282366920938463463374607429620727808, 340282366920938463463374607427473244160, + 340282366920938463463374607423178276864, 340282366920938463463374607414588342272, 340282366920938463463374607397408473088, + 340282366920938463463374607363048734720, 340282366920938463463374607294329257984, 340282366920938463463374607156890304512, + 340282366920938463463374606882012397568, 340282366920938463463374606332256583680, 340282366920938463463374605232744955904, + 340282366920938463463374603033721700352, 340282366920938463463374598635675189248, 340282366920938463463374589839582167040, + 340282366920938463463374572247396122624, 340282366920938463463374537063024033792, 340282366920938463463374466694279856128, + 340282366920938463463374325956791500800, 340282366920938463463374044481814790144, 340282366920938463463373481531861368832, + 340282366920938463463372355631954526208, 340282366920938463463370103832140840960, 340282366920938463463365600232513470464, + 340282366920938463463356593033258729472, 340282366920938463463338578634749247488, 340282366920938463463302549837730283520, + 340282366920938463463230492243692355584, 340282366920938463463086377055616499712, 340282366920938463462798146679464787968, + 340282366920938463462221685927161364480, 340282366920938463461068764422554517504, 340282366920938463458762921413340823552, + 340282366920938463454151235394913435648, 340282366920938463444927863358058659840, 340282366920938463426481119284349108224, + 340282366920938463389587631136930004992, 340282366920938463315800654842091798528, 340282366920938463168226702252415385600, + 340282366920938462873078797073062559744, 340282366920938462282782986714356908032, 340282366920938461102191365996945604608, + 340282366920938458741008124562122997760, 340282366920938454018641641692477784064, 340282366920938444573908675953187356672, + 340282366920938425684442744474606501888, 340282366920938387905510881517444792320, 340282366920938312347647155603121373184, + 340282366920938161231919703774474534912, 340282366920937859000464800117180858368, 340282366920937254537554992802593505280, + 340282366920936045611735378173418799104, 340282366920933627760096148915069386752, 340282366920928792056817690398370562048, + 340282366920919120650260773364972912640, 340282366920899777837146939298177613824, 340282366920861092210919271164587016192, + 340282366920783720958463934897405820928, 340282366920628978453553262363043430400, 340282366920319493443731917294318649344, + 340282366919700523424089227156869087232, 340282366918462583384803846881969963008, 340282366915986703306233086332171714560, + 340282366911034943149091565232575217664, 340282366901131422834808523033382223872, 340282366881324382206242438634996236288, + 340282366841710300949110269838224261120, 340282366762482138434845932244680310784, 340282366604025813406317257057592410112, + 340282366287113163349259906683416608768, 340282365653287863235145205935065006080, 340282364385637263006915804438361800704, + 340282361850336062550457001444955389952, 340282356779733661637539395458142568448, 340282346638528859811704183484516925440, + 340282326356119256160033759537265639424, 340282285791300048856692911642763067392, 340282204661661634250011215853757923328, + 340282042402384805036647824275747635200, 340281717883831146609921041119727058944, 340281068846723829756467474807685906432, + 340279770772509196049560342183603601408, 340277174624079928635746076935438991360, 340271982327221393808117546439109771264, + 340261597733504324152860485446451331072, 340240828546070184842346363461134450688, 340199290171201906221318119490500689920, + 340116213421465348979261631549233168384, 339950059921992234495148655666698125312, 339617752923046005526922703901628039168, + 338953138925153547590470800371487866880, 337623910929368631717566993311207522304, 334965454937798799971759379190646833152, + 329648542954659136480144150949525454848, 319014718988379809496913694467282698240, 297747071055821155530452781502797185024, + 255211775190703847597530955573826158592, 170141183460469231731687303715884105728, 0 +} + +local netmasks_by_key_type = {} +netmasks_by_key_type["ipv4"] = ipv4_netmasks +netmasks_by_key_type["ipv6"] = ipv6_netmasks + +_M.netmasks_by_key_type = netmasks_by_key_type + +return _M \ No newline at end of file