bunkerweb 1.4.0
This commit is contained in:
20
core/limit/confs/http/limitconn.conf
Normal file
20
core/limit/confs/http/limitconn.conf
Normal file
@@ -0,0 +1,20 @@
|
||||
{% if has_variable(all, "USE_LIMIT_CONN", "yes") +%}
|
||||
|
||||
map $http2 $v1ip {
|
||||
default "";
|
||||
"" $binary_remote_addr;
|
||||
}
|
||||
|
||||
map $http2 $v2ip {
|
||||
default $binary_remote_addr;
|
||||
"" "";
|
||||
}
|
||||
|
||||
limit_conn_zone $v1ip zone=v1ips:10m;
|
||||
limit_conn_zone $v2ip zone=v2ips:10m;
|
||||
|
||||
limit_conn_log_level warn;
|
||||
|
||||
limit_conn_status 429;
|
||||
|
||||
{% endif %}
|
||||
6
core/limit/confs/server-http/limitconn.conf
Normal file
6
core/limit/confs/server-http/limitconn.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
{% if USE_LIMIT_CONN == "yes" +%}
|
||||
|
||||
limit_conn v1ips {{ LIMIT_CONN_MAX_HTTP1 }};
|
||||
limit_conn v2ips {{ LIMIT_CONN_MAX_HTTP2 }};
|
||||
|
||||
{% endif %}
|
||||
150
core/limit/limit.lua
Normal file
150
core/limit/limit.lua
Normal file
@@ -0,0 +1,150 @@
|
||||
local _M = {}
|
||||
_M.__index = _M
|
||||
|
||||
local utils = require "utils"
|
||||
local datastore = require "datastore"
|
||||
local logger = require "logger"
|
||||
local cjson = require "cjson"
|
||||
|
||||
function _M.new()
|
||||
local self = setmetatable({}, _M)
|
||||
return self, nil
|
||||
end
|
||||
|
||||
function _M:init()
|
||||
-- Check if init is needed
|
||||
local init_needed, err = utils.has_variable("USE_LIMIT_REQ", "yes")
|
||||
if init_needed == nil then
|
||||
return false, err
|
||||
end
|
||||
if not init_needed then
|
||||
return true, "no service uses Limit for requests, skipping init"
|
||||
end
|
||||
-- Get variables
|
||||
local variables, err = utils.get_multiple_variables({"LIMIT_REQ_URL", "LIMIT_REQ_RATE"})
|
||||
if variables == nil then
|
||||
return false, err
|
||||
end
|
||||
-- Store URLs and rates
|
||||
local data = {}
|
||||
local i = 0
|
||||
for srv, vars in pairs(variables) do
|
||||
for var, value in pairs(vars) do
|
||||
if var:match("LIMIT_REQ_URL") then
|
||||
local url = value
|
||||
local rate = vars[var:gsub("URL", "RATE")]
|
||||
if data[srv] == nil then
|
||||
data[srv] = {}
|
||||
end
|
||||
data[srv][url] = rate
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
local ok, err = datastore:set("plugin_limit_rules", cjson.encode(data))
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
return true, "successfully loaded " .. tostring(i) .. " limit rules for requests"
|
||||
end
|
||||
|
||||
function _M:access()
|
||||
-- Check if access is needed
|
||||
local access_needed, err = utils.get_variable("USE_LIMIT_REQ")
|
||||
if access_needed == nil then
|
||||
return false, err, nil, nil
|
||||
end
|
||||
if access_needed ~= "yes" then
|
||||
return true, "Limit for request not activated", nil, nil
|
||||
end
|
||||
|
||||
-- Don't go further if URL is not limited
|
||||
local limited = false
|
||||
local all_rules, err = datastore:get("plugin_limit_rules")
|
||||
if not all_rules then
|
||||
return false, err, nil, nil
|
||||
end
|
||||
all_rules = cjson.decode(all_rules)
|
||||
local limited = false
|
||||
local rate = ""
|
||||
if not limited and all_rules[ngx.var.server_name] then
|
||||
for k, v in pairs(all_rules[ngx.var.server_name]) do
|
||||
if ngx.var.uri:match(k) and k ~= "/" then
|
||||
limited = true
|
||||
rate = all_rules[ngx.var.server_name][k]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if all_rules.global and not limited then
|
||||
for k, v in pairs(all_rules.global) do
|
||||
if ngx.var.uri:match(k) and k ~= "/" then
|
||||
limited = true
|
||||
rate = all_rules.global[k]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not limited then
|
||||
if all_rules[ngx.var.server_name] and all_rules[ngx.var.server_name]["/"] then
|
||||
limited = true
|
||||
rate = all_rules[ngx.var.server_name]["/"]
|
||||
elseif all_rules.global and all_rules.global["/"] then
|
||||
limited = true
|
||||
rate = all_rules.global["/"]
|
||||
end
|
||||
if not limited then
|
||||
return true, "URL " .. ngx.var.uri .. " is not limited by a rule, skipping check", nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Get the rate
|
||||
local _, _, rate_max, rate_time = rate:find("(%d+)r/(.)")
|
||||
|
||||
-- Get current requests timestamps
|
||||
local requests, err = datastore:get("plugin_limit_cache_" .. ngx.var.remote_addr .. ngx.var.uri)
|
||||
if not requests and err ~= "not found" then
|
||||
return false, err, nil, nil
|
||||
elseif err == "not found" then
|
||||
requests = "{}"
|
||||
end
|
||||
|
||||
-- Compute new timestamps
|
||||
local new_timestamps = {}
|
||||
local current_timestamp = os.time(os.date("!*t"))
|
||||
local delay = 0
|
||||
if rate_time == "s" then
|
||||
delay = 1
|
||||
elseif rate_time == "m" then
|
||||
delay = 60
|
||||
elseif rate_time == "h" then
|
||||
delay = 3600
|
||||
elseif rate_time == "d" then
|
||||
delay = 86400
|
||||
end
|
||||
for i, timestamp in ipairs(cjson.decode(requests)) do
|
||||
if current_timestamp - timestamp <= delay then
|
||||
table.insert(new_timestamps, timestamp)
|
||||
end
|
||||
end
|
||||
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
|
||||
if #new_timestamps <= tonumber(rate_max) then
|
||||
table.insert(new_timestamps, current_timestamp)
|
||||
end
|
||||
|
||||
-- Save the new timestamps
|
||||
local ok, err = datastore:set("plugin_limit_cache_" .. ngx.var.remote_addr .. ngx.var.uri, cjson.encode(new_timestamps), delay)
|
||||
if not ok then
|
||||
return false, "can't update timestamps : " .. err, nil, nil
|
||||
end
|
||||
|
||||
-- Deny if the rate is higher than the one defined in rule
|
||||
if #new_timestamps > tonumber(rate_max) then
|
||||
return true, "client IP " .. ngx.var.remote_addr .. " is limited for URL " .. ngx.var.uri .. " (current rate = " .. tostring(#new_timestamps) .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", true, ngx.HTTP_TOO_MANY_REQUESTS
|
||||
end
|
||||
|
||||
-- Limit not reached
|
||||
return true, "client IP " .. ngx.var.remote_addr .. " is not limited for URL " .. ngx.var.uri .. " (current rate = " .. tostring(#new_timestamps) .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", nil, nil
|
||||
end
|
||||
|
||||
return _M
|
||||
65
core/limit/plugin.json
Normal file
65
core/limit/plugin.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"id": "limit",
|
||||
"order": 3,
|
||||
"name": "Limit",
|
||||
"description": "Limit maximum number of requests and connections.",
|
||||
"version": "0.1",
|
||||
"settings": {
|
||||
"USE_LIMIT_REQ": {
|
||||
"context": "multisite",
|
||||
"default": "yes",
|
||||
"help": "Activate limit requests feature.",
|
||||
"id": "use-limit-req",
|
||||
"label": "Activate limit requests",
|
||||
"regex": "^(yes|no)$",
|
||||
"type": "check"
|
||||
},
|
||||
"LIMIT_REQ_URL": {
|
||||
"context": "multisite",
|
||||
"default": "/",
|
||||
"help": "URL where the limit request will be applied.",
|
||||
"id": "limit-req-url",
|
||||
"label": "URL",
|
||||
"regex": "^.*$",
|
||||
"type": "text",
|
||||
"multiple": "limit-req"
|
||||
},
|
||||
"LIMIT_REQ_RATE": {
|
||||
"context": "multisite",
|
||||
"default": "2r/s",
|
||||
"help": "Rate to apply to the URL (s for second, m for minute, h for hour and d for day).",
|
||||
"id": "limit-req-rate",
|
||||
"label": "Rate",
|
||||
"regex": "^.*$",
|
||||
"type": "text",
|
||||
"multiple": "limit-req"
|
||||
},
|
||||
"USE_LIMIT_CONN": {
|
||||
"context": "multisite",
|
||||
"default": "yes",
|
||||
"help": "Activate limit connections feature.",
|
||||
"id": "use-limit-conn",
|
||||
"label": "Activate limit connections",
|
||||
"regex": "^(yes|no)$",
|
||||
"type": "check"
|
||||
},
|
||||
"LIMIT_CONN_MAX_HTTP1": {
|
||||
"context": "multisite",
|
||||
"default": "10",
|
||||
"help": "Maximum number of connections per IP when using HTTP/1.X protocol.",
|
||||
"id": "limit-conn-max-http1",
|
||||
"label": "Maximum number of HTTP/1.X connections",
|
||||
"regex": "^.*$",
|
||||
"type": "text"
|
||||
},
|
||||
"LIMIT_CONN_MAX_HTTP2": {
|
||||
"context": "multisite",
|
||||
"default": "100",
|
||||
"help": "Maximum number of streams per IP when using HTTP/2 protocol.",
|
||||
"id": "limit-conn-max-http2",
|
||||
"label": "Maximum number of HTTP/2 streams",
|
||||
"regex": "^.*$",
|
||||
"type": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user