refactor - badbehavior, blacklist, bunkernet, cache, cors, country and dnsbl

This commit is contained in:
bunkerity 2023-04-13 18:14:02 +02:00
parent 03ec271e21
commit 2075a5d4c2
9 changed files with 667 additions and 1080 deletions

View File

@ -36,7 +36,7 @@ local cache, err = mlcache.new(
}
)
logger:new("CACHESTORE")
if not store then
if not cache then
logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
end
@ -49,40 +49,27 @@ end
function cachestore:get(key)
local function callback(key)
-- Connect to redis
local clusterstore = require "clusterstore"
local redis, err = clusterstore:connect()
if not redis then
local clusterstore = require "bunkerweb.clusterstore"
local ok, err = clusterstore:new()
if not ok then
return nil, "clusterstore:new() failed : " .. err, nil
end
local ok, err = clusterstore:connect()
if not ok then
return nil, "can't connect to redis : " .. err, nil
end
-- Start transaction
local ok, err = redis:multi()
if not ok then
clusterstore:close(redis)
return nil, "multi() failed : " .. err, nil
end
-- GET
local ok, err = redis:get(key)
if not ok then
clusterstore:close(redis)
return nil, "get() failed : " .. err, nil
end
-- TTL
local ok, err = redis:ttl(key)
if not ok then
clusterstore:close(redis)
return nil, "ttl() failed : " .. err, nil
end
-- Exec transaction
local exec, err = redis:exec()
local calls = {
{"get", {key}},
{"ttl", {key}}
}
-- Exec transaction
local exec, err = clusterstore:multi(calls)
if err then
clusterstore:close(redis)
clusterstore:close()
return nil, "exec() failed : " .. err, nil
end
-- Get results
if type(exec) ~= "table" then
clusterstore:close(redis)
return nil, "exec() result is not a table", nil
end
local value = exec[1]
if type(value) == "table" then
clusterstore:close(redis)

View File

