bw - init work on redis

This commit is contained in:
bunkerity 2023-02-16 16:42:38 +01:00
parent 8fa94d6a52
commit 902fe6ad07
5 changed files with 326 additions and 43 deletions

View File

@ -0,0 +1,56 @@
local M = {}
local redis = require "resty.redis"
local utils = require "utils"
function M:connect()
-- Instantiate object
local redis_client, err = redis:new()
if redis_client == nil then
return false, err
end
-- Get variables
local variables = {
["REDIS_HOST"] = "",
["REDIS_PORT"] = "",
["REDIS_SSL"] = "",
["REDIS_TIMEOUT"] = "",
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = ""
}
for k, v in pairs(variables) do
local value, err = utils.get_variable(k)
if value == nil then
return false, err
end
variables[k] = value
end
-- Set timeouts
redis_client:set_timeouts(tonumber(variables["REDIS_TIMEOUT"]), tonumber(variables["REDIS_TIMEOUT"]), tonumber(variables["REDIS_TIMEOUT"]))
-- Connect
local options = {
["ssl"] = false
}
if variables["REDIS_SSL"] == "yes" then
options["ssl"] = true
end
return redis.connect(variables["REDIS_HOST"], tonumber(variables["REDIS_PORT"]), options)
end
function M:close(redis_client)
-- Get variables
local variables = {
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = ""
}
for k, v in pairs(variables) do
local value, err = utils.get_variable(k)
if value == nil then
return false, err
end
variables[k] = value
end
-- Equivalent to close but keep a pool of connections
return redis_client:set_keepalive(tonumber(variables["REDIS_KEEPALIVE_IDLE"]), tonumber(variables["REDIS_KEEPALIVE_POOL"]))
end
return M

View File

@ -19,6 +19,34 @@ 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
-- 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)
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_" .. ip, 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()
end
end
-- List all plugins
local list, err = plugins:list()

View File

@ -1,74 +1,173 @@
local _M = {}
_M.__index = _M
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local clusterstore = require "clusterstore"
function _M.new()
local self = setmetatable({}, _M)
return self, nil
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
-- 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
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
-- 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
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()
end
-- Store local counter
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, counter)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) can't save counts to the datastore : " .. err)
return
end
-- Store local ban
if counter > threshold then
local ok, err = datastore:set("bans_ip_" .. ip, "bad behavior", ban_time)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) can't save ban to the datastore : " .. err)
return
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
end
if not count then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) Count is null")
return
end
local new_count = count - 1
if new_count <= 0 then
datastore:delete("plugin_badbehavior_count_" .. ip)
return
end
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, new_count)
if not ok then
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()
-- Get vars
self.use = utils.get_variable("USE_BAD_BEHAVIOR")
self.ban_time = utils.get_variable("BAD_BEHAVIOR_BAN_TIME")
self.status_codes = utils.get_variable("BAD_BEHAVIOR_STATUS_CODES")
self.threshold = utils.get_variable("BAD_BEHAVIOR_THRESHOLD")
self.count_time = utils.get_variable("BAD_BEHAVIOR_COUNT_TIME")
self.use_redis = utils.get_variable("USE_REDIS")
-- Check if bad behavior is activated
if self.use ~= "yes" then
return true, "bad behavior not activated"
end
-- Check if we have a bad status code
if not self.status_codes:match(tostring(ngx.status)) then
return true, "not increasing counter"
end
-- Check if we are whitelisted
if ngx.var.is_whitelisted == "yes" then
return true, "client is whitelisted"
end
local count, err = datastore:get("plugin_badbehavior_count_" .. ngx.var.remote_addr)
if not count and err ~= "not found" then
return false, "can't get counts from the datastore : " .. err
-- Call increase function later and with cosocket enabled
local use_redis = false
if self.use_redis == "yes" then
use_redis = true
end
local new_count = 1
if count ~= nil then
new_count = count + 1
end
local ok, err = datastore:set("plugin_badbehavior_count_" .. ngx.var.remote_addr, new_count)
if not ok then
return false, "can't save counts to the datastore : " .. err
end
local function decrease_callback(premature, ip)
local count, err = datastore:get("plugin_badbehavior_count_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease_callback) Can't get counts from the datastore : " .. err)
return
end
if not count then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease_callback) Count is null")
return
end
local new_count = count - 1
if new_count <= 0 then
datastore:delete("plugin_badbehavior_count_" .. ip)
return
end
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, new_count)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease_callback) Can't save counts to the datastore : " .. err)
end
end
local hdr, err = ngx.timer.at(tonumber(self.count_time), decrease_callback, ngx.var.remote_addr)
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
end
if new_count > tonumber(self.threshold) then
local ok, err = datastore:set("bans_ip_" .. ngx.var.remote_addr, "bad behavior", tonumber(self.ban_time))
if not ok then
return false, "can't save ban to the datastore : " .. err
end
logger.log(ngx.WARN, "BAD-BEHAVIOR", "IP " .. ngx.var.remote_addr .. " is banned for " .. tostring(self.ban_time) .. "s (" .. tostring(new_count) .. "/" .. tostring(self.threshold) .. ")")
end
return true, "success"
end

View File

@ -0,0 +1,72 @@
{
"id": "redis",
"order": 999,
"name": "Redis",
"description": "Redis server configuration when using BunkerWeb in cluster mode.",
"version": "0.1",
"settings": {
"USE_REDIS": {
"context": "global",
"default": "no",
"help": "Activate Redis.",
"id": "use-redis",
"label": "Activate Redis",
"regex": "^(yes|no)$",
"type": "check"
},
"REDIS_HOST": {
"context": "global",
"default": "",
"help": "Redis server IP or hostname.",
"id": "redis-host",
"label": "Redis server",
"regex": "^.*$",
"type": "text"
},
"REDIS_PORT": {
"context": "global",
"default": "6379",
"help": "Redis server port.",
"id": "redis-port",
"label": "Redis port",
"regex": "^[0-9]+$",
"type": "text"
},
"REDIS_SSL": {
"context": "global",
"default": "no",
"help": "Use SSL/TLS connection with Redis server.",
"id": "redis-ssl",
"label": "Redis SSL/TLS",
"regex": "^(yes|no)$",
"type": "check"
},
"REDIS_TIMEOUT": {
"context": "global",
"default": "1000",
"help": "Redis server timeout (in ms) for connect, read and write.",
"id": "redis-timeout",
"label": "Redis timeout (ms)",
"regex": "^[0-9]+$",
"type": "text"
},
"REDIS_KEEPALIVE_IDLE": {
"context": "global",
"default": "30000",
"help": "Max idle time (in ms) before closing redis connection in the pool.",
"id": "redis-keepalive-idle",
"label": "Redis keepalive idle (ms)",
"regex": "^[0-9]+$",
"type": "text"
},
"REDIS_KEEPALIVE_POOL": {
"context": "global",
"default": "10",
"help": "Max number of redis connection(s) kept in the pool.",
"id": "redis-keepalive-pool",
"label": "Redis keepalive pool",
"regex": "^[0-9]+$",
"type": "text"
}
}
}

View File

@ -0,0 +1,28 @@
local _M = {}
_M.__index = _M
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
function _M.new()
local self = setmetatable({}, _M)
return self, nil
end
function _M:init()
-- Check if init is needed
local init_needed, err = utils.get_variable("USE_REDIS", "yes")
if init_needed == nil then
return false, "can't check USE_REDIS variable : " .. err
end
if not init_needed then
return true, "redis not used"
end
-- TODO : check redis connectivity
return true, "redis ping successful"
end
return _M