bunkerweb 1.4.0

This commit is contained in:
bunkerity
2022-06-03 17:24:14 +02:00
parent 3a078326c5
commit a9f886804a
5245 changed files with 1432051 additions and 27894 deletions

View 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 %}

View 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
View 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
View 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"
}
}
}