bw - init work on reason data and fix nil REDIS_SENTINEL_HOSTS for sessions

This commit is contained in:
florian 2024-01-04 11:21:39 +01:00
parent 68b3d67857
commit e108d3f533
No known key found for this signature in database
GPG key ID: 93EE47CC3D061500
16 changed files with 221 additions and 104 deletions

View file

@ -88,8 +88,8 @@ function plugin:get_id()
end
-- luacheck: ignore 212
function plugin:ret(ret, msg, status, redirect)
return { ret = ret, msg = msg, status = status, redirect = redirect }
function plugin:ret(ret, msg, status, redirect, data)
return { ret = ret, msg = msg, status = status, redirect = redirect, data = data }
end
return plugin

View file

@ -290,15 +290,24 @@ end
utils.get_reason = function(ctx)
-- ngx.ctx
if ctx and ctx.bw and ctx.bw.reason then
return ctx.bw.reason
return ctx.bw.reason, ctx.bw.reason_data or {}
end
-- ngx.var
if var.reason and var.reason ~= "" then
return var.reason
local var_reason = var.reason
if var_reason and var_reason ~= "" then
local reason_data = {}
local var_reason_data = var.reason_data
if var_reason_data and reason_data ~= "" then
local ok, data = pcall(decode, var_reason_data)
if ok then
reason_data = data
end
end
return var_reason, reason_data
end
-- os.getenv
if os.getenv("REASON") == "modsecurity" then
return "modsecurity"
return "modsecurity", {}
end
-- datastore ban
local ip
@ -309,15 +318,28 @@ utils.get_reason = function(ctx)
end
local banned, _ = datastore:get("bans_ip_" .. ip)
if banned then
return banned
return banned, {}
end
-- unknown
if ngx.status == utils.get_deny_status() then
return "unknown"
return "unknown", {}
end
return nil
end
utils.set_reason = function(reason, reason_data, ctx)
if ctx and ctx.bw then
ctx.bw.reason = reason or "unknown"
ctx.bw.reason_data = reason_data or {}
end
if var.reason then
var.reason = reason
if var.reason_data then
var.reason_data = encode(reason_data or {})
end
end
end
utils.is_whitelisted = function(ctx)
-- ngx.ctx
if ctx and ctx.bw and ctx.bw.is_whitelisted then

View file

