151 lines
4.4 KiB
Lua
151 lines
4.4 KiB
Lua
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
|