various redis fixes and improvements

This commit is contained in:
florian 2023-04-04 00:50:22 +02:00
parent e80965ca9a
commit d3d0136bf5
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
6 changed files with 279 additions and 158 deletions

View File

@ -18,7 +18,7 @@ function M:connect()
["REDIS_KEEPALIVE_POOL"] = ""
}
for k, v in pairs(variables) do
local value, err = utils.get_variable(k)
local value, err = utils.get_variable(k, false)
if value == nil then
return false, err
end
@ -47,7 +47,7 @@ function M:close(redis_client)
["REDIS_KEEPALIVE_POOL"] = ""
}
for k, v in pairs(variables) do
local value, err = utils.get_variable(k)
local value, err = utils.get_variable(k, false)
if value == nil then
return false, err
end

73
src/bw/lua/redisutils.lua Normal file
View File

@ -0,0 +1,73 @@
local clusterstore = require "clusterstore"
local datastore = require "datastore"
local utils = require "utils"
local redisutils = {}
redisutils.ban = function(ip)
-- Connect
local redis_client, err = clusterstore:connect()
if not redis_client then
return nil, "can't connect to redis server : " .. err
end
-- Start transaction
local ok, err = redis_client:multi()
if not ok then
clusterstore:close(redis_client)
return nil, "MULTI failed : " .. err
end
-- Get ban
ok, err = redis_client:get("ban_" .. ip)
if not ok then
clusterstore:close(redis_client)
return nil, "GET failed : " .. err
end
-- Get ttl
ok, err = redis_client:ttl("ban_" .. ip)
if not ok then
clusterstore:close(redis_client)
return nil, "TTL failed : " .. err
end
-- Exec transaction
local exec, err = redis_client:exec()
if err then
clusterstore:close(redis_client)
return nil, "EXEC failed : " .. err
end
if type(exec) ~= "table" then
clusterstore:close(redis_client)
return nil, "EXEC result is not a table"
end
-- Extract ban reason
local reason = exec[1]
if type(reason) == "table" then
clusterstore:close(redis_client)
return nil, "GET failed : " .. reason[2]
end
if reason == ngx.null then
clusterstore:close(redis_client)
datastore:delete("bans_ip_" .. ip)
return false
end
-- Extract ttl
local ttl = exec[2]
if type(ttl) == "table" then
clusterstore:close(redis_client)
return nil, "TTL failed : " .. ttl[2]
end
if ttl <= 0 then
clusterstore:close(redis_client)
return nil, "TTL returned invalid value : " .. tostring(ttl)
end
ok, err = datastore:set("bans_ip_" .. ip, reason, ttl)
if not ok then
clusterstore:close(redis_client)
datastore:delete("bans_ip_" .. ip)
return nil, "can't save ban to local datastore : " .. err
end
-- Return reason
clusterstore:close(redis_client)
return true, reason
end
return redisutils

View File

@ -1,9 +1,9 @@
local datastore = require "datastore"
local ipmatcher = require "resty.ipmatcher"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
local mmdb = require "mmdb"
local logger = require "logger"
local datastore = require "datastore"
local ipmatcher = require "resty.ipmatcher"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
local mmdb = require "mmdb"
local logger = require "logger"
local utils = {}

View File

@ -4,7 +4,7 @@ local logger = require "logger"
local datastore = require "datastore"
local plugins = require "plugins"
local utils = require "utils"
local clusterstore = require "clusterstore"
local redisutils = require "redisutils"
-- Don't process internal requests
if ngx.req.is_internal() then
@ -15,39 +15,33 @@ end
logger.log(ngx.INFO, "ACCESS", "Access phase started")
-- Process bans as soon as possible
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if banned then
logger.log(ngx.WARN, "ACCESS", "IP " .. ngx.var.remote_addr .. " is banned with reason : " .. banned)
ngx.exit(utils.get_deny_status())
end
local banned = nil
-- Redis case
local use_redis = utils.get_variable("USE_REDIS")
if use_redis == "yes" then
-- Connect
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "ACCESS", "can't connect to redis server : " .. err)
local redis_banned, reason = redisutils.ban(ngx.var.remote_addr)
if redis_banned == nil then
logger.log(ngx.ERR, "ACCESS", "Error while checking ban from redis, falling back to local : " .. reason)
elseif not redis_banned then
banned = false
else
-- Get ban
local ban, err = redis_client:get("ban_" .. ngx.var.remote_addr)
if err then
logger.log(ngx.ERR, "ACCESS", "GET failed : " .. err)
elseif ban then
-- Get TTL
local ttl, err = redis_client:ttl("ban_" .. ngx.var.remote_addr)
if not ttl then
logger.log(ngx.ERR, "ACCESS", "TTL failed : " .. err)
else
local ok, err = datastore:set("bans_ip_" .. ngx.var.remote_addr, ban, ttl)
if not ok then
logger.log(ngx.ERR, "ACCESS", "can't save ban to the datastore : " .. err)
return
end
end
end
redis_client:close()
banned = reason
end
end
-- Local case
if banned == nil then
local reason, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if reason then
banned = reason
else
banned = false
end
end
-- Deny request
if banned then
logger.log(ngx.WARN, "ACCESS", "IP " .. ngx.var.remote_addr .. " is banned with reason : " .. banned)
ngx.exit(utils.get_deny_status())
end
-- List all plugins
local list, err = plugins:list()