@ -23,6 +23,7 @@ access_by_lua_block {
local call_plugin = helpers.call_plugin
local is_whitelisted = utils.is_whitelisted
local is_banned = utils.is_banned
local set_reason = utils.set_reason
local get_deny_status = utils.get_deny_status
local tostring = tostring
@ -56,7 +57,7 @@ access_by_lua_block {
logger:log(ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
ctx.bw.is_banned = true
ctx.bw.reason = reason
set_reason(reason, {}, ctx)
save_ctx(ctx)
logger:log(WARN,
"IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
@ -102,7 +103,7 @@ access_by_lua_block {
end
if ret.status then
if ret.status == get_deny_status() then
ctx.bw.reason = plugin_id
set_reason(plugin_id, ret.data, ctx)
logger:log(WARN, "denied access from " .. plugin_id .. " : " .. ret.msg)
else
logger:log(NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)

View file

@ -3,6 +3,7 @@ log_by_lua_block {
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local ngx = ngx
local ERR = ngx.ERR
@ -13,6 +14,7 @@ log_by_lua_block {
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
local encode = cjson.encode
-- Start log phase
local logger = clogger:new("LOG")
@ -72,9 +74,9 @@ log_by_lua_block {
logger:log(INFO, "called log() methods of plugins")
-- Display reason at info level
local reason = get_reason(ctx)
local reason, reason_data = get_reason(ctx)
if reason then
logger:log(INFO, "client was denied with reason : " .. reason)
logger:log(INFO, "client was denied with reason " .. reason .. " and data = " .. encode(reason_data))
end
logger:log(INFO, "log phase ended")

View file

@ -20,6 +20,7 @@ server {
# variables
set $reason '';
set $reason_data = '';
set $ctx_ref '';
# include LUA files

View file

@ -1,71 +1,83 @@
log_by_lua_block {
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local ngx = ngx
local ERR = ngx.ERR
local INFO = ngx.INFO
local fill_ctx = helpers.fill_ctx
local get_reason = utils.get_reason
local require_plugin = helpers.require_plugin
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
local encode = cjson.encode
-- Start log phase
local logger = clogger:new("LOG")
local datastore = cdatastore:new()
logger:log(ngx.INFO, "log phase started")
logger:log(INFO, "log phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors, ctx = helpers.fill_ctx()
logger:log(INFO, "filling ngx.ctx ...")
local ok, ret, errors, ctx = fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
logger:log(ERR, "fill_ctx() failed : " .. ret)
elseif errors then
for i, error in ipairs(errors) do
logger:log(ngx.ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
logger:log(ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
end
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
logger:log(INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
logger:log(ERR, "can't get plugins order from datastore : " .. err)
return
end
-- Call log_stream() methods
logger:log(ngx.INFO, "calling log_stream() methods of plugins ...")
logger:log(INFO, "calling log_stream() methods of plugins ...")
for i, plugin_id in ipairs(order.log_stream) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin_id)
local plugin_lua, err = require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
logger:log(ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.INFO, err)
logger:log(INFO, err)
else
-- Check if plugin has log_stream method
if plugin_lua.log_stream ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
local ok, plugin_obj = new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
logger:log(ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "log_stream")
local ok, ret = call_plugin(plugin_obj, "log_stream")
if not ok then
logger:log(ngx.ERR, ret)
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":log_stream() call failed : " .. ret.msg)
logger:log(ERR, plugin_id .. ":log_stream() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin_id .. ":log_stream() call successful : " .. ret.msg)
logger:log(INFO, plugin_id .. ":log_stream() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method log_stream() is not defined")
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method log_stream() is not defined")
end
end
end
logger:log(ngx.INFO, "called log_stream() methods of plugins")
logger:log(INFO, "called log_stream() methods of plugins")
-- Display reason at info level
if ctx.bw.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. ctx.bw.reason)
local reason, reason_data = get_reason(ctx)
if reason then
logger:log(INFO, "client was denied with reason " .. reason .. " and data = " .. encode(reason_data))
end
logger:log(ngx.INFO, "log phase ended")
logger:log(INFO, "log phase ended")
}

View file

@ -1,105 +1,119 @@
preread_by_lua_block {
ngx.ctx
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local utils = require "bunkerweb.utils"
local cdatastore = require "bunkerweb.datastore"
local cclusterstore = require "bunkerweb.clusterstore"
local cjson = require "cjson"
local ngx = ngx
local exit = ngx.exit
local ERR = ngx.ERR
local INFO = ngx.INFO
local WARN = ngx.WARN
local NOTICE = ngx.NOTICE
local fill_ctx = helpers.fill_ctx
local save_ctx = helpers.save_ctx
local require_plugin = helpers.require_plugin
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local is_whitelisted = utils.is_whitelisted
local is_banned = utils.is_banned
local set_reason = utils.set_reason
local get_deny_status = utils.get_deny_status
local tostring = tostring
-- Start preread phase
local logger = clogger:new("PREREAD")
local datastore = cdatastore:new()
logger:log(ngx.INFO, "preread phase started")
logger:log(INFO, "preread phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors, ctx = helpers.fill_ctx()
logger:log(INFO, "filling ngx.ctx ...")
local ok, ret, errors, ctx = fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
logger:log(ERR, "fill_ctx() failed : " .. ret)
elseif errors then
for i, error in ipairs(errors) do
logger:log(ngx.ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
logger:log(ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
end
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
logger:log(INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Process bans as soon as possible
if ctx.bw.is_whitelisted ~= "yes" then
local banned, reason, ttl = utils.is_banned(ctx.bw.remote_addr)
if not is_whitelisted(ctx) then
local banned, reason, ttl = is_banned(ctx.bw.remote_addr)
if banned == nil then
logger:log(ngx.ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason)
logger:log(ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
ctx.bw.is_banned = true
helpers.save_ctx(ctx)
set_reason(reason, {}, ctx)
save_ctx(ctx)
logger:log(ngx.WARN,
"IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
return ngx.exit(utils.get_deny_status(ctx))
return ngx.exit(get_deny_status())
else
logger:log(ngx.INFO, "IP " .. ctx.bw.remote_addr .. " is not banned")
logger:log(INFO, "IP " .. ctx.bw.remote_addr .. " is not banned")
end
end
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
logger:log(ERR, "can't get plugins order from datastore : " .. err)
return
end
-- Call preread() methods
logger:log(ngx.INFO, "calling preread() methods of plugins ...")
logger:log(INFO, "calling preread() methods of plugins ...")
local status = nil
for i, plugin_id in ipairs(order.preread) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin_id)
local plugin_lua, err = require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
logger:log(ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.INFO, err)
logger:log(INFO, err)
else
-- Check if plugin has preread method
if plugin_lua.preread ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
local ok, plugin_obj = new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
logger:log(ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "preread")
local ok, ret = call_plugin(plugin_obj, "preread")
if not ok then
logger:log(ngx.ERR, ret)
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":preread() call failed : " .. ret.msg)
logger:log(ERR, plugin_id .. ":preread() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin_id .. ":preread() call successful : " .. ret.msg)
logger:log(INFO, plugin_id .. ":preread() call successful : " .. ret.msg)
end
if ret.status then
if ret.status == utils.get_deny_status(ctx) then
ctx.bw.reason = plugin_id
logger:log(ngx.WARN, "denied preread from " .. plugin_id .. " : " .. ret.msg)
if ret.status == get_deny_status() then
set_reason(plugin_id, ret.data, ctx)
logger:log(WARN, "denied preread from " .. plugin_id .. " : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
logger:log(NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
end
status = ret.status
break
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method preread() is not defined")
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method preread() is not defined")
end
end
end
logger:log(ngx.INFO, "called preread() methods of plugins")
logger:log(INFO, "called preread() methods of plugins")
-- Save ctx
helpers.save_ctx(ctx)
save_ctx(ctx)
logger:log(ngx.INFO, "preread phase ended")
logger:log(INFO, "preread phase ended")
-- Return status if needed
if status then
return ngx.exit(status)
return exit(status)
end
return true

View file

@ -16,6 +16,7 @@ server {
# variables
set $reason '';
set $reason_data = '';
set $ctx_ref '';
set $server_name '{{ SERVER_NAME.split(" ")[0] }}';

View file

@ -27,51 +27,63 @@ ssl_certificate_by_lua_block {
local cjson = require "cjson"
local ssl = require "ngx.ssl"
local ngx = ngx
local ngx_req = ngx.req
local is_internal = ngx_req.is_internal
local ERR = ngx.ERR
local INFO = ngx.INFO
local set_cert = ssl.set_cert
local set_priv_key = ssl.set_priv_key
local require_plugin = helpers.require_plugin
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
-- Start ssl_certificate phase
local logger = clogger:new("SSL-CERTIFICATE")
local datastore = cdatastore:new()
logger:log(ngx.INFO, "ssl_certificate phase started")
logger:log(INFO, "ssl_certificate phase started")
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
logger:log(ERR, "can't get plugins order from datastore : " .. err)
return
end
-- Call ssl_certificate() methods
logger:log(ngx.INFO, "calling ssl_certificate() methods of plugins ...")
logger:log(INFO, "calling ssl_certificate() methods of plugins ...")
for i, plugin_id in ipairs(order.ssl_certificate) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin_id)
local plugin_lua, err = require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
logger:log(ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.INFO, err)
logger:log(INFO, err)
else
-- Check if plugin has ssl_certificate method
if plugin_lua.ssl_certificate ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = new_plugin(plugin_lua)
if not ok then
logger:log(ngx.ERR, plugin_obj)
logger:log(ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "ssl_certificate")
local ok, ret = call_plugin(plugin_obj, "ssl_certificate")
if not ok then
logger:log(ngx.ERR, ret)
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":ssl_certificate() call failed : " .. ret.msg)
logger:log(ERR, plugin_id .. ":ssl_certificate() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin_id .. ":ssl_certificate() call successful : " .. ret.msg)
logger:log(INFO, plugin_id .. ":ssl_certificate() call successful : " .. ret.msg)
if ret.status then
logger:log(ngx.INFO, plugin_id .. " is setting certificate/key : " .. ret.msg)
local ok, err = ssl.set_cert(ret.status[1])
local ok, err = set_cert(ret.status[1])
if not ok then
logger:log(ngx.ERR, "error while setting certificate : " .. err)
logger:log(ERR, "error while setting certificate : " .. err)
else
local ok, err = ssl.set_priv_key(ret.status[2])
local ok, err = set_priv_key(ret.status[2])
if not ok then
logger:log(ngx.ERR, "error while setting private key : " .. err)
logger:log(ERR, "error while setting private key : " .. err)
else
return true
end
@ -80,13 +92,13 @@ ssl_certificate_by_lua_block {
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method ssl_certificate() is not defined")
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method ssl_certificate() is not defined")
end
end
end
logger:log(ngx.INFO, "called ssl_certificate() methods of plugins")
logger:log(INFO, "called ssl_certificate() methods of plugins")
logger:log(ngx.INFO, "ssl_certificate phase ended")
logger:log(INFO, "ssl_certificate phase ended")
return true
}

View file

@ -184,7 +184,10 @@ function antibot:access()
end
-- Method is suspicious, let's deny the request
return self:ret(true, "unsupported HTTP method for antibot", get_deny_status())
local data = {}
data["id"] = "suspicious-method"
data["method"] = self.ctx.bw.request_method
return self:ret(true, "unsupported HTTP method for antibot", get_deny_status(), nil, data)
end
function antibot:content()

View file

@ -135,7 +135,9 @@ function blacklist:access()
return self:ret(
true,
k .. " is in cached blacklist (info : " .. cached .. ")",
get_deny_status()
get_deny_status(),
nil,
self:get_data(cached)
)
end
if ok and cached then
@ -159,10 +161,15 @@ function blacklist:access()
self.logger:log(ERR, "error while adding element to cache : " .. err)
end
if blacklisted ~= "ok" then
local data = {}
data["id"] = "blacklisted-" ..
data["method"] = self.ctx.bw.request_method
return self:ret(
true,
k .. " is blacklisted (info : " .. blacklisted .. ")",
get_deny_status()
get_deny_status(),
nil,
self:get_data(blacklisted)
)
end
end
@ -344,4 +351,19 @@ function blacklist:is_blacklisted_ua()
return false, "ok"
end
function blacklist:get_data(blacklisted)
local data = {}
if blacklisted == "ip" then
data["id"] = "ip"
else
local id, value = blacklisted:match("^(.+) (.+)$")
if id and value then
id = id:lower()
data["id"] = id
data[id] = value
end
end
return data
end
return blacklist

View file

@ -181,7 +181,7 @@ function bunkernet:log(bypass_checks)
end
end
-- Check if IP has been blocked
local reason = get_reason(self.ctx)
local reason, reason_data = get_reason(self.ctx)
if not reason then
return self:ret(true, "ip is not blocked")
end
@ -200,8 +200,8 @@ function bunkernet:log(bypass_checks)
return self:ret(true, "already reported recently")
end
-- luacheck: ignore 212 431
local function report_callback(premature, obj, ip, reason, method, url, headers, use_redis)
local ok, err, status, _ = obj:report(ip, reason, method, url, headers)
local function report_callback(premature, obj, ip, reason, reason_data, method, url, headers, use_redis)
local ok, err, status, _ = obj:report(ip, reason, reason_data, method, url, headers)
if status == 429 then
obj.logger:log(WARN, "bunkernet API is rate limiting us")
elseif not ok then
@ -221,6 +221,7 @@ function bunkernet:log(bypass_checks)
self,
self.ctx.bw.remote_addr,
reason,
reason_data,
self.ctx.bw.request_method,
self.ctx.bw.request_uri,
ngx.req.get_headers()
@ -297,10 +298,11 @@ function bunkernet:ping()
return self:request("GET", "/ping", {})
end
function bunkernet:report(ip, reason, method, url, headers)
function bunkernet:report(ip, reason, reason_data, method, url, headers)
local data = {
ip = ip,
reason = reason,
data = reason_data,
method = method,
url = url,
headers = headers,

View file

@ -102,7 +102,12 @@ function cors:access()
return self:ret(
true,
"origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access",
get_deny_status()
get_deny_status(),
nil,
{
id = "origin",
origin = self.ctx.bw.http_origin
}
)
end
-- Send CORS policy with a 204 (no content) status

View file

@ -42,7 +42,12 @@ function country:access()
.. " is in country cache (blacklisted, country = "
.. data.country
.. ")",
get_deny_status()
get_deny_status(),
nil,
{
id = "country",
country = data.country
}
)
end
@ -84,7 +89,12 @@ function country:access()
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
get_deny_status()
get_deny_status(),
nil,
{
id = "country",
country = data.country
}
)
end
@ -99,7 +109,12 @@ function country:access()
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",
get_deny_status()
get_deny_status(),
nil,
{
id = "country",
country = data.country
}
)
end
end

View file

@ -94,7 +94,12 @@ function dnsbl:access()
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
get_deny_status()
get_deny_status(),
nil,
{
id = "dnsbl",
dnsbl = cached
}
)
end
-- Loop on DNSBL list
@ -156,7 +161,7 @@ function dnsbl:access()
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
return self:ret(true, "IP is blacklisted by " .. ret_server, get_deny_status())
return self:ret(true, "IP is blacklisted by " .. ret_server, get_deny_status(), nil, {id = "dnsbl", dnsbl = ret_server})
end
-- Error case
return self:ret(false, ret_err)

View file

@ -126,7 +126,7 @@ function sessions:init()
ssl = redis_vars["REDIS_SSL"] == "yes",
database = tonumber(redis_vars["REDIS_DATABASE"])
}
if redis_vars["REDIS_SENTINEL_HOSTS"] ~= "" then
if redis_vars["REDIS_SENTINEL_HOSTS"] ~= nil then
config.redis.master = redis_vars["REDIS_SENTINEL_MASTER"]
config.redis.role = "master"
config.redis.sentinel_username = redis_vars["REDIS_SENTINEL_USERNAME"]