@ -1,25 +1,63 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local clusterstore = require "bunkerweb.clusterstore"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local clusterstore = require "clusterstore"
local badbehavior = class("badbehavior", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
function badbehavior:new()
-- Call parent new
local ok, err = plugin.new(self, "badbehavior")
if not ok then
return false, err
end
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
self.use_redis = use_redis == "yes"
return true, "success"
end
function _M.increase(premature, use_redis, ip, count_time, ban_time, threshold)
function badbehavior:log()
-- Check if we are whitelisted
if ngx.var.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Check if bad behavior is activated
if self.variables["USE_BAD_BEHAVIOR"] ~= "yes" then
return self:ret(true, "bad behavior not activated")
end
-- Check if we have a bad status code
if not self.variables["BAD_BEHAVIOR_STATUS_CODES"]:match(tostring(ngx.status)) then
return self:ret(true, "not increasing counter")
end
-- Check if we are already banned
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if banned then
return self:ret(true, "already banned")
end
-- Call increase function later and with cosocket enabled
local ok, err = ngx.timer.at(0, badbehavior.increase, self.use_redis, ngx.var.remote_addr, tonumber(self.variables["BAD_BEHAVIOR_COUNT_TIME"]), tonumber(self.variables["BAD_BEHAVIOR_BAN_TIME"]), tonumber(self.variables["BAD_BEHAVIOR_THRESHOLD"]))
if not ok then
return self:ret(false, "can't create increase timer : " .. err)
end
return self:ret(true, "success")
end
function badbehavior:log_default()
return self:log()
end
function badbehavior.increase(premature, use_redis, ip, count_time, ban_time, threshold)
-- Declare counter
local counter = false
-- Redis case
if use_redis then
local redis_counter = _M.redis_increase(ip, count_time, ban_time, threshold)
local redis_counter, err = badbehavior.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")
badbehavior.logger:log(ngx.ERR, "(increase) redis_increase failed, falling back to local : " .. err)
else
counter = redis_counter
end
@ -28,7 +66,7 @@ function _M.increase(premature, use_redis, ip, count_time, ban_time, threshold)
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
badbehavior.logger:log(ngx.ERR, "(increase) can't get counts from the datastore : " .. err)
end
if local_counter == nil then
local_counter = 0
@ -36,197 +74,97 @@ function _M.increase(premature, use_redis, ip, count_time, ban_time, threshold)
counter = local_counter + 1
end
-- Call decrease later
local ok, err = ngx.timer.at(count_time, _M.decrease, use_redis, ip)
local ok, err = ngx.timer.at(count_time, badbehavior.decrease, use_redis, ip)
if not ok then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(increase) can't create decrease timer : " .. err)
badbehavior.logger:log(ngx.ERR, "(increase) can't create decrease timer : " .. err)
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)
badbehavior.logger:log(ngx.ERR, "(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)
badbehavior.logger:log(ngx.ERR, "(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) .. ")")
badbehavior.logger:log(ngx.WARN, "IP " .. ip .. " is banned for " .. ban_time .. "s (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
end
end
function _M.decrease(premature, use_redis, ip)
-- 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
-- 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
-- Update local counter
if counter <= 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
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
-- 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
use_redis = true
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 increase timer : " .. err
end
return true, "success"
end
function _M:log_default()
return _M:log()
end
function _M.redis_increase(ip, count_time, ban_time, threshold)
function badbehavior.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
local cstore, err = clusterstore:new()
if not cstore then
return false, err
end
-- Start transaction
local ok, err = redis_client:multi()
local ok, err = clusterstore:connect()
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
return false, err
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
local calls = {
{"incr", {"bad_behavior_" .. ip}},
{"expire", {"bad_behavior_" .. ip, count_time}}
}
local ok, err, exec = clusterstore:multi(calls)
if not ok then
clusterstore:close()
return false, err
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
clusterstore:close()
return false, counter[2]
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
clusterstore:close()
return false, expire[2]
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)
local ok, err = clusterstore:call("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
clusterstore:close()
return false, err
end
end
-- End connection
clusterstore:close(redis_client)
clusterstore:close()
return counter
end
function _M.redis_decrease(ip)
function badbehavior.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
local cstore, err = clusterstore:new()
if not cstore then
return false, err
end
local ok, err = clusterstore:connect()
if not ok then
return false, err
end
-- Decrement counter
local counter, err = redis_client:decr("bad_behavior_" .. ip)
local counter, err = clusterstore:call("decr", "bad_behavior_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) DECR failed : " .. err)
clusterstore:close(redis_client)
return false
clusterstore:close()
return false, err
end
-- Delete counter
if counter < 0 then
counter = 0
end
if counter == 0 then
local ok, err = redis_client:del("bad_behavior_" .. ip)
local ok, err = clusterstore:call("del", "bad_behavior_" .. ip)
if err then
logger.log(ngx.ERR, "BAD-BEHAVIOR", "(decrease) DEL failed : " .. err)
clusterstore:close(redis_client)
return false
return false, err
end
end
-- End connection
@ -234,4 +172,4 @@ function _M.redis_decrease(ip)
return counter
end
return _M
return badbehavior

View File

@ -1,525 +1,300 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local blacklist = class("blacklist", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
end
function _M:init()
function blacklist:new()
-- Call parent new
local ok, err = plugin.new(self, "antibot")
if not ok then
return false, err
end
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
self.use_redis = use_redis == "yes"
-- Check if init is needed
local init_needed, err = utils.has_variable("USE_BLACKLIST", "yes")
if init_needed == nil then
return false, err
end
if not init_needed then
return true, "no service uses Blacklist, skipping init"
end
-- Read blacklists
local blacklists = {
["IP"] = {},
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {},
["IGNORE_IP"] = {},
["IGNORE_RDNS"] = {},
["IGNORE_ASN"] = {},
["IGNORE_USER_AGENT"] = {},
["IGNORE_URI"] = {},
}
local i = 0
for kind, _ in pairs(blacklists) do
local f, err = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(blacklists[kind], line)
i = i + 1
end
f:close()
if ngx.get_phase() == "init" then
local init_needed, err = utils.has_variable("USE_BLACKLIST", "yes")
if init_needed == nil then
return false, err
end
end
-- Load them into datastore
local ok, err = datastore:set("plugin_blacklist_list", cjson.encode(blacklists))
if not ok then
return false, "can't store Blacklist list into datastore : " .. err
end
return true, "successfully loaded " .. tostring(i) .. " bad IP/network/rDNS/ASN/User-Agent/URI"
end
function _M:access()
-- Check if access is needed
local access_needed, err = utils.get_variable("USE_BLACKLIST")
if access_needed == nil then
return false, err
end
if access_needed ~= "yes" then
return true, "Blacklist not activated"
end
-- Check the cache
local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr)
local cached_ignored_ip, err = self:is_in_cache("ignore_ip" .. ngx.var.remote_addr)
if cached_ignored_ip then
logger.log(ngx.NOTICE, "BLACKLIST", "IP is in cached ignore blacklist (info: " .. cached_ignored_ip .. ")")
elseif cached_ip and cached_ip ~= "ok" then
return true, "IP is in blacklist cache (info = " .. cached_ip .. ")", true, utils.get_deny_status()
end
local cached_uri, err = self:is_in_cache("uri" .. ngx.var.uri)
local cached_ignored_uri, err = self:is_in_cache("ignore_uri" .. ngx.var.uri)
if cached_ignored_uri then
logger.log(ngx.NOTICE, "BLACKLIST", "URI is in cached ignore blacklist (info: " .. cached_ignored_uri .. ")")
elseif cached_uri and cached_uri ~= "ok" then
return true, "URI is in blacklist cache (info = " .. cached_uri .. ")", true, utils.get_deny_status()
end
local cached_ua = true
local cached_ignored_ua = false
if ngx.var.http_user_agent then
cached_ua, err = self:is_in_cache("ua" .. ngx.var.http_user_agent)
cached_ignored_ua, err = self:is_in_cache("ignore_ua" .. ngx.var.http_user_agent)
if cached_ignored_ua then
logger.log(ngx.NOTICE, "BLACKLIST", "User-Agent is in cached ignore blacklist (info: " .. cached_ignored_ua .. ")")
elseif cached_ua and cached_ua ~= "ok" then
return true, "User-Agent is in blacklist cache (info = " .. cached_ua .. ")", true, utils.get_deny_status()
self.init_needed = init_needed
-- Decode lists
else
local lists, err = datastore:get("plugin_blacklist_lists")
if not lists then
return false, err
end
self.lists = cjson.decode(lists)
end
if cached_ignored_ip and cached_ignored_uri and cached_ignored_ua then
logger.log(ngx.NOTICE, "BLACKLIST", "full request is in cached ignore blacklist")
elseif cached_ip and cached_uri and cached_ua then
return true, "full request is in blacklist cache (not blacklisted)", false, nil
end
-- Get list
local data, err = datastore:get("plugin_blacklist_list")
if not data then
return false, "can't get Blacklist list : " .. err, false, nil
end
local ok, blacklists = pcall(cjson.decode, data)
if not ok then
return false, "error while decoding blacklists : " .. blacklists, false, nil
end
-- Return value
local ret, ret_err = true, "success"
-- Check if IP is in IP/net blacklist
local ip_net, err = utils.get_variable("BLACKLIST_IP")
local ignored_ip_net, err = utils.get_variable("BLACKLIST_IGNORE_IP")
if ip_net and ip_net ~= "" then
for element in ip_net:gmatch("%S+") do
table.insert(blacklists["IP"], element)
end
end
if ignored_ip_net and ignored_ip_net ~= "" then
for element in ignored_ip_net:gmatch("%S+") do
table.insert(blacklists["IGNORE_IP"], element)
end
end
if not cached_ip then
local ipm, err = ipmatcher.new(blacklists["IP"])
local ipm_ignore, err_ignore = ipmatcher.new(blacklists["IGNORE_IP"])
if not ipm then
ret = false
ret_err = "can't instantiate ipmatcher " .. err
elseif not ipm_ignore then
ret = false
ret_err = "can't instantiate ipmatcher " .. err_ignore
else
if ipm:match(ngx.var.remote_addr) then
if ipm_ignore:match(ngx.var.remote_addr) then
self:add_to_cache("ignore_ip" .. ngx.var.remote_addr, "ip/net")
logger.log(ngx.NOTICE, "BLACKLIST", "client IP " .. ngx.var.remote_addr .. " is in blacklist but is ignored")
else
self:add_to_cache("ip" .. ngx.var.remote_addr, "ip/net")
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist", true, utils.get_deny_status()
end
end
end
end
-- Instantiate ignore variable
local ignore = false
-- Check if rDNS is in blacklist
local rdns_global, err = utils.get_variable("BLACKLIST_RDNS_GLOBAL")
local check = true
if not rdns_global then
logger.log(ngx.ERR, "BLACKLIST", "Error while getting BLACKLIST_RDNS_GLOBAL variable : " .. err)
elseif rdns_global == "yes" then
check, err = utils.ip_is_global(ngx.var.remote_addr)
if check == nil then
logger.log(ngx.ERR, "BLACKLIST", "Error while getting checking if IP is global : " .. err)
end
end
if not cached_ip and check then
local rdns, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns then
ret = false
ret_err = "error while trying to get reverse dns : " .. err
else
local rdns_list, err = utils.get_variable("BLACKLIST_RDNS")
local ignored_rdns_list, err = utils.get_variable("BLACKLIST_IGNORE_RDNS")
if rdns_list and rdns_list ~= "" then
for element in rdns_list:gmatch("%S+") do
table.insert(blacklists["RDNS"], element)
end
end
if ignored_rdns_list and ignored_rdns_list ~= "" then
for element in ignored_rdns_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_RDNS"], element)
end
end
for i, suffix in ipairs(blacklists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for j, ignore_suffix in ipairs(blacklists["IGNORE_RDNS"]) do
if rdns:sub(- #ignore_suffix) == ignore_suffix then
ignore = true
self:add_to_cache("ignore_rdns" .. ngx.var.remote_addr, "rDNS" .. suffix)
logger.log(ngx.NOTICE, "BLACKLIST",
"client IP " .. ngx.var.remote_addr .. " is in blacklist (info = rDNS " .. suffix .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("ip" .. ngx.var.remote_addr, "rDNS" .. suffix)
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist (info = rDNS " .. suffix .. ")", true,
utils.get_deny_status()
end
end
end
end
end
-- Check if ASN is in blacklist
if not cached_ip then
if utils.ip_is_global(ngx.var.remote_addr) then
local asn, err = utils.get_asn(ngx.var.remote_addr)
if not asn then
ret = false
ret_err = "error while trying to get asn number : " .. err
else
local asn_list, err = utils.get_variable("BLACKLIST_ASN")
local ignored_asn_list, err = utils.get_variable("BLACKLIST_IGNORE_ASN")
if asn_list and asn_list ~= "" then
for element in asn_list:gmatch("%S+") do
table.insert(blacklists["ASN"], element)
end
end
if ignored_asn_list and ignored_asn_list ~= "" then
for element in ignored_asn_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_ASN"], element)
end
end
for i, asn_bl in ipairs(blacklists["ASN"]) do
if tostring(asn) == asn_bl then
for j, ignore_asn_bl in ipairs(blacklists["IGNORE_ASN"]) do
if tostring(asn) == ignore_asn_bl then
ignore = true
self:add_to_cache("ignore_asn" .. ngx.var.remote_addr, "ASN" .. tostring(asn))
logger.log(ngx.NOTICE, "BLACKLIST",
"client IP " .. ngx.var.remote_addr .. " is in blacklist (info = ASN " .. tostring(asn) .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ASN " .. tostring(asn))
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist (kind = ASN " .. tostring(asn) .. ")", true,
utils.get_deny_status()
end
end
end
end
end
end
-- IP is not blacklisted
local ok, err = self:add_to_cache("ip" .. ngx.var.remote_addr, "ok")
if not ok then
ret = false
ret_err = err
end
-- Check if User-Agent is in blacklist
if not cached_ua and ngx.var.http_user_agent then
local ua_list, err = utils.get_variable("BLACKLIST_USER_AGENT")
local ignored_ua_list, err = utils.get_variable("BLACKLIST_IGNORE_USER_AGENT")
if ua_list and ua_list ~= "" then
for element in ua_list:gmatch("%S+") do
table.insert(blacklists["USER_AGENT"], element)
end
end
if ignored_ua_list and ignored_ua_list ~= "" then
for element in ignored_ua_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_USER_AGENT"], element)
end
end
for i, ua_bl in ipairs(blacklists["USER_AGENT"]) do
if ngx.var.http_user_agent:match(ua_bl) then
for j, ignore_ua_bl in ipairs(blacklists["IGNORE_USER_AGENT"]) do
if ngx.var.http_user_agent:match(ignore_ua_bl) then
ignore = true
self:add_to_cache("ignore_ua" .. ngx.var.remote_addr, "UA" .. ua_bl)
logger.log(ngx.NOTICE, "BLACKLIST",
"client User-Agent " .. ngx.var.http_user_agent .. " is in blacklist (matched " .. ua_bl .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("ua" .. ngx.var.http_user_agent, "UA " .. ua_bl)
return ret, "client User-Agent " .. ngx.var.http_user_agent .. " is in blacklist (matched " .. ua_bl .. ")", true,
utils.get_deny_status()
end
end
end
-- UA is not blacklisted
local ok, err = self:add_to_cache("ua" .. ngx.var.http_user_agent, "ok")
if not ok then
ret = false
ret_err = err
end
end
-- Check if URI is in blacklist
if not cached_uri then
local uri_list, err = utils.get_variable("BLACKLIST_URI")
local ignored_uri_list, err = utils.get_variable("BLACKLIST_IGNORE_URI")
if uri_list and uri_list ~= "" then
for element in uri_list:gmatch("%S+") do
table.insert(blacklists["URI"], element)
end
end
if ignored_uri_list and ignored_uri_list ~= "" then
for element in ignored_uri_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_URI"], element)
end
end
for i, uri_bl in ipairs(blacklists["URI"]) do
if ngx.var.uri:match(uri_bl) then
for j, ignore_uri_bl in ipairs(blacklists["IGNORE_URI"]) do
if ngx.var.uri:match(ignore_uri_bl) then
ignore = true
self:add_to_cache("ignore_uri" .. ngx.var.remote_addr, "URI" .. uri_bl)
logger.log(ngx.NOTICE, "BLACKLIST",
"client URI " .. ngx.var.uri .. " is in blacklist (matched " .. uri_bl .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("uri" .. ngx.var.uri, "URI " .. uri_bl)
return ret, "client URI " .. ngx.var.uri .. " is in blacklist (matched " .. uri_bl .. ")", true,
utils.get_deny_status()
end
end
end
end
-- URI is not blacklisted
local ok, err = self:add_to_cache("uri" .. ngx.var.uri, "ok")
if not ok then
ret = false
ret_err = err
end
return ret, "IP is not in list (error = " .. ret_err .. ")", false, nil
end
function _M:preread()
-- Check if preread is needed
local preread_needed, err = utils.get_variable("USE_BLACKLIST")
if preread_needed == nil then
return false, err
end
if access_needed ~= "yes" then
return true, "Blacklist not activated"
end
-- Check the cache
local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr)
local cached_ignored_ip, err = self:is_in_cache("ignore_ip" .. ngx.var.remote_addr)
if cached_ignored_ip then
logger.log(ngx.NOTICE, "BLACKLIST", "IP is in cached ignore blacklist (info: " .. cached_ignored_ip .. ")")
elseif cached_ip and cached_ip ~= "ok" then
return true, "IP is in blacklist cache (info = " .. cached_ip .. ")", true, utils.get_deny_status()
elseif cached_ip then
return true, "IP is in blacklist cache (not blacklisted)", false, nil
end
-- Get list
local data, err = datastore:get("plugin_blacklist_list")
if not data then
return false, "can't get Blacklist list : " .. err, false, nil
end
local ok, blacklists = pcall(cjson.decode, data)
if not ok then
return false, "error while decoding blacklists : " .. blacklists, false, nil
end
-- Return value
local ret, ret_err = true, "success"
-- Check if IP is in IP/net blacklist
local ip_net, err = utils.get_variable("BLACKLIST_IP")
local ignored_ip_net, err = utils.get_variable("BLACKLIST_IGNORE_IP")
if ip_net and ip_net ~= "" then
for element in ip_net:gmatch("%S+") do
table.insert(blacklists["IP"], element)
end
end
if ignored_ip_net and ignored_ip_net ~= "" then
for element in ignored_ip_net:gmatch("%S+") do
table.insert(blacklists["IGNORE_IP"], element)
end
end
if not cached_ip then
local ipm, err = ipmatcher.new(blacklists["IP"])
local ipm_ignore, err_ignore = ipmatcher.new(blacklists["IGNORE_IP"])
if not ipm then
ret = false
ret_err = "can't instantiate ipmatcher " .. err
elseif not ipm_ignore then
ret = false
ret_err = "can't instantiate ipmatcher " .. err_ignore
else
if ipm:match(ngx.var.remote_addr) then
if ipm_ignore:match(ngx.var.remote_addr) then
self:add_to_cache("ignore_ip" .. ngx.var.remote_addr, "ip/net")
logger.log(ngx.NOTICE, "BLACKLIST", "client IP " .. ngx.var.remote_addr .. " is in blacklist but is ignored")
else
self:add_to_cache("ip" .. ngx.var.remote_addr, "ip/net")
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist", true, utils.get_deny_status()
end
end
end
end
-- Instantiate ignore variable
local ignore = false
-- Check if rDNS is in blacklist
local rdns_global, err = utils.get_variable("BLACKLIST_RDNS_GLOBAL")
local check = true
if not rdns_global then
logger.log(ngx.ERR, "BLACKLIST", "Error while getting BLACKLIST_RDNS_GLOBAL variable : " .. err)
elseif rdns_global == "yes" then
check, err = utils.ip_is_global(ngx.var.remote_addr)
if check == nil then
logger.log(ngx.ERR, "BLACKLIST", "Error while getting checking if IP is global : " .. err)
end
end
if not cached_ip and check then
local rdns, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns then
ret = false
ret_err = "error while trying to get reverse dns : " .. err
else
local rdns_list, err = utils.get_variable("BLACKLIST_RDNS")
local ignored_rdns_list, err = utils.get_variable("BLACKLIST_IGNORE_RDNS")
if rdns_list and rdns_list ~= "" then
for element in rdns_list:gmatch("%S+") do
table.insert(blacklists["RDNS"], element)
end
end
if ignored_rdns_list and ignored_rdns_list ~= "" then
for element in ignored_rdns_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_RDNS"], element)
end
end
for i, suffix in ipairs(blacklists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for j, ignore_suffix in ipairs(blacklists["IGNORE_RDNS"]) do
if rdns:sub(- #ignore_suffix) == ignore_suffix then
ignore = true
self:add_to_cache("ignore_rdns" .. ngx.var.remote_addr, "rDNS" .. suffix)
logger.log(ngx.NOTICE, "BLACKLIST",
"client IP " .. ngx.var.remote_addr .. " is in blacklist (info = rDNS " .. suffix .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("ip" .. ngx.var.remote_addr, "rDNS" .. suffix)
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist (info = rDNS " .. suffix .. ")", true,
utils.get_deny_status()
end
end
end
end
end
-- Check if ASN is in blacklist
if not cached_ip then
if utils.ip_is_global(ngx.var.remote_addr) then
local asn, err = utils.get_asn(ngx.var.remote_addr)
if not asn then
ret = false
ret_err = "error while trying to get asn number : " .. err
else
local asn_list, err = utils.get_variable("BLACKLIST_ASN")
local ignored_asn_list, err = utils.get_variable("BLACKLIST_IGNORE_ASN")
if asn_list and asn_list ~= "" then
for element in asn_list:gmatch("%S+") do
table.insert(blacklists["ASN"], element)
end
end
if ignored_asn_list and ignored_asn_list ~= "" then
for element in ignored_asn_list:gmatch("%S+") do
table.insert(blacklists["IGNORE_ASN"], element)
end
end
for i, asn_bl in ipairs(blacklists["ASN"]) do
if tostring(asn) == asn_bl then
for j, ignore_asn_bl in ipairs(blacklists["IGNORE_ASN"]) do
if tostring(asn) == ignore_asn_bl then
ignore = true
self:add_to_cache("ignore_asn" .. ngx.var.remote_addr, "ASN" .. tostring(asn))
logger.log(ngx.NOTICE, "BLACKLIST",
"client IP " .. ngx.var.remote_addr .. " is in blacklist (info = ASN " .. tostring(asn) .. ") but is ignored")
break
end
end
if not ignore then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ASN " .. tostring(asn))
return ret, "client IP " .. ngx.var.remote_addr .. " is in blacklist (kind = ASN " .. tostring(asn) .. ")", true,
utils.get_deny_status()
end
end
end
end
end
end
-- IP is not blacklisted
local ok, err = self:add_to_cache("ip" .. ngx.var.remote_addr, "ok")
if not ok then
ret = false
ret_err = err
end
return ret, "IP is not in list (error = " .. ret_err .. ")", false, nil
end
function _M:is_in_cache(ele)
local kind, err = datastore:get("plugin_blacklist_cache_" .. ngx.var.server_name .. ele)
if not kind then
if err ~= "not found" then
logger.log(ngx.ERR, "BLACKLIST", "Error while accessing cache : " .. err)
end
return false, err
end
return kind, "success"
end
function _M:add_to_cache(ele, kind)
local ok, err = datastore:set("plugin_blacklist_cache_" .. ngx.var.server_name .. ele, kind, 3600)
if not ok then
logger.log(ngx.ERR, "BLACKLIST", "Error while adding element to cache : " .. err)
return false, err
end
-- Instantiate cachestore
cachestore:new(use_redis)
return true, "success"
end
return _M
function blacklist:init()
if self.init_needed then
-- Read blacklists
local blacklists = {
["IP"] = {},
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {},
["IGNORE_IP"] = {},
["IGNORE_RDNS"] = {},
["IGNORE_ASN"] = {},
["IGNORE_USER_AGENT"] = {},
["IGNORE_URI"] = {},
}
local i = 0
for kind, _ in pairs(blacklists) do
local f, err = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(blacklists[kind], line)
i = i + 1
end
f:close()
end
end
-- Load them into datastore
local ok, err = datastore:set("plugin_blacklist_lists", cjson.encode(blacklists))
if not ok then
return self:ret(false, "can't store blacklist list into datastore : " .. err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " bad IP/network/rDNS/ASN/User-Agent/URI")
end
end
function blacklist:access()
-- Check if access is needed
if self.variables["USE_BLACKLIST"] ~= "yes" then
return self:ret(true, "blacklist not activated")
end
-- Check the caches
local checks = {
["IP"] = "ip" .. ngx.var.remote_addr
}
if ngx.var.http_user_agent then
checks["UA"] = "ua" .. ngx.var.http_user_agent
end
if ngx.var.uri then
checks["URI"] = "uri" .. ngx.var.uri
end
local already_cached = {
["IP"] = false,
["URI"] = false,
["UA"] = false
}
for k, v in pairs(checks) do
local cached, err = self:is_in_cache(v)
if not cached and err ~= "success" then
self.logger:log(ngx.ERR, "error while checking cache : " .. err)
elseif cached and cached ~= "ok" then
return self:ret(true, k + " is in cached blacklist (info : " .. cached .. ")", utils.get_deny_status())
end
if cached then
already_cached[k] = true
end
end
-- Check lists
if not self.lists then
return self:ret(false, "lists is nil")
end
-- Perform checks
for k, v in pairs(checks) do
if not already_cached[k] then
local blacklisted, err = self:is_blacklisted(k)
if blacklisted == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is blacklisted : " .. err)
else
local ok, err = self:add_to_cache(v, blacklisted or "ok")
if not ok then
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
end
if blacklisted ~= "ok" then
return self:ret(true, k + " is in cached blacklist (info : " .. blacklisted .. ")", utils.get_deny_status())
end
end
end
end
-- Return
return self:ret(true, "not blacklisted")
end
function blacklist:preread()
return self:access()
end
function blacklist:is_in_cache(ele)
local ok, data = cachestore:get("plugin_blacklist_" .. ele)
if not ok then then
return false, data
end
return true, data
end
function blacklist:add_to_cache(ele, value)
local ok, err = cachestore:set("plugin_blacklist_" .. ele, value)
if not ok then then
return false, err
end
return true
end
function blacklist:is_blacklisted(kind)
if kind == "IP" then
return self:is_blacklisted_ip()
elseif kind == "URI"
return self:is_blacklisted_uri()
elseif kind == "UA"
return self:is_blacklisted_ua()
return false, "unknown kind " .. kind
end
function blacklist:is_blacklisted_ip()
-- Check if IP is in ignore list
local ipm, err = ipmatcher.new(self.lists["IGNORE_IP"])
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.var.remote_addr)
if err then
return nil, err
end
if not match then
-- Check if IP is in blacklist
local ipm, err = ipmatcher.new(self.lists["IP"])
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.var.remote_addr)
if err then
return nil, err
end
if match then
return true, "ip"
end
end
-- Check if rDNS is needed
local check_rdns = true
if self.variables["BLACKLIST_RDNS_GLOBAL"] == "yes" then
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
return nil, err
end
if not is_global then
check_rdns = false
end
end
if check_rdns then
-- Get rDNS
local rdns_list, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns_list then
return false, err
end
-- Check if rDNS is in ignore list
local ignore = false
for i, ignore_suffix in ipairs(self.lists["IGNORE_RDNS"]) do
for j, rdns in ipairs(rdns_list) do
if rdns:sub(-#ignore_suffix) == ignore_suffix then
ignore = true
break
end
end
if ignore then
break
end
end
-- Check if rDNS is in blacklist
if not ignore then
for i, suffix in ipairs(self.lists["RDNS"]) do
for j, rdns in ipairs(rdns_list) do
if rdns:sub(-#suffix) == suffix then
return true, "rDNS " .. suffix
end
end
end
end
end
-- Check if ASN is in ignore list
local asn, err = utils.get_asn(ngx.var.remote_addr)
if not asn then
return nil, err
end
local ignore = false
for i, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
if ignore_asn == tostring(asn) then
ignore = true
break
end
end
-- Check if ASN is in blacklist
if not ignore then
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
end
end
-- Not blacklisted
return false, "ok"
end
function blacklist:is_blacklisted_uri()
-- Check if URI is in ignore list
local ignore = false
for i, ignore_uri in ipairs(self.lists["IGNORE_URI"]) do
if ngx.var.uri:match(ignore_uri) then
ignore = true
break
end
end
-- Check if URI is in blacklist
if not ignore then
for i, uri in ipairs(self.lists["URI"]) do
if ngx.var.uri:match(uri) then
return true, "URI " .. uri
end
end
end
-- URI is not blacklisted
return false, "ok"
end
function blacklist:is_blacklisted_ua()
-- Check if UA is in ignore list
local ignore = false
for i, ignore_ua in ipairs(self.lists["IGNORE_USER_AGENT"]) do
if ngx.var.http_user_agent:match(ignore_ua) then
ignore = true
break
end
end
-- Check if UA is in blacklist
if not ignore then
for i, ua in ipairs(self.lists["USER_AGENT"]) do
if ngx.var.http_user_agent:match(ua) then
return true, "UA " .. ua
end
end
end
-- UA is not blacklisted
return false, "ok"
end
return blacklist

View File

@ -1,67 +1,54 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local json = require "cjson"
local http = require "resty.http"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local http = require "resty.http"
local bunkernet = class("bunkernet", plugin)
function _M.new()
local self = setmetatable({}, _M)
local server, err = datastore:get("variable_BUNKERNET_SERVER")
if not server then
return nil, "can't get BUNKERNET_SERVER from datastore : " .. err
end
self.server = server
local id, err = datastore:get("plugin_bunkernet_id")
if not id then
self.id = nil
else
self.id = id
end
return self, nil
end
function _M:init()
local init_needed, err = utils.has_variable("USE_BUNKERNET", "yes")
if init_needed == nil then
function bunkernet:new()
-- Call parent new
local ok, err = plugin.new(self, "bunkernet")
if not ok then
return false, err
end
if not init_needed then
return true, "no service uses BunkerNet, skipping init"
-- Check if init is needed
if ngx.get_phase() == "init" then
local init_needed, err = utils.has_variable("USE_BUNKERNET", "yes")
if init_needed == nil then
return false, err
end
self.init_needed = true
-- Get BunkerNet ID
else
local id, err = datastore:get("plugin_bunkernet_id")
if not id then
self.bunkernet_id = nil
else
self.bunkernet_id = id
end
end
return true, "success"
end
function bunkernet:init()
-- Check if init is needed
if not self.init_needed then
return self:ret(true, "no service uses bunkernet, skipping init")
end
-- Check if instance ID is present
local f, err = io.open("/var/cache/bunkerweb/bunkernet/instance.id", "r")
if not f then
return false, "can't read instance id : " .. err
return self:ret(false, "can't read instance id : " .. err)
end
-- Retrieve instance ID
id = f:read("*all"):gsub("[\r\n]", "")
f:close()
self.id = id
-- TODO : regex check just in case
-- Send a ping with the ID
--local ok, err, status, response = self:ping()
-- BunkerNet server is down or instance can't access it
--if not ok then
--return false, "can't send request to BunkerNet service : " .. err
-- Local instance ID is unknown to the server, let's delete it
--elseif status == 401 then
--local ok, message = os.remove("/var/cache/bunkerweb/bunkernet/instance.id")
--if not ok then
--return false, "can't remove instance ID " .. message
--end
--return false, "instance ID is not valid"
--elseif status == 429 then
--return false, "sent too many requests to the BunkerNet service"
--elseif status ~= 200 then
--return false, "unknown error from BunkerNet service (HTTP status = " .. tostring(status) .. ")"
--end
-- Store ID in datastore
local ok, err = datastore:set("plugin_bunkernet_id", id)
if not ok then
return false, "can't save instance ID to the datastore : " .. err
return self:ret(false, "can't save instance ID to the datastore : " .. err)
end
-- Load databases
local ret = true
@ -81,19 +68,85 @@ function _M:init()
end
end
if not ret then
return false, "error while reading database : " .. err
return self:ret(false, "error while reading database : " .. err)
end
f:close()
local ok, err = datastore:set("plugin_bunkernet_db", cjson.encode(db))
if not ok then
return false, "can't store BunkerNet database into datastore : " .. err
return self:ret(false, "can't store bunkernet database into datastore : " .. err)
end
return true,
"successfully connected to the BunkerNet service " ..
self.server .. " with machine ID " .. id .. " and " .. tostring(i) .. " bad IPs in database"
return self:ret(true, "successfully connected to the bunkernet service " .. self.server .. " with machine ID " .. id .. " and " .. tostring(i) .. " bad IPs in database")
end
function _M:request(method, url, data)
function bunkernet:log(bypass_use_bunkernet)
if not bypass_use_bunkernet then
-- Check if BunkerNet is enabled
if self.variables["USE_BUNKERNET"] ~= "yes" then
return self:ret(true, "bunkernet not activated")
end
end
-- Check if BunkerNet ID is generated
if not self.bunkernet_id then
return self:ret(true, "bunkernet ID is not generated")
end
-- Check if IP has been blocked
local reason = utils.get_reason()
if not reason then
return self:ret(true, "ip is not blocked")
end
if reason == "bunkernet" then
return self:ret(true, "skipping report because the reason is bunkernet")
end
-- Check if IP is global
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
return self:ret(false, "error while checking if IP is global " .. err)
end
if not is_global then
return self:ret(true, "IP is not global")
end
-- TODO : check if IP has been reported recently
local function report_callback(premature, obj, ip, reason, method, url, headers)
local ok, err, status, data = obj:report(ip, reason, method, url, headers)
if status == 429 then
obj.logger:log(ngx.WARN, "bunkernet API is rate limiting us")
elseif not ok then
obj.logger:log(ngx.ERR, "can't report IP : " .. err)
else
obj.logger:log(ngx.NOTICE, "successfully reported IP " .. ip .. " (reason : " .. reason .. ")")
end
end
local hdr, err = ngx.timer.at(0, report_callback, self, ngx.var.remote_addr, reason, ngx.var.request_method,
ngx.var.request_uri, ngx.req.get_headers())
if not hdr then
return self:ret(false, "can't create report timer : " .. err)
end
return self:ret(true, "created report timer")
end
function bunkernet:log_default()
-- Check if BunkerNet is activated
local check, err = utils.has_variable("USE_BUNKERNET", "yes")
if check == nil then
return false, "error while checking variable USE_BUNKERNET (" .. err .. ")"
end
if not check then
return true, "bunkernet not enabled"
end
-- Check if default server is disabled
local check, err = utils.get_variable("DISABLE_DEFAULT_SERVER", false)
if check == nil then
return false, "error while getting variable DISABLE_DEFAULT_SERVER (" .. err .. ")"
end
if check ~= "yes" then
return true, "default server not disabled"
end
-- Call log method
return self:log(true)
end
function bunkernet:request(method, url, data)
local httpc, err = http.new()
if not httpc then
return false, "can't instantiate http object : " .. err, nil, nil
@ -106,7 +159,7 @@ function _M:request(method, url, data)
for k, v in pairs(data) do
all_data[k] = v
end
local res, err = httpc:request_uri(self.server .. url, {
local res, err = httpc:request_uri(self.variables["BUNKERNET_SERVER"] .. url, {
method = method,
body = cjson.encode(all_data),
headers = {
@ -128,11 +181,11 @@ function _M:request(method, url, data)
return true, "success", res.status, ret
end
function _M:ping()
function bunkernet:ping()
return self:request("GET", "/ping", {})
end
function _M:report(ip, reason, method, url, headers)
function bunkernet:report(ip, reason, method, url, headers)
local data = {
ip = ip,
reason = reason,
@ -143,107 +196,4 @@ function _M:report(ip, reason, method, url, headers)
return self:request("POST", "/report", data)
end
function _M:log(bypass_use_bunkernet)
if not bypass_use_bunkernet then
-- Check if BunkerNet is activated
local use_bunkernet = utils.get_variable("USE_BUNKERNET")
if use_bunkernet ~= "yes" then
return true, "bunkernet not activated"
end
end
-- Check if BunkerNet ID is generated
if not self.id then
return true, "bunkernet ID is not generated"
end
-- Check if IP has been blocked
local reason = utils.get_reason()
if not reason then
return true, "ip is not blocked"
end
if reason == "bunkernet" then
return true, "skipping report because the reason is bunkernet"
end
-- Check if IP is global
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
return false, "error while checking if IP is global " .. err
end
if not is_global then
return true, "IP is not global"
end
-- Only report if it hasn't been reported for the same reason recently
--local reported = datastore:get("plugin_bunkernet_cache_" .. ngx.var.remote_addr .. reason)
--if reported then
--return true, "ip already reported recently"
--end
local function report_callback(premature, obj, ip, reason, method, url, headers)
local ok, err, status, data = obj:report(ip, reason, method, url, headers)
if status == 429 then
logger.log(ngx.WARN, "BUNKERNET", "BunkerNet API is rate limiting us")
elseif not ok then
logger.log(ngx.ERR, "BUNKERNET", "Can't report IP : " .. err)
else
logger.log(ngx.NOTICE, "BUNKERNET", "Successfully reported IP " .. ip .. " (reason : " .. reason .. ")")
--local ok, err = datastore:set("plugin_bunkernet_cache_" .. ip .. reason, true, 3600)
--if not ok then
--logger.log(ngx.ERR, "BUNKERNET", "Can't store cached report : " .. err)
--end
end
end
local hdr, err = ngx.timer.at(0, report_callback, self, ngx.var.remote_addr, reason, ngx.var.request_method,
ngx.var.request_uri, ngx.req.get_headers())
if not hdr then
return false, "can't create report timer : " .. err
end
return true, "created report timer"
end
function _M:log_default()
-- Check if bunkernet is activated
local check, err = utils.has_variable("USE_BUNKERNET", "yes")
if check == nil then
return false, "error while checking variable USE_BUNKERNET (" .. err .. ")"
end
if not check then
return true, "bunkernet not enabled"
end
-- Check if default server is disabled
local check, err = utils.get_variable("DISABLE_DEFAULT_SERVER", false)
if check == nil then
return false, "error while getting variable DISABLE_DEFAULT_SERVER (" .. err .. ")"
end
if check ~= "yes" then
return true, "default server not disabled"
end
-- Call log method
return self:log(true)
end
function _M:access()
local use_bunkernet = utils.get_variable("USE_BUNKERNET")
if use_bunkernet ~= "yes" then
return true, "bunkernet not activated", false, nil
end
-- Check if BunkerNet ID is generated
if not self.id then
return true, "bunkernet ID is not generated"
end
local data, err = datastore:get("plugin_bunkernet_db")
if not data then
return false, "can't get bunkernet db : " .. err, false, nil
end
local db = cjson.decode(data)
for index, value in ipairs(db.ip) do
if value == ngx.var.remote_addr then
return true, "ip is in database", true, utils.get_deny_status()
end
end
return true, "ip is not in database", false, nil
end
function _M:api()
return false, nil, nil
end
return _M
return bunkernet

View File

@ -1,4 +0,0 @@
local _M = {}
local mlcache = require "resty.mlcache"

View File

@ -1,22 +0,0 @@
local class = require "bunkerweb.middleclass"
local logger = require "bunkerweb.logger"
local settings = require "bunkerweb.settings"
local plugin = class("plugin")
function plugin:initialize(name, fill_settings)
self.name = name
self.logger = logger:new(name)
self.settings = {}
for i, setting in ipairs(fill_settings) do
local value, err = settings:get(setting)
if not value then
local err = string.format("can't get setting %s : %s", setting, err)
self.logger:log(ngx.ERR, err)
return false, err
end
self.settings[setting] = value
end
return true, nil
end
return plugin

View File

@ -1,30 +1,25 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cors = class("cors", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
function cors:new()
-- Call parent new
local ok, err = plugin.new(self, "cors")
if not ok then
return false, err
end
end
function _M:access()
-- Check if access is needed
local cors, err = utils.get_variable("USE_CORS")
if cors == nil then
return false, err, nil, nil
function cors:header()
-- Check if header is needed
if self.variables["USE_CORS"] ~= "yes" then
return self:ret(true, "service doesn't use CORS")
end
if cors == "no" then
return true, "CORS not activated", nil, nil
end
-- Check if method is OPTIONS
if ngx.var.request_method ~= "OPTIONS" then
return true, "method is not OPTIONS", nil, nil
return self:ret(true, "method is not OPTIONS")
end
-- Add headers
local cors_headers = {
["CORS_MAX_AGE"] = "Access-Control-Max-Age",
@ -32,9 +27,7 @@ function _M:access()
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers"
}
for variable, header in pairs(cors_headers) do
local value, err = utils.get_variable(variable)
if value == nil then
logger.log(ngx.ERR, "CORS", "can't get " .. variable .. " from datastore : " .. err)
local value = self.variables[variable]
elseif value ~= "" then
ngx.header[header] = value
end
@ -43,8 +36,7 @@ function _M:access()
ngx.header["Content-Length"] = "0"
-- Send CORS policy with a 204 (no content) status
return true, "sent CORS policy", true, ngx.HTTP_NO_CONTENT
return return self:ret(true, "sent CORS policy", ngx.HTTP_NO_CONTENT)
end
return _M
return cors

View File

@ -1,74 +1,85 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local country = class("country", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
end
function _M:access()
-- Get variables
local whitelist, err = utils.get_variable("WHITELIST_COUNTRY")
if whitelist == nil then
function country:new()
-- Call parent new
local ok, err = plugin.new(self, "country")
if not ok then
return false, err
end
local blacklist, err = utils.get_variable("BLACKLIST_COUNTRY")
if blacklist == nil then
return false, err
end
-- Don't go further if nothing is enabled
if whitelist == "" and blacklist == "" then
return true, "country not activated"
end
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
cachestore:new(use_redis)
return true, "success"
end
function country:access()
-- Don't go further if nothing is enabled
if self.variables["WHITELIST_COUNTRY"] == "" and self.variables["BLACKLIST_COUNTRY"] == "" then
return self:ret(true, "country not activated")
end
-- Check if IP is in cache
local data, err = self:is_in_cache(ngx.var.remote_addr)
if data then
if data.result == "ok" then
return true, "client IP " .. ngx.var.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")", nil, nil
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")")
end
return true, "client IP " .. ngx.var.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")", true, utils.get_deny_status()
end
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")", utils.get_deny_status()
end
-- Don't go further if IP is not global
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
logger.log(ngx.ERR, "COUNTRY", "error while checking if ip is global : " .. err)
return self:ret(false, "error while checking if ip is global : " .. err)
elseif not is_global then
self:add_to_cache(ngx.var.remote_addr, "unknown", "ok")
return true, "client IP " .. ngx.var.remote_addr .. " is not global, skipping check", nil, nil
local ok, err = self:add_to_cache(ngx.var.remote_addr, "unknown", "ok")
if not ok then
return self:ret(false, "error while adding ip to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is not global, skipping check")
end
-- Get the country of client
local country, err = utils.get_country(ngx.var.remote_addr)
if not country then
return false, "can't get country of client IP " .. ngx.var.remote_addr .. " : " .. err, nil, nil
return self:ret(false, "can't get country of client IP " .. ngx.var.remote_addr .. " : " .. err)
end
-- Process whitelist first
if whitelist ~= "" then
for wh_country in whitelist:gmatch("%S+") do
if self.variables["WHITELIST_COUNTRY"] ~= "" then
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch("%S+") do
if wh_country == country then
self:add_to_cache(ngx.var.remote_addr, country, "ok")
return true, "client IP " .. ngx.var.remote_addr .. " is whitelisted (country = " .. country .. ")", nil, nil
local ok, err = self:add_to_cache(ngx.var.remote_addr, country, "ok")
if not then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is whitelisted (country = " .. country .. ")")
end
end
self:add_to_cache(ngx.var.remote_addr, country, "ko")
return true, "client IP " .. ngx.var.remote_addr .. " is not whitelisted (country = " .. country .. ")", true, utils.get_deny_status()
local ok, err = self:add_to_cache(ngx.var.remote_addr, country, "ko")
if not then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is not whitelisted (country = " .. country .. ")", utils.get_deny_status())
end
-- And then blacklist
if blacklist ~= "" then
for bl_country in blacklist:gmatch("%S+") do
if self.variables["BLACKLIST_COUNTRY"] ~= "" then
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch("%S+") do
if bl_country == country then
self:add_to_cache(ngx.var.remote_addr, country, "ko")
return true, "client IP " .. ngx.var.remote_addr .. " is blacklisted (country = " .. country .. ")", true, utils.get_deny_status()
local ok, err = self:add_to_cache(ngx.var.remote_addr, country, "ko")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is blacklisted (country = " .. country .. ")", true, utils.get_deny_status())
end
end
end
@ -76,37 +87,29 @@ function _M:access()
-- Country IP is not in blacklist
local ok, err = self:add_to_cache(ngx.var.remote_addr, country, "ok")
if not ok then
return false, "error while caching IP " .. ngx.var.remote_addr .. " : " .. err, false, nil
return self:ret(false, "error while caching IP " .. ngx.var.remote_addr .. " : " .. err)
end
return true, "client IP " .. ngx.var.remote_addr .. " is not blacklisted (country = " .. country .. ")", nil, nil
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is not blacklisted (country = " .. country .. ")")
end
function _M:preread()
function country:preread()
return self:access()
end
function _M:is_in_cache(ip)
local data, err = datastore:get("plugin_country_cache_" .. ip)
if not data then
if err ~= "not found" then
logger.log(ngx.ERR, "COUNTRY", "Error while accessing cache : " .. err)
end
return false, err
end
return cjson.decode(data), "success"
function country:is_in_cache(ip)
local ok, data = cachestore:get("plugin_country_" .. ip)
if not ok then then
return false, data
end
return true, cjson.decode(data)
end
function _M:add_to_cache(ip, country, result)
local data = {
country = country,
result = result
}
local ok, err = datastore:set("plugin_country_cache_" .. ip, cjson.encode(data), 3600)
if not ok then
logger.log(ngx.ERR, "COUNTRY", "Error while adding ip to cache : " .. err)
function country:add_to_cache(ip, country, result)
local ok, err = cachestore:set("plugin_country_" .. ip, cjson.encode({country = country, result = result}))
if not ok then then
return false, err
end
return true, "success"
end
return true
end
return _M
return country

View File

@ -1,112 +1,100 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
local dnsbl = class("country", plugin)
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_DNSBL", "yes")
if init_needed == nil then
return false, "can't check USE_DNS variable : " .. err
end
if not init_needed then
return true, "no service uses Blacklist, skipping init"
end
-- Read DNSBL list
local str_dnsbls, err = utils.get_variable("DNSBL_LIST", false)
if not str_dnsbls then
return false, "can't get DNSBL_LIST variable : " .. err
end
local dnsbls = {}
local i = 0
for dnsbl in str_dnsbls:gmatch("%S+") do
table.insert(dnsbls, dnsbl)
i = i + 1
end
-- Load it into datastore
local ok, err = datastore:set("plugin_dnsbl_list", cjson.encode(dnsbls))
local dnsbl:new()
-- Call parent new
local ok, err = plugin.new(self, "country")
if not ok then
return false, "can't store DNSBL list into datastore : " .. err
end
return true, "successfully loaded " .. tostring(i) .. " DNSBL server(s)"
end
function _M:access()
-- Check if access is needed
local access_needed, err = utils.get_variable("USE_DNSBL")
if access_needed == nil then
return false, err
end
if access_needed ~= "yes" then
return true, "DNSBL not activated"
end
-- Instantiate cachestore
cachestore:new(use_redis)
return true, "success"
end
function dnsbl:access()
-- Check if access is needed
if self.variables["USE_DNSBL"] ~= "yes" then
return self:ret(true, "dnsbl not activated")
end
if self.variables["DNSBL_LIST"] == "" then
return self:ret(true, "dnsbl list is empty")
end
-- Check if IP is in cache
local dnsbl, err = self:is_in_cache(ngx.var.remote_addr)
if dnsbl then
if dnsbl == "ok" then
return true, "client IP " .. ngx.var.remote_addr .. " is in DNSBL cache (not blacklisted)", nil, nil
local cache, err = self:is_in_cache(ngx.var.remote_addr)
if not cache and err ~= "success" then
return self:ret(false, "error while checking cache : " .. err)
elseif cache then
if cache == "ok" then
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is in DNSBL cache (not blacklisted)")
end
return true, "client IP " .. ngx.var.remote_addr .. " is in DNSBL cache (server = " .. dnsbl .. ")", true, utils.get_deny_status()
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is in DNSBL cache (server = " .. cache .. ")", utils.get_deny_status())
end
-- Don't go further if IP is not global
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
return false, "can't check if client IP is global : " .. err, nil, nil
return self:ret(false, "can't check if client IP is global : " .. err)
end
if not utils.ip_is_global(ngx.var.remote_addr) then
self:add_to_cache(ngx.var.remote_addr, "ok")
return true, "client IP is not global, skipping DNSBL check", nil, nil
if not is_global then
local ok, err self:add_to_cache(ngx.var.remote_addr, "ok")
if not ok then
return self:ret(false, "error while adding element to cache")
end
return self:ret(true, "client IP is not global, skipping DNSBL check")
end
-- Get list
local data, err = datastore:get("plugin_dnsbl_list")
if not data then
return false, "can't get DNSBL list : " .. err, false, nil
end
local ok, dnsbls = pcall(cjson.decode, data)
if not ok then
return false, "error while decoding DNSBL list : " .. dnsbls, false, nil
end
-- Loop on dnsbl list
for i, dnsbl in ipairs(dnsbls) do
local result, err = self:is_in_dnsbl(dnsbl, ngx.var.remote_addr)
-- Loop on DNSBL list
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
local result, err = self:is_in_dnsbl(server)
if result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
end
if result then
self:add_to_cache(ngx.var.remote_addr, dnsbl)
return ret, "client IP " .. ngx.var.remote_addr .. " is in DNSBL (server = " .. dnsbl .. ")", true, utils.get_deny_status()
local ok, err self:add_to_cache(ngx.var.remote_addr, server)
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
return self:ret(true, "IP is blacklisted by " .. server, utils.get_deny_status())
end
end
-- IP is not in DNSBL
local ok, err = self:add_to_cache(ngx.var.remote_addr, "ok")
if not ok then
return false, "IP is not in DNSBL (error = " .. err .. ")", false, nil
return self:ret(false, "IP is not in DNSBL (error = " .. err .. ")")
end
return true, "IP is not in DNSBL", false, nil
return self:ret(true, "IP is not in DNSBL", false, nil)
end
function _M:preread()
function dnsbl:preread()
return self:access()
end
function _M:is_in_dnsbl(dnsbl, ip)
local request = resolver.arpa_str(ip) .. "." .. dnsbl
function dnsl:is_in_cache(ip)
local ok, data = cachestore:get("plugin_dnsbl_" .. ip)
if not ok then then
return false, data
end
return true, data
end
function dnsbl:add_to_cache(ip, value)
local ok, err = cachestore:set("plugin_dnsbl_" .. ip, value)
if not ok then then
return false, err
end
return true
end
function dnsbl:is_in_dnsbl(server)
local request = resolver.arpa_str(ip) .. "." .. server
local ips, err = utils.get_ips(request)
if not ips then
logger.log(ngx.ERR, "DNSBL", "Error while asking DNSBL server " .. dnsbl .. " : " .. err)
return false, err
return nil, err
end
for i, ip in ipairs(ips) do
local a, b, c, d = ip:match("([%d]+).([%d]+).([%d]+).([%d]+)")
@ -117,24 +105,4 @@ function _M:is_in_dnsbl(dnsbl, ip)
return false, "success"
end
function _M:is_in_cache(ip)
local kind, err = datastore:get("plugin_dnsbl_cache_" .. ip)
if not kind then
if err ~= "not found" then
logger.log(ngx.ERR, "DNSBL", "Error while accessing cache : " .. err)
end
return false, err
end
return kind, "success"
end
function _M:add_to_cache(ip, kind)
local ok, err = datastore:set("plugin_dnsbl_cache_" .. ip, kind, 3600)
if not ok then
logger.log(ngx.ERR, "DNSBL", "Error while adding ip to cache : " .. err)
return false, err
end
return true, "success"
end
return _M
return dnsbl