View File

@ -1,46 +1,41 @@
preread_by_lua_block {
local logger = require "logger"
local datastore = require "datastore"
local plugins = require "plugins"
local utils = require "utils"
local logger = require "logger"
local datastore = require "datastore"
local plugins = require "plugins"
local utils = require "utils"
local redisutils = require "redisutils"
logger.log(ngx.INFO, "PREREAD", "Preread phase started")
-- Process bans as soon as possible
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if banned then
logger.log(ngx.WARN, "PREREAD", "IP " .. ngx.var.remote_addr .. " is banned with reason : " .. banned)
ngx.exit(utils.get_deny_status())
end
local banned = nil
-- Redis case
local use_redis = utils.get_variable("USE_REDIS")
if use_redis == "yes" then
-- Connect
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "PREREAD", "can't connect to redis server : " .. err)
local redis_banned, reason = redisutils.ban(ngx.var.remote_addr)
if redis_banned == nil then
logger.log(ngx.ERR, "ACCESS", "Error while checking ban from redis, falling back to local : " .. reason)
elseif not redis_banned then
banned = false
else
-- Get ban
local ban, err = redis_client:get("ban_" .. ngx.var.remote_addr)
if err then
logger.log(ngx.ERR, "PREREAD", "GET failed : " .. err)
elseif ban then
-- Get TTL
local ttl, err = redis_client:ttl("ban_" .. ngx.var.remote_addr)
if not ttl then
logger.log(ngx.ERR, "PREREAD", "TTL failed : " .. err)
else
local ok, err = datastore:set("bans_ip_" .. ip, ban, ttl)
if not ok then
logger.log(ngx.ERR, "PREREAD", "can't save ban to the datastore : " .. err)
return
end
end
end
redis_client:close()
banned = reason
end
end
-- Local case
if banned == nil then
local reason, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if reason then
banned = reason
else
banned = false
end
end
-- Deny request
if banned then
logger.log(ngx.WARN, "ACCESS", "IP " .. ngx.var.remote_addr .. " is banned with reason : " .. banned)
ngx.exit(utils.get_deny_status())
end
-- List all plugins
local list, err = plugins:list()

View File

