refactor - badbehavior, blacklist, bunkernet, cache, cors, country and dnsbl
This commit is contained in:
parent
03ec271e21
commit
2075a5d4c2
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
local _M = {}
|
||||
|
||||
local mlcache = require "resty.mlcache"
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue