share common objects during the phase and add threading to DNSBL and reverse scan

This commit is contained in:
florian 2023-05-21 13:46:46 +02:00
parent b19ebbe6a8
commit eea6d32cd3
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
22 changed files with 329 additions and 191 deletions

3
TODO
View File

@ -1,6 +1,5 @@
- Ansible
- Vagrant
- Plugins
- sessions helpers in utils
- sessions security : check IP address, check UA, ...
- Find a way to do rdns in background
- fix db warnings (Got an error reading communication packets)

View File

@ -253,14 +253,19 @@ That kind of security is implemented but not enabled by default in BunkerWeb and
Here is the list of related settings :
| Setting | Default | Description |
| :--------------------------------------------------------: | :----------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `USE_ANTIBOT` | `no` | Accepted values to enable Antibot feature : `cookie`, `javascript`, `captcha`, `hcaptcha` and `recaptcha`. |
| `ANTIBOT_URI` | `/challenge` | URI that clients will be redirected to in order to solve the challenge. Be sure that it isn't used in your web application. |
| `ANTIBOT_SESSION_SECRET` | `random` | The secret used to encrypt cookies when using Antibot. The special value `random` will generate one for you. Be sure to set it when you use a clustered integration (32 chars). |
| `ANTIBOT_HCAPTCHA_SITEKEY` and `ANTIBOT_RECAPTCHA_SITEKEY` | | The Sitekey value to use when `USE_ANTIBOT` is set to `hcaptcha` or `recaptcha`. |
| `ANTIBOT_HCAPTCHA_SECRET` and `ANTIBOT_RECAPTCHA_SECRET` | | The Secret value to use when `USE_ANTIBOT` is set to `hcaptcha` or `recaptcha`. |
| `ANTIBOT_RECAPTCHA_SCORE` | `0.7` | The minimum score that clients must have when `USE_ANTIBOT` is set to `recaptcha`. |
| Setting | Default | Context |Multiple| Description |
|---------------------------|------------|---------|--------|------------------------------------------------------------------------------------------------------------------------------|
|`USE_ANTIBOT` |`no` |multisite|no |Activate antibot feature. |
|`ANTIBOT_URI` |`/challenge`|multisite|no |Unused URI that clients will be redirected to to solve the challenge. |
|`ANTIBOT_RECAPTCHA_SCORE` |`0.7` |multisite|no |Minimum score required for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SITEKEY`| |multisite|no |Sitekey for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
|`ANTIBOT_HCAPTCHA_SECRET` | |multisite|no |Secret for hCaptcha challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
Please note that antibot feature is using a cookie to maintain a session with clients. If you are using BunkerWeb in a clustered environment, you will need to set the `SESSIONS_SECRET` and `SESSIONS_NAME` settings to another value than the default one (which is `random`). You will find more info about sessions [here](settings.md#sessions).
## Blacklisting, whitelisting and greylisting

View File

@ -498,6 +498,8 @@ Management of session used by other plugins.
|`SESSIONS_IDLING_TIMEOUT` |`1800` |global |no |Maximum time (in seconds) of inactivity before the session is invalidated. |
|`SESSIONS_ROLLING_TIMEOUT` |`3600` |global |no |Maximum time (in seconds) before a session must be renewed. |
|`SESSIONS_ABSOLUTE_TIMEOUT`|`86400` |global |no |Maximum time (in seconds) before a session is destroyed. |
|`SESSIONS_CHECK_IP` |`yes` |global |no |Destroy session if IP address is different than original one. |
|`SESSIONS_CHECK_USER_AGENT`|`yes` |global |no |Destroy session if User-Agent is different than original one. |
### UI

View File

@ -1,4 +1,5 @@
local mlcache = require "resty.mlcache"
local clusterstore = require "bunkerweb.clusterstore"
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
@ -41,17 +42,22 @@ if not cache then
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
end
function cachestore:initialize(use_redis)
function cachestore:initialize(use_redis, new_cs)
self.cache = cache
self.use_redis = (use_redis and utils.is_cosocket_available()) or false
self.use_redis = use_redis or false
self.logger = module_logger
if new_cs then
self.clusterstore = clusterstore:new(false)
else
self.clusterstore = utils.get_ctx_obj("clusterstore")
end
end
function cachestore:get(key)
local callback = function(key)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local clusterstore = require "bunkerweb.clusterstore":new(false)
local ok, err, reused = clusterstore:connect()
if not ok then
return nil, "can't connect to redis : " .. err, nil
end
@ -88,7 +94,7 @@ function cachestore:get(key)
return nil, nil, -1
end
local value, err, hit_level
if self.use_redis then
if self.use_redis and utils.is_cosocket_available() then
value, err, hit_level = self.cache:get(key, nil, callback, key)
else
value, err, hit_level = self.cache:get(key, nil, callback_no_miss)
@ -101,7 +107,7 @@ function cachestore:get(key)
end
function cachestore:set(key, value, ex)
if self.use_redis then
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self:set_redis(key, value, ex)
if not ok then
self.logger:log(ngx.ERR, err)
@ -121,24 +127,23 @@ end
function cachestore:set_redis(key, value, ex)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local ok, err, reused = self.clusterstore:connect()
if not ok then
return false, "can't connect to redis : " .. err
end
-- Set value with ttl
local default_ex = ex or 30
local ok, err = clusterstore:call("set", key, value, "EX", default_ex)
local ok, err = self.clusterstore:call("set", key, value, "EX", default_ex)
if err then
clusterstore:close()
self.clusterstore:close()
return false, "SET failed : " .. err
end
clusterstore:close()
self.clusterstore:close()
return true
end
function cachestore:delete(key, value, ex)
if self.use_redis then
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self.del_redis(key)
if not ok then
self.logger:log(ngx.ERR, err)
@ -153,18 +158,17 @@ end
function cachestore:del_redis(key)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local ok, err = self.clusterstore:connect()
if not ok then
return false, "can't connect to redis : " .. err
end
-- Set value with ttl
local ok, err = clusterstore:del(key)
local ok, err = self.clusterstore:del(key)
if err then
clusterstore:close()
self.clusterstore:close()
return false, "DEL failed : " .. err
end
clusterstore:close()
self.clusterstore:close()
return true
end

View File

@ -5,7 +5,7 @@ local redis = require "resty.redis"
local clusterstore = class("clusterstore")
function clusterstore:initialize()
function clusterstore:initialize(pool)
-- Instantiate logger
self.logger = logger:new("CLUSTERSTORE")
-- Get variables
@ -29,12 +29,13 @@ function clusterstore:initialize()
end
-- Don't instantiate a redis object for now
self.redis_client = nil
self.pool = pool == nil or pool
end
function clusterstore:connect()
-- Check if we are already connected
if self.redis_client ~= nil then
return true, "already connected"
if self.redis_client then
return true, "already connected", self.redis_client:get_reused_times()
end
-- Instantiate object
local redis_client, err = redis:new()
@ -42,42 +43,50 @@ function clusterstore:connect()
return false, err
end
-- Set timeouts
redis_client:set_timeouts(tonumber(self.variables["REDIS_TIMEOUT"]), tonumber(self.variables["REDIS_TIMEOUT"]),
tonumber(self.variables["REDIS_TIMEOUT"]))
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
-- Connect
local options = {
ssl = self.variables["REDIS_SSL"] == "yes",
pool = "bw",
pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
}
if self.pool then
options.pool = "bw-redis"
options.pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
end
local ok, err = redis_client:connect(self.variables["REDIS_HOST"], tonumber(self.variables["REDIS_PORT"]), options)
if not ok then
return false, err
end
-- Save client
self.redis_client = redis_client
-- Select database if needed
local times, err = redis_client:get_reused_times()
local times, err = self.redis_client:get_reused_times()
if err then
self:close()
return false, err
end
if times == 0 then
local select, err = redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
local select, err = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
if err then
self:close()
return false, err
end
end
return true, "success"
return true, "success", times
end
function clusterstore:close()
if self.redis_client then
-- Equivalent to close but keep a pool of connections
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]),
tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
self.redis_client = nil
if self.pool then
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]), tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
self.redis_client = nil
if not ok then
require "bunkerweb.logger":new("clusterstore-close"):log(ngx.ERR, err)
end
return ok, err
end
-- Close
local ok, err = self.redis_client:close()
self.redis_client.redis_client = nil
return ok, err
end
return false, "not connected"

View File

@ -146,45 +146,52 @@ helpers.call_plugin = function(plugin, method)
end
helpers.fill_ctx = function()
-- Check if ctx is already filled
if ngx.ctx.bw then
return true, "already filled"
end
-- Return errors as table
local errors = {}
-- Instantiate bw table
local data = {}
-- Common vars
data.kind = "http"
if ngx.shared.datastore_stream then
data.kind = "stream"
local use_redis = nil
-- Check if ctx is already filled
if not ngx.ctx.bw then
-- Instantiate bw table
local data = {}
-- Common vars
data.kind = "http"
if ngx.shared.datastore_stream then
data.kind = "stream"
end
data.remote_addr = ngx.var.remote_addr
data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type
data.http_origin = ngx.var.http_origin
-- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then
table.insert(errors, "can't check if IP is global : " .. err)
else
data.ip_is_global = ip_is_global
end
-- IP data : v4 / v6
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
-- Misc info
data.integration = utils.get_integration()
data.version = utils.get_version()
-- Common objects
use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
table.insert(errors, "can't get variable from datastore : " .. err)
end
-- Fill ctx
ngx.ctx.bw = data
end
data.remote_addr = ngx.var.remote_addr
data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type
data.http_origin = ngx.var.http_origin
-- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then
table.insert(errors, "can't check if IP is global : " .. err)
else
data.ip_is_global = ip_is_global
end
-- IP data : v4 / v6
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
-- Misc info
data.integration = utils.get_integration()
data.version = utils.get_version()
-- Plugins
data.plugins = {}
-- Fill ctx
ngx.ctx.bw = data
-- Always create new objects for current phases in case of cosockets
ngx.ctx.bw.datastore = require "bunkerweb.datastore":new()
ngx.ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ngx.ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors
end

View File

@ -1,25 +1,15 @@
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local clusterstore = require "bunkerweb.clusterstore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
function plugin:initialize(id)
-- Store default values
-- Store common, values
self.id = id
self.variables = {}
-- Instantiate objects
self.logger = logger:new(id)
self.datastore = datastore:new()
-- Get metadata
local encoded_metadata, err = self.datastore:get("plugin_" .. id)
if not encoded_metadata then
self.logger:log(ngx.ERR, err)
return
end
-- Store variables
local metadata = cjson.decode(encoded_metadata)
local multisite = false
local current_phase = ngx.get_phase()
for i, check_phase in ipairs({ "set", "access", "content", "header", "log", "preread", "log_stream", "log_default" }) do
@ -29,6 +19,31 @@ function plugin:initialize(id)
end
end
self.is_request = multisite
-- Store common objets
self.logger = logger:new(self.id)
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
if self.is_request then
self.datastore = utils.get_ctx_obj("datastore") or datastore:new()
self.cachestore = utils.get_ctx_obj("cachestore") or cachestore:new(use_redis == "yes", true)
self.clusterstore = utils.get_ctx_obj("clusterstore") or clusterstore:new(false)
else
self.datastore = datastore:new()
self.cachestore = cachestore:new(use_redis == "yes", true)
self.clusterstore = clusterstore:new(false)
end
-- Get metadata
local encoded_metadata, err = self.datastore:get("plugin_" .. id)
if not encoded_metadata then
self.logger:log(ngx.ERR, err)
return
end
-- Store variables
self.variables = {}
local metadata = cjson.decode(encoded_metadata)
for k, v in pairs(metadata.settings) do
local value, err = utils.get_variable(k, v.context == "multisite" and multisite)
if value == nil then

View File

@ -680,7 +680,7 @@ utils.new_cachestore = function()
use_redis = use_redis == "yes"
end
-- Instantiate
return require "bunkerweb.cachestore":new(use_redis)
return require "bunkerweb.cachestore":new(use_redis, true)
end
utils.regex_match = function(str, regex, options)
@ -725,4 +725,20 @@ utils.is_cosocket_available = function()
return false
end
utils.kill_all_threads = function(threads)
for i, thread in ipairs(threads) do
local ok, err = ngx.thread.kill(thread)
if not ok then
logger:log(ngx.ERR, "error while killing thread : " .. err)
end
end
end
utils.get_ctx_obj = function(obj)
if ngx.ctx and ngx.ctx.bw then
return ngx.ctx.bw[obj]
end
return nil
end
return utils

View File

@ -49,6 +49,10 @@ lua_shared_dict cachestore {{ CACHESTORE_MEMORY_SIZE }};
lua_shared_dict cachestore_ipc {{ CACHESTORE_IPC_MEMORY_SIZE }};
lua_shared_dict cachestore_miss {{ CACHESTORE_MISS_MEMORY_SIZE }};
lua_shared_dict cachestore_locks {{ CACHESTORE_LOCKS_MEMORY_SIZE }};
# only show LUA socket errors at info/debug
{% if LOG_LEVEL != "info" and LOG_LEVEL != "debug" %}
lua_socket_log_errors off;
{% endif %}
# LUA init block
include /etc/nginx/init-lua.conf;

View File

@ -11,13 +11,6 @@ local logger = clogger:new("INIT")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init phase started")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new()
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin", "^variable_", "^api_", "^misc_"}
@ -50,6 +43,13 @@ for line in io.lines("/etc/nginx/variables.env") do
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")

View File

@ -23,7 +23,7 @@ local ready_work = function(premature)
end
-- Instantiate lock
local lock = require "resty.lock":new("worker_lock")
local lock = require "resty.lock":new("worker_lock", {timeout = 10})
if not lock then
logger:log(ngx.ERR, "lock:new() failed : " .. err)
return

View File

@ -33,6 +33,10 @@ lua_shared_dict cachestore_stream {{ CACHESTORE_MEMORY_SIZE }};
lua_shared_dict cachestore_ipc_stream {{ CACHESTORE_IPC_MEMORY_SIZE }};
lua_shared_dict cachestore_miss_stream {{ CACHESTORE_MISS_MEMORY_SIZE }};
lua_shared_dict cachestore_locks_stream {{ CACHESTORE_LOCKS_MEMORY_SIZE }};
# only show LUA socket errors at info/debug
{% if LOG_LEVEL != "info" and LOG_LEVEL != "debug" %}
lua_socket_log_errors off;
{% endif %}
# LUA init block
include /etc/nginx/init-stream-lua.conf;

View File

@ -7,12 +7,6 @@ local badbehavior = class("badbehavior", plugin)
function badbehavior:initialize()
-- Call parent initialize
plugin.initialize(self, "badbehavior")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
end
function badbehavior:log()
@ -146,7 +140,7 @@ end
function badbehavior.redis_increase(ip, count_time, ban_time)
-- Instantiate objects
local clusterstore = require "bunkerweb.clusterstore":new()
local clusterstore = require "bunkerweb.clusterstore":new(false)
-- Our LUA script to execute on redis
local redis_script = [[
local ret_incr = redis.pcall("INCR", KEYS[1])
@ -188,7 +182,7 @@ end
function badbehavior.redis_decrease(ip, count_time)
-- Instantiate objects
local clusterstore = require "bunkerweb.clusterstore":new()
local clusterstore = require "bunkerweb.clusterstore":new(false)
-- Our LUA script to execute on redis
local redis_script = [[
local ret_decr = redis.pcall("DECR", KEYS[1])

View File

@ -11,12 +11,6 @@ local blacklist = class("blacklist", plugin)
function blacklist:initialize()
-- Call parent initialize
plugin.initialize(self, "blacklist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_blacklist_lists")
@ -47,8 +41,6 @@ function blacklist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function blacklist:is_needed()

View File

@ -9,13 +9,6 @@ local country = class("country", plugin)
function country:initialize()
-- Call parent initialize
plugin.initialize(self, "country")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function country:access()

View File

@ -10,13 +10,6 @@ local dnsbl = class("dnsbl", plugin)
function dnsbl:initialize()
-- Call parent initialize
plugin.initialize(self, "dnsbl")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function dnsbl:init_worker()
@ -32,9 +25,18 @@ function dnsbl:init_worker()
return self:ret(true, "no service uses DNSBL, skipping init_worker")
end
-- Loop on DNSBL list
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
local result, err = self:is_in_dnsbl("127.0.0.2", server)
if result == nil then
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, "127.0.0.2", server)
threads[server] = thread
end
-- Wait for threads
for dnsbl, thread in pairs(threads) do
local ok, result, server, err = ngx.thread.wait(thread)
if not ok then
self.logger:log(ngx.ERR, "error while waiting thread of " .. dnsbl .. " check : " .. result)
elseif result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
elseif not result then
self.logger:log(ngx.ERR, "dnsbl check for " .. server .. " failed")
@ -69,25 +71,74 @@ function dnsbl:access()
utils.get_deny_status())
end
-- Loop on DNSBL list
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
local result, err = self:is_in_dnsbl(ngx.ctx.bw.remote_addr, server)
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, ngx.ctx.bw.remote_addr, server)
threads[server] = thread
end
-- Wait for threads
local ret_threads = nil
local ret_err = nil
local ret_server = nil
while true do
-- Compute threads to wait
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No server reported IP
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, result, server, err = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. result
break
end
-- Remove thread from list
threads[server] = nil
-- DNS error
if result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
end
-- IP is in DNSBL
if result then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, server)
ret_threads = true
ret_err = "IP is blacklisted by " .. server
ret_server = server
break
end
end
if ret_threads ~= nil then
-- Kill other threads
if #threads > 0 then
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Blacklisted by a server : add to cache and deny access
if ret_threads then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, ret_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())
return self:ret(true, "IP is blacklisted by " .. ret_server, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- IP is not in DNSBL
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, "ok")
if not ok then
return self:ret(false, "IP is not in DNSBL (error = " .. err .. ")")
end
return self:ret(true, "IP is not in DNSBL", false, nil)
return self:ret(true, "IP is not in DNSBL")
end
function dnsbl:preread()
@ -114,14 +165,15 @@ function dnsbl:is_in_dnsbl(ip, server)
local request = resolver.arpa_str(ip):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "") .. "." .. server
local ips, err = utils.get_ips(request, false)
if not ips then
return nil, err
return nil, server, err
end
for i, ip in ipairs(ips) do
if ip:find("^127%.0%.0%.") then
return true, "success"
ngx.sleep(5)
return true, server
end
end
return false, "success"
return false, server
end
return dnsbl

View File

@ -10,12 +10,6 @@ local greylist = class("greylist", plugin)
function greylist:initialize()
-- Call parent initialize
plugin.initialize(self, "greylist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_greylist_lists")
@ -41,8 +35,6 @@ function greylist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function greylist:is_needed()

View File

@ -10,13 +10,6 @@ local limit = class("limit", plugin)
function limit:initialize()
-- Call parent initialize
plugin.initialize(self, "limit")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.clusterstore = clusterstore:new()
-- Load rules if needed
if ngx.get_phase() ~= "init" and self:is_needed() then
-- Get all rules from datastore

View File

@ -9,7 +9,6 @@ local redis = class("redis", plugin)
function redis:initialize()
-- Call parent initialize
plugin.initialize(self, "redis")
self.clusterstore = clusterstore:new()
end
function redis:init_worker()

View File

@ -2,19 +2,13 @@ local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local reversescan = class("reversescan", plugin)
function reversescan:initialize()
-- Call parent initialize
plugin.initialize(self, "reversescan")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function reversescan:access()
@ -23,31 +17,103 @@ function reversescan:access()
return self:ret(true, "reverse scan not activated")
end
-- Loop on ports
local threads = {}
local ret_threads = nil
local ret_err = nil
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
-- Check if the scan is already cached
local ok, cached = self:is_in_cache(ngx.ctx.bw.remote_addr .. ":" .. port)
if not ok then
return self:ret(false, "error getting cache from datastore : " .. cached)
end
if cached == "open" then
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr,
utils.get_deny_status())
ret_threads = false
ret_err = "error getting info from cachestore : " .. cached
break
-- Deny access if port opened
elseif cached == "open" then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
break
-- Perform scan in a thread
elseif not cached then
-- Do the scan
local res = self:scan(ngx.ctx.bw.remote_addr, tonumber(port),
tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
-- Cache the result
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr .. ":" .. port, res)
if not ok then
return self:ret(false, "error updating cache from datastore : " .. err)
end
-- Deny request if port is open
if res == "open" then
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr,
utils.get_deny_status())
end
local thread = ngx.thread.spawn(self.scan, ngx.ctx.bw.remote_addr, tonumber(port), tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
threads[port] = thread
end
end
if ret_threads ~= nil then
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- Check results of threads
ret_threads = nil
ret_err = nil
local results = {}
while true do
-- Compute threads to wait
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No port opened
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. open
break
end
port = tostring(port)
-- Remove thread from list
threads[port] = nil
-- Add result to cache
local result = "close"
if open then
result = "open"
end
results[port] = result
-- Port is opened
if open then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
break
end
end
-- Kill running threads
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Cache results
for port, result in pairs(results) do
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr .. ":" .. port, result)
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
end
if ret_threads ~= nil then
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- No port opened
return self:ret(true, "no port open for IP " .. ngx.ctx.bw.remote_addr)
end
@ -56,15 +122,15 @@ function reversescan:preread()
return self:access()
end
function reversescan:scan(ip, port, timeout)
function reversescan.scan(ip, port, timeout)
local tcpsock = ngx.socket.tcp()
tcpsock:settimeout(timeout)
local ok, err = tcpsock:connect(ip, port)
tcpsock:close()
if not ok then
return "close"
return false, port
end
return "open"
return true, port
end
function reversescan:is_in_cache(ip_port)

View File

@ -103,7 +103,7 @@ function sessions:init()
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
pool = "bw",
pool = "bw-redis",
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
ssl = redis_vars["REDIS_SSL"] == "yes",
host = redis_vars["REDIS_HOST"],

View File

@ -12,12 +12,6 @@ local whitelist = class("whitelist", plugin)
function whitelist:initialize()
-- Call parent initialize
plugin.initialize(self, "whitelist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_whitelist_lists")
@ -43,8 +37,6 @@ function whitelist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function whitelist:is_needed()