@ -13,69 +13,32 @@ function _M.new()
end
function _M.increase(premature, use_redis, ip, count_time, ban_time, threshold)
-- Local case
local counter, err = datastore:get("plugin_badbehavior_count_" .. ip)
if not counter and err ~= "not found" then
return false, "can't get counts from the datastore : " .. err
end
if counter == nil then
counter = 0
end
counter = counter + 1
-- Declare counter
local counter = false
-- Redis case
if use_redis then
-- Connect to server
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) Can't connect to redis server : " .. err)
return
local redis_counter = _M.redis_increase(ip, count_time, ban_time, threshold)
if not redis_counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) redis_increase failed, falling back to local")
else
counter = redis_counter
end
-- Start transaction
local ok, err = redis_client:multi()
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) Can't start transaction : " .. err)
redis_client:close()
return
end
-- Local case
if not counter then
local local_counter, err = datastore:get("plugin_badbehavior_count_" .. ip)
if not local_counter and err ~= "not found" then
return false, "can't get counts from the datastore : " .. err
end
-- Increment counter
counter, err = redis_client:incr("bad_behavior_" .. ip)
if not counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) INCR failed : " .. err)
redis_client:close()
return
if local_counter == nil then
counter = 0
end
-- Expires counter
local expire, err = redis_client:expire("bad_behavior_" .. ip, count_time)
if not counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXPIRE failed : " .. err)
redis_client:close()
return
end
-- Add IP to redis bans if needed
if counter > threshold then
local ban, err = redis_client:set("ban_" .. ip, "bad behavior", "EX", ban_time)
if not ban then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) SET failed : " .. err)
redis_client:close()
return
end
end
-- Exec transaction
local exec, err = redis_client:exec()
if not exec then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXEC failed : " .. err)
redis_client:close()
return
end
for i, v in ipairs(exec) do
if v[1] == false then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) Transaction failed : " .. v[2])
redis_client:close()
return
end
end
-- End connection
redis_client:close()
counter = counter + 1
end
-- Call decrease later
local ok, err = ngx.timer.at(count_time, _M.decrease, use_redis, ip)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) can't create decrease timer : " .. err)
end
-- Store local counter
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, counter)
@ -92,26 +55,35 @@ function _M.increase(premature, use_redis, ip, count_time, ban_time, threshold)
end
logger.log(ngx.WARN, "BAD-BEHAVIOR", "IP " .. ip .. " is banned for " .. ban_time .. "s (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
end
-- Call decrease later
local ok, err = ngx.timer.at(count_time, _M.decrease, use_redis, ip)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) can't create decrease timer : " .. err)
end
end
function _M.decrease(premature, use_redis, ip)
-- Decrease from local store
local count, err = datastore:get("plugin_badbehavior_count_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Can't get counts from the datastore : " .. err)
return
-- Declare counter
local counter = false
-- Redis case
if use_redis then
local redis_counter = _M.redis_decrease(ip)
if not redis_counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) redis_decrease failed, falling back to local")
else
counter = redis_counter
end
end
if not count then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Count is null")
return
-- Local case
if not counter then
local local_counter, err = datastore:get("plugin_badbehavior_count_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Can't get counts from the datastore : " .. err)
return
end
if not local_counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Count is null")
return
end
counter = local_counter - 1
end
local new_count = count - 1
if new_count <= 0 then
-- Update local counter
if counter <= 0 then
datastore:delete("plugin_badbehavior_count_" .. ip)
return
end
@ -120,23 +92,6 @@ function _M.decrease(premature, use_redis, ip)
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Can't save counts to the datastore : " .. err)
return
end
-- Decrease from redis
if use_redis then
-- Connect to server
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Can't connect to redis server : " .. err)
return
end
-- Decrement counter
local counter, err = redis_client:decr("bad_behavior_" .. ip)
if not counter then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) DECR failed : " .. err)
redis_client:close()
return
end
redis_client:close()
end
end
function _M:log()
@ -159,6 +114,11 @@ function _M:log()
if ngx.var.is_whitelisted == "yes" then
return true, "client is whitelisted"
end
-- Check if we are already banned
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if banned then
return true, "already banned"
end
-- Call increase function later and with cosocket enabled
local use_redis = false
if self.use_redis == "yes" then
@ -166,7 +126,7 @@ function _M:log()
end
local ok, err = ngx.timer.at(0, _M.increase, use_redis, ngx.var.remote_addr, tonumber(self.count_time), tonumber(self.ban_time), tonumber(self.threshold))
if not ok then
return false, "can't create decrease timer : " .. err
return false, "can't create increase timer : " .. err
end
return true, "success"
end
@ -175,4 +135,103 @@ function _M:log_default()
return _M:log()
end
function _M.redis_increase(ip, count_time, ban_time, threshold)
-- Connect to server
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) Can't connect to redis server : " .. err)
return false
end
-- Start transaction
local ok, err = redis_client:multi()
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) Can't start transaction : " .. err)
clusterstore:close(redis_client)
return false
end
-- Increment counter
ok, err = redis_client:incr("bad_behavior_" .. ip)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) INCR failed : " .. err)
clusterstore:close(redis_client)
return false
end
-- Expires counter
ok, err = redis_client:expire("bad_behavior_" .. ip, count_time)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXPIRE failed : " .. err)
clusterstore:close(redis_client)
return false
end
-- Exec transaction
local exec, err = redis_client:exec()
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXEC failed : " .. err)
clusterstore:close(redis_client)
return false
end
if type(exec) ~= "table" then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXEC result is not a table")
clusterstore:close(redis_client)
return false
end
-- Extract counter
local counter = exec[1]
if type(counter) == "table" then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) INCR error : " .. counter[2])
clusterstore:close(redis_client)
return false
end
-- Check expire result
local expire = exec[2]
if type(expire) == "table" then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) EXPIRE error : " .. expire[2])
clusterstore:close(redis_client)
return false
end
-- Add IP to redis bans if needed
if counter > threshold then
local ban, err = redis_client:set("ban_" .. ip, "bad behavior", "EX", ban_time)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) SET failed : " .. err)
clusterstore:close(redis_client)
return false
end
end
-- End connection
clusterstore:close(redis_client)
return counter
end
function _M.redis_decrease(ip)
-- Connect to server
local redis_client, err = clusterstore:connect()
if not redis_client then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Can't connect to redis server : " .. err)
return false
end
-- Decrement counter
local counter, err = redis_client:decr("bad_behavior_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) DECR failed : " .. err)
clusterstore:close(redis_client)
return false
end
-- Delete counter
if counter < 0 then
counter = 0
end
if counter == 0 then
local ok, err = redis_client:del("bad_behavior_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) DEL failed : " .. err)
clusterstore:close(redis_client)
return false
end
end
-- End connection
clusterstore:close(redis_client)
return counter
end
return _M