Add StyLua and luacheck to precommit config file and apply it

This commit is contained in:
Théophile Diot 2023-10-12 17:08:13 +02:00
parent cd1f87b9a2
commit 77bfe2697f
No known key found for this signature in database
GPG Key ID: 248FEA4BAE400D06
29 changed files with 1428 additions and 1220 deletions

2
.luacheckrc Normal file
View File

@ -0,0 +1,2 @@
globals = {"ngx", "delay", "unpack"}
ignore = {"411"}

View File

@ -26,6 +26,19 @@ repos:
- id: prettier
name: Prettier Code Formatter
- repo: https://github.com/JohnnyMorganz/StyLua
rev: 27e6b388796604181e810ef05c9fb15a9f7a7769 # frozen: v0.18.2
hooks:
- id: stylua-github
exclude: ^src/(bw/lua/middleclass.lua|common/core/antibot/captcha.lua)$
- repo: https://github.com/lunarmodules/luacheck
rev: ababb6d403d634eb74d2c541035e9ede966e710d # frozen: v1.1.1
hooks:
- id: luacheck
exclude: ^src/(bw/lua/middleclass.lua|common/core/antibot/captcha.lua)$
args: ["--std", "min", "--codes", "--ranges", "--no-cache"]
- repo: https://github.com/pycqa/flake8
rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6 # frozen: 6.1.0
hooks:

View File

@ -1,15 +1,15 @@
local class = require "middleclass"
local cjson = require "cjson"
local class = require "middleclass"
local datastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils"
local logger = require "bunkerweb.logger"
local cjson = require "cjson"
local upload = require "resty.upload"
local rsignal = require "resty.signal"
local process = require "ngx.process"
local logger = require "bunkerweb.logger"
local process = require "ngx.process"
local rsignal = require "resty.signal"
local upload = require "resty.upload"
local utils = require "bunkerweb.utils"
local api = class("api")
local api = class("api")
api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
function api:initialize()
self.datastore = datastore:new()
@ -26,6 +26,7 @@ function api:initialize()
end
end
-- luacheck: ignore 212
function api:log_cmd(cmd, status, stdout, stderr)
local level = ngx.NOTICE
local prefix = "success"
@ -33,7 +34,7 @@ function api:log_cmd(cmd, status, stdout, stderr)
level = ngx.ERR
prefix = "error"
end
self.logger:log(level, prefix .. " while running command " .. command)
self.logger:log(level, prefix .. " while running command " .. cmd)
self.logger:log(level, "stdout = " .. stdout)
self.logger:log(level, "stdout = " .. stderr)
end
@ -41,6 +42,7 @@ end
-- TODO : use this if we switch to OpenResty
function api:cmd(cmd)
-- Non-blocking command
-- luacheck: ignore 113
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, 10000)
self.logger:log_cmd(cmd, status, stdout, stderr)
-- Timeout
@ -51,6 +53,7 @@ function api:cmd(cmd)
return status == 0, reason, status
end
-- luacheck: ignore 212
function api:response(http_status, api_status, msg)
local resp = {}
resp["status"] = api_status
@ -101,6 +104,7 @@ api.global.POST["^/confs$"] = function(self)
form:set_timeout(1000)
local file = io.open(tmp, "w+")
while true do
-- luacheck: ignore 421
local typ, res, err = form:read()
if not typ then
file:close()
@ -117,9 +121,9 @@ api.global.POST["^/confs$"] = function(self)
file:close()
local cmds = {
"rm -rf " .. destination .. "/*",
"tar xzf " .. tmp .. " -C " .. destination
"tar xzf " .. tmp .. " -C " .. destination,
}
for i, cmd in ipairs(cmds) do
for _, cmd in ipairs(cmds) do
local status = os.execute(cmd)
if status ~= 0 then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
@ -176,17 +180,23 @@ end
api.global.GET["^/bans$"] = function(self)
local data = {}
for i, k in ipairs(self.datastore:keys()) do
for _, k in ipairs(self.datastore:keys()) do
if k:find("^bans_ip_") then
local reason, err = self.datastore:get(k)
if err then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error",
"can't access " .. k .. " from datastore : " .. reason)
return self:response(
ngx.HTTP_INTERNAL_SERVER_ERROR,
"error",
"can't access " .. k .. " from datastore : " .. reason
)
end
local ok, ttl = self.datastore:ttl(k)
if not ok then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error",
"can't access ttl " .. k .. " from datastore : " .. ttl)
return self:response(
ngx.HTTP_INTERNAL_SERVER_ERROR,
"error",
"can't access ttl " .. k .. " from datastore : " .. ttl
)
end
local ban = { ip = k:sub(9, #k), reason = reason, exp = math.floor(ttl) }
table.insert(data, ban)
@ -196,7 +206,7 @@ api.global.GET["^/bans$"] = function(self)
end
api.global.GET["^/variables$"] = function(self)
local variables, err = datastore:get('variables', true)
local variables, err = datastore:get("variables", true)
if not variables then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't access variables from datastore : " .. err)
end
@ -219,9 +229,9 @@ function api:do_api_call()
if status ~= ngx.HTTP_OK then
ret = false
end
if (#resp["msg"] == 0) then
if #resp["msg"] == 0 then
resp["msg"] = ""
elseif (type(resp["msg"]) == "table") then
elseif type(resp["msg"]) == "table" then
resp["data"] = resp["msg"]
resp["msg"] = resp["status"]
end
@ -231,10 +241,10 @@ function api:do_api_call()
end
local list, err = self.datastore:get("plugins", true)
if not list then
local status, resp = self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't list loaded plugins : " .. err)
local _, resp = self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't list loaded plugins : " .. err)
return false, resp["msg"], ngx.HTTP_INTERNAL_SERVER_ERROR, cjson.encode(resp)
end
for i, plugin in ipairs(list) do
for _, plugin in ipairs(list) do
if pcall(require, plugin.id .. "/" .. plugin.id) then
local plugin_lua = require(plugin.id .. "/" .. plugin.id)
if plugin_lua.api ~= nil then

View File

@ -1,42 +1,38 @@
local mlcache = require "resty.mlcache"
local class = require "middleclass"
local clusterstore = require "bunkerweb.clusterstore"
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local cachestore = class("cachestore")
local logger = require "bunkerweb.logger"
local mlcache = require "resty.mlcache"
local utils = require "bunkerweb.utils"
local cachestore = class("cachestore")
-- Instantiate mlcache object at module level (which will be cached when running init phase)
-- TODO : custom settings
local shm = "cachestore"
local ipc_shm = "cachestore_ipc"
local shm_miss = "cachestore_miss"
local shm_locks = "cachestore_locks"
local shm = "cachestore"
local ipc_shm = "cachestore_ipc"
local shm_miss = "cachestore_miss"
local shm_locks = "cachestore_locks"
if not ngx.shared.cachestore then
shm = "cachestore_stream"
ipc_shm = "cachestore_ipc_stream"
shm_miss = "cachestore_miss_stream"
shm = "cachestore_stream"
ipc_shm = "cachestore_ipc_stream"
shm_miss = "cachestore_miss_stream"
shm_locks = "cachestore_locks_stream"
end
local cache, err = mlcache.new(
"cachestore",
shm,
{
lru_size = 100,
ttl = 30,
neg_ttl = 0.1,
shm_set_tries = 3,
shm_miss = shm_miss,
shm_locks = shm_locks,
resty_lock_opts = {
exptime = 30,
timeout = 5,
step = 0.001,
ratio = 2,
max_step = 0.5
},
ipc_shm = ipc_shm
}
)
local cache, err = mlcache.new("cachestore", shm, {
lru_size = 100,
ttl = 30,
neg_ttl = 0.1,
shm_set_tries = 3,
shm_miss = shm_miss,
shm_locks = shm_locks,
resty_lock_opts = {
exptime = 30,
timeout = 5,
step = 0.001,
ratio = 2,
max_step = 0.5,
},
ipc_shm = ipc_shm,
})
local module_logger = logger:new("CACHESTORE")
if not cache then
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
@ -57,10 +53,12 @@ function cachestore:initialize(use_redis, new_cs, ctx)
end
function cachestore:get(key)
-- luacheck: ignore 432
local callback = function(key, cs)
-- Connect to redis
-- luacheck: ignore 431
local clusterstore = cs or require "bunkerweb.clusterstore":new(false)
local ok, err, reused = clusterstore:connect()
local ok, err, _ = clusterstore:connect()
if not ok then
return nil, "can't connect to redis : " .. err, nil
end
@ -96,6 +94,7 @@ function cachestore:get(key)
local callback_no_miss = function()
return nil, nil, -1
end
-- luacheck: ignore 431
local value, err, hit_level
if self.use_redis and utils.is_cosocket_available() then
local cs = nil
@ -114,13 +113,14 @@ function cachestore:get(key)
end
function cachestore:set(key, value, ex)
-- luacheck: ignore 431
local ok, err
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self:set_redis(key, value, ex)
ok, err = self:set_redis(key, value, ex)
if not ok then
self.logger:log(ngx.ERR, err)
end
end
local ok, err
if ex then
ok, err = self.cache:set(key, { ttl = ex }, value)
else
@ -134,13 +134,14 @@ end
function cachestore:set_redis(key, value, ex)
-- Connect to redis
local ok, err, reused = self.clusterstore:connect()
-- luacheck: ignore 431
local ok, err, _ = 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 = self.clusterstore:call("set", key, value, "EX", default_ex)
local _, err = self.clusterstore:call("set", key, value, "EX", default_ex)
if err then
self.clusterstore:close()
return false, "SET failed : " .. err
@ -149,14 +150,16 @@ function cachestore:set_redis(key, value, ex)
return true
end
function cachestore:delete(key, value, ex)
function cachestore:delete(key)
-- luacheck: ignore 431
local ok, err
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self.del_redis(key)
ok, err = self:del_redis(key)
if not ok then
self.logger:log(ngx.ERR, err)
end
end
local ok, err = self.cache:delete(key)
ok, err = self.cache:delete(key)
if not ok then
return false, err
end
@ -165,12 +168,13 @@ end
function cachestore:del_redis(key)
-- Connect to redis
-- luacheck: ignore 431
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 = self.clusterstore:del(key)
local _, err = self.clusterstore:del(key)
if err then
self.clusterstore:close()
return false, "DEL failed : " .. err

View File

@ -1,135 +1,138 @@
local class = require "middleclass"
local utils = require "bunkerweb.utils"
local logger = require "bunkerweb.logger"
local redis = require "resty.redis"
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local redis = require "resty.redis"
local utils = require "bunkerweb.utils"
local clusterstore = class("clusterstore")
function clusterstore:initialize(pool)
-- Instantiate logger
self.logger = logger:new("CLUSTERSTORE")
-- Get variables
local variables = {
["REDIS_HOST"] = "",
["REDIS_PORT"] = "",
["REDIS_DATABASE"] = "",
["REDIS_SSL"] = "",
["REDIS_TIMEOUT"] = "",
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = ""
}
-- Set them for later user
self.variables = {}
for k, v in pairs(variables) do
local value, err = utils.get_variable(k, false)
if value == nil then
self.logger:log(ngx.ERR, err)
end
self.variables[k] = value
end
-- Don't instantiate a redis object for now
self.redis_client = nil
self.pool = pool == nil or pool
-- Instantiate logger
self.logger = logger:new("CLUSTERSTORE")
-- Get variables
local variables = {
["REDIS_HOST"] = "",
["REDIS_PORT"] = "",
["REDIS_DATABASE"] = "",
["REDIS_SSL"] = "",
["REDIS_TIMEOUT"] = "",
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = "",
}
-- Set them for later user
self.variables = {}
for k, _ in pairs(variables) do
local value, err = utils.get_variable(k, false)
if value == nil then
self.logger:log(ngx.ERR, err)
end
self.variables[k] = value
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 then
return true, "already connected", self.redis_client:get_reused_times()
end
-- Instantiate object
local redis_client, err = redis:new()
if redis_client == nil then
return false, err
end
-- Set timeouts
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
-- Connect
local options = {
ssl = self.variables["REDIS_SSL"] == "yes",
}
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
self.redis_client = redis_client
-- Select database if needed
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 = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
if err then
self:close()
return false, err
end
end
return true, "success", times
-- Check if we are 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()
if redis_client == nil then
return false, err
end
-- Set timeouts
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
-- Connect
local options = {
ssl = self.variables["REDIS_SSL"] == "yes",
}
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
self.redis_client = redis_client
-- Select database if needed
local times, err = self.redis_client:get_reused_times()
if err then
self:close()
return false, err
end
if times == 0 then
-- luacheck: ignore 421
local _, err = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
if err then
self:close()
return false, err
end
end
return true, "success", times
end
function clusterstore:close()
if self.redis_client then
-- Equivalent to close but keep a pool of connections
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"
if self.redis_client then
-- Equivalent to close but keep a pool of connections
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"
end
function clusterstore:call(method, ...)
-- Check if we are connected
if not self.redis_client then
return false, "not connected"
end
-- Call method
return self.redis_client[method](self.redis_client, ...)
-- Check if we are connected
if not self.redis_client then
return false, "not connected"
end
-- Call method
return self.redis_client[method](self.redis_client, ...)
end
function clusterstore:multi(calls)
-- Check if we are connected
if not self.redis_client then
return false, "not connected"
end
-- Start transaction
local ok, err = self.redis_client:multi()
if not ok then
return false, "multi() failed : " .. err
end
-- Loop on calls
for i, call in ipairs(calls) do
local method = call[1]
local args = unpack(call[2])
local ok, err = self.redis_client[method](self.redis_client, args)
if not ok then
return false, method + "() failed : " .. err
end
end
-- Exec transaction
local exec, err = self.redis_client:exec()
if not exec then
return false, "exec() failed : " .. err
end
if type(exec) ~= "table" then
return false, "exec() result is not a table"
end
return true, "success", exec
-- Check if we are connected
if not self.redis_client then
return false, "not connected"
end
-- Start transaction
local ok, err = self.redis_client:multi()
if not ok then
return false, "multi() failed : " .. err
end
-- Loop on calls
for _, call in ipairs(calls) do
local method = call[1]
local args = unpack(call[2])
ok, err = self.redis_client[method](self.redis_client, args)
if not ok then
return false, method + "() failed : " .. err
end
end
-- Exec transaction
local exec, err = self.redis_client:exec()
if not exec then
return false, "exec() failed : " .. err
end
if type(exec) ~= "table" then
return false, "exec() result is not a table"
end
return true, "success", exec
end
return clusterstore

View File

@ -1,11 +1,12 @@
local class = require "middleclass"
local lrucache = require "resty.lrucache"
local class = require "middleclass"
local lrucache = require "resty.lrucache"
local datastore = class("datastore")
local lru, err = lrucache.new(100000)
local lru, err = lrucache.new(100000)
if not lru then
require "bunkerweb.logger":new("DATASTORE"):log(ngx.ERR,
"failed to instantiate LRU cache : " .. (err or "unknown error"))
require "bunkerweb.logger"
:new("DATASTORE")
:log(ngx.ERR, "failed to instantiate LRU cache : " .. (err or "unknown error"))
end
function datastore:initialize()
@ -16,11 +17,13 @@ function datastore:initialize()
end
function datastore:get(key, worker)
-- luacheck: ignore 431
local value, err
if worker then
local value, err = lru:get(key)
value, err = lru:get(key)
return value, err or "not found"
end
local value, err = self.dict:get(key)
value, err = self.dict:get(key)
if not value and not err then
err = "not found"
end
@ -52,10 +55,11 @@ function datastore:keys(worker)
return self.dict:get_keys(0)
end
function datastore:ttl(key)
function datastore:ttl(key, worker)
if worker then
return false, "not supported by LRU"
end
-- luacheck: ignore 431
local ttl, err = self.dict:ttl(key)
if not ttl then
return false, err
@ -64,13 +68,13 @@ function datastore:ttl(key)
end
function datastore:delete_all(pattern, worker)
local keys = {}
local keys
if worker then
keys = lru:keys(0)
else
keys = self.dict:get_keys(0)
end
for i, key in ipairs(keys) do
for _, key in ipairs(keys) do
if key:match(pattern) then
self.dict:delete(key)
end
@ -78,6 +82,7 @@ function datastore:delete_all(pattern, worker)
return true, "success"
end
-- luacheck: ignore 212
function datastore:flush_lru()
lru:flush_all()
end

View File

@ -1,263 +1,263 @@
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local cjson = require "cjson"
local utils = require "bunkerweb.utils"
local helpers = {}
local helpers = {}
helpers.load_plugin = function(json)
-- Open file
local file, err, nb = io.open(json, "r")
if not file then
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
end
-- Decode JSON
local ok, plugin = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid JSON at " .. json .. " : " .. err
end
-- Check fields
local missing_fields = {}
local required_fields = { "id", "name", "description", "version", "settings", "stream" }
for i, field in ipairs(required_fields) do
if plugin[field] == nil then
table.insert(missing_fields, field)
end
end
if #missing_fields > 0 then
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
end
-- Try require
local plugin_lua, err = helpers.require_plugin(plugin.id)
if plugin_lua == false then
return false, err
end
-- Fill phases
local phases = utils.get_phases()
plugin.phases = {}
if plugin_lua then
for i, phase in ipairs(phases) do
if plugin_lua[phase] ~= nil then
table.insert(plugin.phases, phase)
end
end
end
-- Return plugin
return true, plugin
helpers.load_plugin = function(json)
-- Open file
local file, err, nb = io.open(json, "r")
if not file then
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
end
-- Decode JSON
local ok, plugin = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid JSON at " .. json .. " : " .. err
end
-- Check fields
local missing_fields = {}
local required_fields = { "id", "name", "description", "version", "settings", "stream" }
for _, field in ipairs(required_fields) do
if plugin[field] == nil then
table.insert(missing_fields, field)
end
end
if #missing_fields > 0 then
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
end
-- Try require
local plugin_lua, err = helpers.require_plugin(plugin.id)
if plugin_lua == false then
return false, err
end
-- Fill phases
local phases = utils.get_phases()
plugin.phases = {}
if plugin_lua then
for _, phase in ipairs(phases) do
if plugin_lua[phase] ~= nil then
table.insert(plugin.phases, phase)
end
end
end
-- Return plugin
return true, plugin
end
helpers.order_plugins = function(plugins)
-- Extract orders
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
if not file then
return false, err .. " (nb = " .. tostring(nb) .. ")"
end
local ok, orders = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid order.json : " .. err
end
-- Compute plugins/id/phases table
local plugins_phases = {}
for i, plugin in ipairs(plugins) do
plugins_phases[plugin.id] = {}
for j, phase in ipairs(plugin.phases) do
plugins_phases[plugin.id][phase] = true
end
end
-- Order result
local result_orders = {}
for i, phase in ipairs(utils.get_phases()) do
result_orders[phase] = {}
end
-- Fill order first
for phase, order in pairs(orders) do
for i, id in ipairs(order) do
local plugin = plugins_phases[id]
if plugin and plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
-- Then append missing plugins to the end
for i, phase in ipairs(utils.get_phases()) do
for id, plugin in pairs(plugins_phases) do
if plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
return true, result_orders
helpers.order_plugins = function(plugins)
-- Extract orders
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
if not file then
return false, err .. " (nb = " .. tostring(nb) .. ")"
end
local ok, orders = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid order.json : " .. err
end
-- Compute plugins/id/phases table
local plugins_phases = {}
for _, plugin in ipairs(plugins) do
plugins_phases[plugin.id] = {}
for _, phase in ipairs(plugin.phases) do
plugins_phases[plugin.id][phase] = true
end
end
-- Order result
local result_orders = {}
for _, phase in ipairs(utils.get_phases()) do
result_orders[phase] = {}
end
-- Fill order first
for phase, order in pairs(orders) do
for _, id in ipairs(order) do
local plugin = plugins_phases[id]
if plugin and plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
-- Then append missing plugins to the end
for _, phase in ipairs(utils.get_phases()) do
for id, plugin in pairs(plugins_phases) do
if plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
return true, result_orders
end
helpers.require_plugin = function(id)
-- Require call
local ok, plugin_lua = pcall(require, id .. "/" .. id)
if not ok then
if plugin_lua:match("not found") then
return nil, "plugin " .. id .. " doesn't have LUA code"
end
return false, "require error for plugin " .. id .. " : " .. plugin_lua
end
-- New call
if plugin_lua.new == nil then
return false, "missing new() method for plugin " .. id
end
-- Return plugin
return plugin_lua, "require() call successful for plugin " .. id
-- Require call
local ok, plugin_lua = pcall(require, id .. "/" .. id)
if not ok then
if plugin_lua:match("not found") then
return nil, "plugin " .. id .. " doesn't have LUA code"
end
return false, "require error for plugin " .. id .. " : " .. plugin_lua
end
-- New call
if plugin_lua.new == nil then
return false, "missing new() method for plugin " .. id
end
-- Return plugin
return plugin_lua, "require() call successful for plugin " .. id
end
helpers.new_plugin = function(plugin_lua, ctx)
-- Require call
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx)
if not ok then
return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj
end
return true, plugin_obj
helpers.new_plugin = function(plugin_lua, ctx)
-- Require call
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx)
if not ok then
return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj
end
return true, plugin_obj
end
helpers.call_plugin = function(plugin, method)
-- Check if method is present
if plugin[method] == nil then
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
end
-- Call method
local ok, ret = pcall(plugin[method], plugin)
if not ok then
return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret
end
if ret == nil then
return false, plugin:get_id() .. ":" .. method .. "() returned nil value"
end
-- Check values
local missing_values = {}
local required_values = { "ret", "msg" }
for i, value in ipairs(required_values) do
if ret[value] == nil then
table.insert(missing_values, value)
end
end
if #missing_values > 0 then
return false, "missing required return value(s) : " .. cjson.encode(missing_values)
end
-- Return
return true, ret
helpers.call_plugin = function(plugin, method)
-- Check if method is present
if plugin[method] == nil then
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
end
-- Call method
local ok, ret = pcall(plugin[method], plugin)
if not ok then
return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret
end
if ret == nil then
return false, plugin:get_id() .. ":" .. method .. "() returned nil value"
end
-- Check values
local missing_values = {}
local required_values = { "ret", "msg" }
for _, value in ipairs(required_values) do
if ret[value] == nil then
table.insert(missing_values, value)
end
end
if #missing_values > 0 then
return false, "missing required return value(s) : " .. cjson.encode(missing_values)
end
-- Return
return true, ret
end
helpers.fill_ctx = function()
-- Return errors as table
local errors = {}
local ctx = ngx.ctx
-- Check if ctx is already filled
if not 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.server_name = ngx.var.server_name
if data.kind == "http" then
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_content_length = ngx.var.http_content_length
data.http_origin = ngx.var.http_origin
data.http_version = ngx.req.http_version()
data.scheme = ngx.var.scheme
end
-- 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()
-- Fill ctx
ctx.bw = data
end
-- Always create new objects for current phases in case of cosockets
local 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
ctx.bw.datastore = require "bunkerweb.datastore":new()
ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors, ctx
helpers.fill_ctx = function()
-- Return errors as table
local errors = {}
local ctx = ngx.ctx
-- Check if ctx is already filled
if not 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.server_name = ngx.var.server_name
if data.kind == "http" then
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_content_length = ngx.var.http_content_length
data.http_origin = ngx.var.http_origin
data.http_version = ngx.req.http_version()
data.scheme = ngx.var.scheme
end
-- 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()
-- Fill ctx
ctx.bw = data
end
-- Always create new objects for current phases in case of cosockets
local 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
ctx.bw.datastore = require "bunkerweb.datastore":new()
ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors, ctx
end
function helpers.load_variables(all_variables, plugins)
-- Extract settings from plugins and global ones
local all_settings = {}
for i, plugin in ipairs(plugins) do
if plugin.settings then
for setting, data in pairs(plugin.settings) do
all_settings[setting] = data
end
end
end
local file = io.open("/usr/share/bunkerweb/settings.json")
if not file then
return false, "can't open settings.json"
end
local ok, settings = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid settings.json : " .. err
end
for setting, data in pairs(settings) do
all_settings[setting] = data
end
-- Extract vars
local variables = { ["global"] = {} }
local multisite = all_variables["MULTISITE"] == "yes"
local server_names = {}
if multisite then
for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do
variables[server_name] = {}
table.insert(server_names, server_name)
end
end
for setting, data in pairs(all_settings) do
if all_variables[setting] then
variables["global"][setting] = all_variables[setting]
end
if data.multiple then
for variable, value in pairs(all_variables) do
local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))")
if multiple_setting then
if multisite and server_name and server_name:match("%S+_$") then
variables[server_name:sub(1, -2)][multiple_setting] = value
else
variables["global"][multiple_setting] = value
end
end
end
end
if multisite then
for i, server_name in ipairs(server_names) do
local key = server_name .. "_" .. setting
if all_variables[key] then
variables[server_name][setting] = all_variables[key]
end
end
end
end
return true, variables
-- Extract settings from plugins and global ones
local all_settings = {}
for _, plugin in ipairs(plugins) do
if plugin.settings then
for setting, data in pairs(plugin.settings) do
all_settings[setting] = data
end
end
end
local file = io.open("/usr/share/bunkerweb/settings.json")
if not file then
return false, "can't open settings.json"
end
local ok, settings = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid settings.json : " .. settings
end
for setting, data in pairs(settings) do
all_settings[setting] = data
end
-- Extract vars
local variables = { ["global"] = {} }
local multisite = all_variables["MULTISITE"] == "yes"
local server_names = {}
if multisite then
for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do
variables[server_name] = {}
table.insert(server_names, server_name)
end
end
for setting, data in pairs(all_settings) do
if all_variables[setting] then
variables["global"][setting] = all_variables[setting]
end
if data.multiple then
for variable, value in pairs(all_variables) do
local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))")
if multiple_setting then
if multisite and server_name and server_name:match("%S+_$") then
variables[server_name:sub(1, -2)][multiple_setting] = value
else
variables["global"][multiple_setting] = value
end
end
end
end
if multisite then
for _, server_name in ipairs(server_names) do
local key = server_name .. "_" .. setting
if all_variables[key] then
variables[server_name][setting] = all_variables[key]
end
end
end
end
return true, variables
end
return helpers

View File

@ -1,5 +1,5 @@
local class = require "middleclass"
local errlog = require "ngx.errlog"
local class = require "middleclass"
local logger = class("logger")
function logger:initialize(prefix)

View File

@ -1,6 +1,6 @@
local geoip = require "geoip.mmdb"
return {
country_db = geoip.load_database("/var/cache/bunkerweb/country.mmdb"),
asn_db = geoip.load_database("/var/cache/bunkerweb/asn.mmdb")
country_db = geoip.load_database "/var/cache/bunkerweb/country.mmdb",
asn_db = geoip.load_database "/var/cache/bunkerweb/asn.mmdb",
}

View File

@ -1,87 +1,88 @@
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local cachestore = require "bunkerweb.cachestore"
local class = require "middleclass"
local clusterstore = require "bunkerweb.clusterstore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
local datastore = require "bunkerweb.datastore"
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local plugin = class("plugin")
function plugin:initialize(id, ctx)
-- Store common, values
self.id = id
local multisite = false
local current_phase = ngx.get_phase()
for i, check_phase in ipairs({ "set", "access", "content", "header_filter", "log", "preread", "log_stream",
"log_default" }) do
if current_phase == check_phase then
multisite = true
break
end
end
self.is_request = multisite
-- Store common objects
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
-- Store ctx
self.ctx = ctx or ngx.ctx
self.datastore = utils.get_ctx_obj("datastore", self.ctx) or datastore:new()
self.cachestore = utils.get_ctx_obj("cachestore", self.ctx) or cachestore:new(use_redis == "yes", true, self.ctx)
self.clusterstore = utils.get_ctx_obj("clusterstore", self.ctx) 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 metadata, err = self.datastore:get("plugin_" .. id, true)
if not metadata then
self.logger:log(ngx.ERR, err)
return
end
-- Store variables
self.variables = {}
self.multiples = {}
for k, v in pairs(metadata.settings) do
local value, err = utils.get_variable(k, v.context == "multisite" and multisite)
if value == nil then
self.logger:log(ngx.ERR, "can't get " .. k .. " variable : " .. err)
end
self.variables[k] = value
-- if v.multiple then
-- local multiples, err = utils.get_multiple_variables(k)
-- if not multiples then
-- self.logger:log(ngx.ERR, "can't get " .. k .. " multiple variable : " .. err)
-- self.multiples[k] = {}
-- else
-- self.multiples[k] = multiples
-- end
-- end
end
-- Is loading
local is_loading, err = utils.get_variable("IS_LOADING", false)
if is_loading == nil then
self.logger:log(ngx.ERR, "can't get IS_LOADING variable : " .. err)
end
self.is_loading = is_loading == "yes"
-- Kind of server
self.kind = "http"
if ngx.shared.datastore_stream then
self.kind = "stream"
end
-- Store common, values
self.id = id
local multisite = false
local current_phase = ngx.get_phase()
for _, check_phase in ipairs {
"set",
"access",
"content",
"header_filter",
"log",
"preread",
"log_stream",
"log_default",
} do
if current_phase == check_phase then
multisite = true
break
end
end
self.is_request = multisite
-- Store common objects
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
-- Store ctx
self.ctx = ctx or ngx.ctx
self.datastore = utils.get_ctx_obj("datastore", self.ctx) or datastore:new()
self.cachestore = utils.get_ctx_obj("cachestore", self.ctx)
or cachestore:new(use_redis == "yes", true, self.ctx)
self.clusterstore = utils.get_ctx_obj("clusterstore", self.ctx) 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 metadata, err = self.datastore:get("plugin_" .. id, true)
if not metadata then
self.logger:log(ngx.ERR, err)
return
end
-- Store variables
self.variables = {}
self.multiples = {}
local value
for k, v in pairs(metadata.settings) do
value, err = utils.get_variable(k, v.context == "multisite" and multisite)
if value == nil then
self.logger:log(ngx.ERR, "can't get " .. k .. " variable : " .. err)
end
self.variables[k] = value
end
-- Is loading
local is_loading, err = utils.get_variable("IS_LOADING", false)
if is_loading == nil then
self.logger:log(ngx.ERR, "can't get IS_LOADING variable : " .. err)
end
self.is_loading = is_loading == "yes"
-- Kind of server
self.kind = "http"
if ngx.shared.datastore_stream then
self.kind = "stream"
end
end
function plugin:get_id()
return self.id
return self.id
end
-- luacheck: ignore 212
function plugin:ret(ret, msg, status, redirect)
return { ret = ret, msg = msg, status = status, redirect = redirect }
return { ret = ret, msg = msg, status = status, redirect = redirect }
end
return plugin

View File

@ -1,26 +1,26 @@
local cdatastore = require "bunkerweb.datastore"
local mmdb = require "bunkerweb.mmdb"
local clogger = require "bunkerweb.logger"
local clogger = require "bunkerweb.logger"
local mmdb = require "bunkerweb.mmdb"
local ipmatcher = require "resty.ipmatcher"
local resolver = require "resty.dns.resolver"
local session = require "resty.session"
local cjson = require "cjson"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local resolver = require "resty.dns.resolver"
local session = require "resty.session"
local logger = clogger:new("UTILS")
local datastore = cdatastore:new()
local logger = clogger:new("UTILS")
local datastore = cdatastore:new()
local utils = {}
local utils = {}
math.randomseed(os.time())
utils.get_variable = function(var, site_search)
utils.get_variable = function(var, site_search)
-- Default site search to true
if site_search == nil then
site_search = true
end
-- Get global value
local variables, err = datastore:get('variables', true)
local variables, err = datastore:get("variables", true)
if not variables then
return nil, "can't access variables from datastore : " .. err
end
@ -33,9 +33,9 @@ utils.get_variable = function(var, site_search)
return value, "success"
end
utils.has_variable = function(var, value)
utils.has_variable = function(var, value)
-- Get global variable
local variables, err = datastore:get('variables', true)
local variables, err = datastore:get("variables", true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
@ -56,9 +56,9 @@ utils.has_variable = function(var, value)
return variables["global"][var] == value, "success"
end
utils.has_not_variable = function(var, value)
utils.has_not_variable = function(var, value)
-- Get global variable
local variables, err = datastore:get('variables', true)
local variables, err = datastore:get("variables", true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
@ -80,9 +80,9 @@ utils.has_not_variable = function(var, value)
end
utils.get_multiple_variables = function(vars)
local variables, err = datastore:get('variables', true)
local variables, err = datastore:get("variables", true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
return nil, "can't access variables " .. vars .. " from datastore : " .. err
end
local result = {}
-- Loop on scoped vars
@ -90,7 +90,7 @@ utils.get_multiple_variables = function(vars)
result[scope] = {}
-- Loop on vars
for variable, value in pairs(scoped_vars) do
for i, var in ipairs(vars) do
for _, var in ipairs(vars) do
if variable:find("^" .. var .. "_?[0-9]*$") then
result[scope][variable] = value
end
@ -100,7 +100,7 @@ utils.get_multiple_variables = function(vars)
return result
end
utils.is_ip_in_networks = function(ip, networks)
utils.is_ip_in_networks = function(ip, networks)
-- Instantiate ipmatcher
local ipm, err = ipmatcher.new(networks)
if not ipm then
@ -114,15 +114,15 @@ utils.is_ip_in_networks = function(ip, networks)
return matched
end
utils.is_ipv4 = function(ip)
utils.is_ipv4 = function(ip)
return ipmatcher.parse_ipv4(ip)
end
utils.is_ipv6 = function(ip)
utils.is_ipv6 = function(ip)
return ipmatcher.parse_ipv6(ip)
end
utils.ip_is_global = function(ip)
utils.ip_is_global = function(ip)
-- Reserved, non public IPs
local reserved_ips = {
"0.0.0.0/8",
@ -154,7 +154,7 @@ utils.ip_is_global = function(ip)
"2002::/16",
"fc00::/7",
"fe80::/10",
"ff00::/8"
"ff00::/8",
}
-- Instantiate ipmatcher
local ipm, err = ipmatcher.new(reserved_ips)
@ -169,9 +169,9 @@ utils.ip_is_global = function(ip)
return not matched, "success"
end
utils.get_integration = function()
utils.get_integration = function()
-- Check if already in datastore
local integration, err = datastore:get("misc_integration", true)
local integration, _ = datastore:get("misc_integration", true)
if integration then
return integration
end
@ -193,12 +193,12 @@ utils.get_integration = function()
integration = "autoconf"
else
-- Already present (e.g. : linux)
local f, err = io.open("/usr/share/bunkerweb/INTEGRATION", "r")
local f, _ = io.open("/usr/share/bunkerweb/INTEGRATION", "r")
if f then
integration = f:read("*a"):gsub("[\n\r]", "")
f:close()
else
local f, err = io.open("/etc/os-release", "r")
f, _ = io.open("/etc/os-release", "r")
if f then
local data = f:read("*a")
f:close()
@ -222,9 +222,9 @@ utils.get_integration = function()
return integration
end
utils.get_version = function()
utils.get_version = function()
-- Check if already in datastore
local version, err = datastore:get("misc_version", true)
local version, _ = datastore:get("misc_version", true)
if version then
return version
end
@ -244,7 +244,7 @@ utils.get_version = function()
return version
end
utils.get_reason = function(ctx)
utils.get_reason = function(ctx)
-- ngx.ctx
if ctx.bw.reason then
return ctx.bw.reason
@ -258,7 +258,7 @@ utils.get_reason = function(ctx)
return "modsecurity"
end
-- datastore ban
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
local banned, _ = datastore:get("bans_ip_" .. ngx.var.remote_addr)
if banned then
return banned
end
@ -269,9 +269,9 @@ utils.get_reason = function(ctx)
return nil
end
utils.get_resolvers = function()
utils.get_resolvers = function()
-- Get resolvers from datastore if existing
local resolvers, err = datastore:get("misc_resolvers", true)
local resolvers, _ = datastore:get("misc_resolvers", true)
if resolvers then
return resolvers
end
@ -282,7 +282,7 @@ utils.get_resolvers = function()
return "unknown"
end
-- Make table for resolver1 resolver2 ... string
local resolvers = {}
resolvers = {}
for str_resolver in variables["global"]["DNS_RESOLVERS"]:gmatch("%S+") do
table.insert(resolvers, str_resolver)
end
@ -294,7 +294,7 @@ utils.get_resolvers = function()
return resolvers
end
utils.get_rdns = function(ip)
utils.get_rdns = function(ip)
-- Check cache
local cachestore = utils.new_cachestore()
local ok, value = cachestore:get("rdns_" .. ip)
@ -312,7 +312,7 @@ utils.get_rdns = function(ip)
local rdns, err = resolver:new {
nameservers = resolvers,
retrans = 1,
timeout = 1000
timeout = 1000,
}
if not rdns then
return false, err
@ -330,21 +330,21 @@ utils.get_rdns = function(ip)
ret_err = answers.errstr
end
-- Extract all PTR
for i, answer in ipairs(answers) do
for _, answer in ipairs(answers) do
if answer.ptrdname then
table.insert(ptrs, answer.ptrdname)
end
end
end
-- Save to cache
local ok, err = cachestore:set("rdns_" .. ip, cjson.encode(ptrs), 3600)
ok, err = cachestore:set("rdns_" .. ip, cjson.encode(ptrs), 3600)
if not ok then
logger:log(ngx.ERR, "can't set rdns into cachestore : " .. err)
end
return ptrs, ret_err
end
utils.get_ips = function(fqdn, ipv6)
utils.get_ips = function(fqdn, ipv6)
-- Check cache
local cachestore = utils.new_cachestore()
local ok, value = cachestore:get("dns_" .. fqdn)
@ -366,7 +366,7 @@ utils.get_ips = function(fqdn, ipv6)
local res, err = resolver:new {
nameservers = resolvers,
retrans = 1,
timeout = 1000
timeout = 1000,
}
if not res then
return false, err
@ -374,6 +374,7 @@ utils.get_ips = function(fqdn, ipv6)
-- Get query types : AAAA and A if using IPv6 / only A if not using IPv6
local qtypes = {}
if ipv6 then
-- luacheck: ignore 421
local use_ipv6, err = utils.get_variable("USE_IPV6", false)
if not use_ipv6 then
logger:log(ngx.ERR, "can't get USE_IPV6 variable " .. err)
@ -386,9 +387,10 @@ utils.get_ips = function(fqdn, ipv6)
local res_answers = {}
local res_errors = {}
local ans_errors = {}
for i, qtype in ipairs(qtypes) do
local answers
for _, qtype in ipairs(qtypes) do
-- Query FQDN
local answers, err = res:query(fqdn, { qtype = qtype }, {})
answers, err = res:query(fqdn, { qtype = qtype }, {})
local qtype_str = qtype == res.TYPE_AAAA and "AAAA" or "A"
if not answers then
res_errors[qtype_str] = err
@ -403,22 +405,23 @@ utils.get_ips = function(fqdn, ipv6)
end
-- Extract all IPs
local ips = {}
for i, answers in ipairs(res_answers) do
for j, answer in ipairs(answers) do
-- luacheck: ignore 421
for _, answers in ipairs(res_answers) do
for _, answer in ipairs(answers) do
if answer.address then
table.insert(ips, answer.address)
end
end
end
-- Save to cache
local ok, err = cachestore:set("dns_" .. fqdn, cjson.encode(ips), 3600)
ok, err = cachestore:set("dns_" .. fqdn, cjson.encode(ips), 3600)
if not ok then
logger:log(ngx.ERR, "can't set dns into cachestore : " .. err)
end
return ips, cjson.encode(res_errors) .. " " .. cjson.encode(ans_errors)
end
utils.get_country = function(ip)
utils.get_country = function(ip)
-- Check if mmdb is loaded
if not mmdb.country_db then
return false, "mmdb country not loaded"
@ -434,7 +437,7 @@ utils.get_country = function(ip)
return result.country.iso_code, "success"
end
utils.get_asn = function(ip)
utils.get_asn = function(ip)
-- Check if mmdp is loaded
if not mmdb.asn_db then
return false, "mmdb asn not loaded"
@ -450,22 +453,28 @@ utils.get_asn = function(ip)
return result.autonomous_system_number, "success"
end
utils.rand = function(nb, no_numbers)
utils.rand = function(nb, no_numbers)
local charset = {}
-- lowers, uppers and numbers
if not no_numbers then
for i = 48, 57 do table.insert(charset, string.char(i)) end
for i = 48, 57 do
table.insert(charset, string.char(i))
end
end
for i = 65, 90 do
table.insert(charset, string.char(i))
end
for i = 97, 122 do
table.insert(charset, string.char(i))
end
for i = 65, 90 do table.insert(charset, string.char(i)) end
for i = 97, 122 do table.insert(charset, string.char(i)) end
local result = ""
for i = 1, nb do
for _ = 1, nb do
result = result .. charset[math.random(1, #charset)]
end
return result
end
utils.get_deny_status = function(ctx)
utils.get_deny_status = function(ctx)
-- Stream case
if ctx.bw and ctx.bw.kind == "stream" then
return 444
@ -479,10 +488,10 @@ utils.get_deny_status = function(ctx)
return tonumber(variables["global"]["DENY_HTTP_STATUS"])
end
utils.check_session = function(ctx)
local _session, err, exists, refreshed = session.start({ audience = "metadata" })
utils.check_session = function(ctx)
local _session, _, exists, _ = session.start({ audience = "metadata" })
if exists then
for i, check in ipairs(ctx.bw.sessions_checks) do
for _, check in ipairs(ctx.bw.sessions_checks) do
local key = check[1]
local value = check[2]
if _session:get(key) ~= value then
@ -496,7 +505,7 @@ utils.check_session = function(ctx)
end
end
else
for i, check in ipairs(ctx.bw.sessions_checks) do
for _, check in ipairs(ctx.bw.sessions_checks) do
_session:set(check[1], check[2])
end
local ok, err = _session:save()
@ -509,7 +518,7 @@ utils.check_session = function(ctx)
return true, exists
end
utils.get_session = function(audience, ctx)
utils.get_session = function(audience, ctx)
-- Check session
if not ctx.bw.sessions_is_checked then
local ok, err = utils.check_session(ctx)
@ -518,14 +527,15 @@ utils.get_session = function(audience, ctx)
end
end
-- Open session with specific audience
local _session, err, exists = session.open({ audience = audience })
local _session, err, _ = session.open({ audience = audience })
if err then
logger:log(ngx.INFO, "session:open() error : " .. err)
end
return _session
end
utils.get_session_data = function(_session, site, ctx)
-- luacheck: ignore 214
utils.get_session_data = function(_session, site, ctx)
local site_only = site == nil or site
local data = _session:get_data()
if site_only then
@ -534,7 +544,8 @@ utils.get_session_data = function(_session, site, ctx)
return data
end
utils.set_session_data = function(_session, data, site, ctx)
-- luacheck: ignore 214
utils.set_session_data = function(_session, data, site, ctx)
local site_only = site == nil or site
if site_only then
local all_data = _session:get_data()
@ -546,7 +557,7 @@ utils.set_session_data = function(_session, data, site, ctx)
return _session:save()
end
utils.is_banned = function(ip)
utils.is_banned = function(ip)
-- Check on local datastore
local reason, err = datastore:get("bans_ip_" .. ip)
if not reason and err ~= "not found" then
@ -599,7 +610,7 @@ utils.is_banned = function(ip)
elseif data[1] ~= ngx.null then
clusterstore:close()
-- Update local cache
local ok, err = datastore:set("bans_ip_" .. ip, data[1], data[2])
ok, err = datastore:set("bans_ip_" .. ip, data[1], data[2])
if not ok then
return nil, "datastore:set() error : " .. err
end
@ -609,7 +620,7 @@ utils.is_banned = function(ip)
return false, "not banned"
end
utils.add_ban = function(ip, reason, ttl)
utils.add_ban = function(ip, reason, ttl)
-- Set on local datastore
local ok, err = datastore:set("bans_ip_" .. ip, reason, ttl)
if not ok then
@ -624,12 +635,12 @@ utils.add_ban = function(ip, reason, ttl)
end
-- Connect
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
ok, err = clusterstore:connect()
if not ok then
return false, "can't connect to redis server : " .. err
end
-- SET call
local ok, err = clusterstore:call("set", "bans_ip_" .. ip, reason, "EX", ttl)
ok, err = clusterstore:call("set", "bans_ip_" .. ip, reason, "EX", ttl)
if not ok then
clusterstore:close()
return false, "redis SET failed : " .. err
@ -638,7 +649,7 @@ utils.add_ban = function(ip, reason, ttl)
return true, "success"
end
utils.new_cachestore = function()
utils.new_cachestore = function()
-- Check if redis is used
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
@ -650,7 +661,7 @@ utils.new_cachestore = function()
return require "bunkerweb.cachestore":new(use_redis, true)
end
utils.regex_match = function(str, regex, options)
utils.regex_match = function(str, regex, options)
local all_options = "o"
if options then
all_options = all_options .. options
@ -663,7 +674,7 @@ utils.regex_match = function(str, regex, options)
return match
end
utils.get_phases = function()
utils.get_phases = function()
return {
"init",
"init_worker",
@ -673,18 +684,18 @@ utils.get_phases = function()
"log",
"preread",
"log_stream",
"log_default"
"log_default",
}
end
utils.is_cosocket_available = function()
utils.is_cosocket_available = function()
local phases = {
"timer",
"access",
"preread"
"preread",
}
local current_phase = ngx.get_phase()
for i, phase in ipairs(phases) do
for _, phase in ipairs(phases) do
if current_phase == phase then
return true
end
@ -692,8 +703,8 @@ utils.is_cosocket_available = function()
return false
end
utils.kill_all_threads = function(threads)
for i, thread in ipairs(threads) do
utils.kill_all_threads = function(threads)
for _, 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)
@ -701,7 +712,7 @@ utils.kill_all_threads = function(threads)
end
end
utils.get_ctx_obj = function(obj)
utils.get_ctx_obj = function(obj)
if ngx.ctx and ngx.ctx.bw then
return ngx.ctx.bw[obj]
end

View File

@ -1,12 +1,12 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local captcha = require "antibot.captcha"
local base64 = require "base64"
local sha256 = require "resty.sha256"
local str = require "resty.string"
local http = require "resty.http"
local base64 = require "base64"
local captcha = require "antibot.captcha"
local cjson = require "cjson"
local class = require "middleclass"
local http = require "resty.http"
local plugin = require "bunkerweb.plugin"
local sha256 = require "resty.sha256"
local str = require "resty.string"
local utils = require "bunkerweb.utils"
local template = nil
if ngx.shared.datastore then
template = require "resty.template"
@ -51,40 +51,41 @@ function antibot:header()
return self:ret(true, "Not antibot uri")
end
local header = "Content-Security-Policy"
if self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
header = header .. "-Report-Only"
end
if self.session_data.type == "recaptcha" then
ngx.header[header] =
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
self.session_data.nonce_script ..
"' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' http: https:; img-src https://www.gstatic.com/recaptcha/ 'self' data:; frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/; style-src 'self' 'nonce-" ..
self.session_data.nonce_style ..
"'; font-src 'self' https://fonts.gstatic.com data:; base-uri 'self';"
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' http: https:;"
.. " img-src https://www.gstatic.com/recaptcha/ 'self' data:; "
.. " frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/;"
.. " style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' https://fonts.gstatic.com data:; base-uri 'self';"
elseif self.session_data.type == "hcaptcha" then
ngx.header[header] =
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
self.session_data.nonce_script ..
"' https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' http: https:; img-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; style-src 'self' 'nonce-" ..
self.session_data.nonce_style ..
"' https://hcaptcha.com https://*.hcaptcha.com; connect-src https://hcaptcha.com https://*.hcaptcha.com; font-src 'self' data:; base-uri 'self';"
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' http: https:; img-src 'self' data:;"
.. " frame-src https://hcaptcha.com https://*.hcaptcha.com; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "' https://hcaptcha.com https://*.hcaptcha.com; connect-src https://hcaptcha.com https://*.hcaptcha.com; "
.. " font-src 'self' data:; base-uri 'self';"
elseif self.session_data.type == "turnstile" then
ngx.header[header] =
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
self.session_data.nonce_script ..
"' https://challenges.cloudflare.com 'unsafe-inline' http: https:; img-src 'self' data:; frame-src https://challenges.cloudflare.com; style-src 'self' 'nonce-" ..
self.session_data.nonce_style ..
"'; font-src 'self' data:; base-uri 'self';"
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://challenges.cloudflare.com 'unsafe-inline' http: https:; img-src 'self' data:;"
.. " frame-src https://challenges.cloudflare.com; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' data:; base-uri 'self';"
else
ngx.header[header] =
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
self.session_data.nonce_script ..
"' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-" ..
self.session_data.nonce_style ..
"'; font-src 'self' data:; base-uri 'self';"
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' data:; base-uri 'self';"
end
return self:ret(true, "Successfully overridden CSP header")
end
@ -138,6 +139,7 @@ function antibot:access()
-- Check challenge
if self.ctx.bw.request_method == "POST" then
-- luacheck: ignore 421
local ok, err, redirect = self:check_challenge()
local set_ok, set_err = self:set_session_data()
if not set_ok then
@ -152,7 +154,7 @@ function antibot:access()
return self:ret(true, "check challenge redirect : " .. redirect, nil, redirect)
end
self:prepare_challenge()
local ok, err = self:set_session_data()
ok, err = self:set_session_data()
if not ok then
return self:ret(false, "can't save session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
@ -215,7 +217,9 @@ function antibot:check_session()
return
end
-- Check if new prepare is needed
if not resolved and (time_resolve > time or time - time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"])) then
if
not resolved and (time_resolve > time or time - time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"]))
then
self.session_data = {}
self.session_updated = true
return
@ -312,7 +316,7 @@ function antibot:check_challenge()
return nil, "challenge not prepared"
end
local resolved = false
local resolved
self.session_data.prepared = false
self.session_updated = true
@ -364,12 +368,15 @@ function antibot:check_challenge()
end
local res, err = httpc:request_uri("https://www.google.com/recaptcha/api/siteverify", {
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_RECAPTCHA_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
body = "secret="
.. self.variables["ANTIBOT_RECAPTCHA_SECRET"]
.. "&response="
.. args["token"]
.. "&remoteip="
.. self.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
["Content-Type"] = "application/x-www-form-urlencoded",
},
})
httpc:close()
if not res then
@ -400,12 +407,15 @@ function antibot:check_challenge()
end
local res, err = httpc:request_uri("https://hcaptcha.com/siteverify", {
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_HCAPTCHA_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
body = "secret="
.. self.variables["ANTIBOT_HCAPTCHA_SECRET"]
.. "&response="
.. args["token"]
.. "&remoteip="
.. self.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
["Content-Type"] = "application/x-www-form-urlencoded",
},
})
httpc:close()
if not res then
@ -413,7 +423,7 @@ function antibot:check_challenge()
end
local ok, hdata = pcall(cjson.decode, res.body)
if not ok then
return nil, "error while decoding JSON from hCaptcha API : " .. data, nil
return nil, "error while decoding JSON from hCaptcha API : " .. hdata, nil
end
if not hdata.success then
return false, "client failed challenge", nil
@ -436,12 +446,15 @@ function antibot:check_challenge()
end
local res, err = httpc:request_uri("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_TURNSTILE_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
body = "secret="
.. self.variables["ANTIBOT_TURNSTILE_SECRET"]
.. "&response="
.. args["token"]
.. "&remoteip="
.. self.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
["Content-Type"] = "application/x-www-form-urlencoded",
},
})
httpc:close()
if not res then
@ -449,7 +462,7 @@ function antibot:check_challenge()
end
local ok, tdata = pcall(cjson.decode, res.body)
if not ok then
return nil, "error while decoding JSON from Turnstile API : " .. data, nil
return nil, "error while decoding JSON from Turnstile API : " .. tdata, nil
end
if not tdata.success then
return false, "client failed challenge", nil

View File

@ -1,6 +1,6 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local badbehavior = class("badbehavior", plugin)
@ -23,14 +23,20 @@ function badbehavior:log()
return self:ret(true, "not increasing counter")
end
-- Check if we are already banned
local banned, err = self.datastore:get("bans_ip_" .. self.ctx.bw.remote_addr)
local banned, _ = self.datastore:get("bans_ip_" .. self.ctx.bw.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.ctx.bw.remote_addr,
tonumber(self.variables["BAD_BEHAVIOR_COUNT_TIME"]), tonumber(self.variables["BAD_BEHAVIOR_BAN_TIME"]),
tonumber(self.variables["BAD_BEHAVIOR_THRESHOLD"]), self.use_redis)
local ok, err = ngx.timer.at(
0,
badbehavior.increase,
self.ctx.bw.remote_addr,
tonumber(self.variables["BAD_BEHAVIOR_COUNT_TIME"]),
tonumber(self.variables["BAD_BEHAVIOR_BAN_TIME"]),
tonumber(self.variables["BAD_BEHAVIOR_THRESHOLD"]),
self.use_redis
)
if not ok then
return self:ret(false, "can't create increase timer : " .. err)
end
@ -45,6 +51,7 @@ function badbehavior:log_stream()
return self:log()
end
-- luacheck: ignore 212
function badbehavior.increase(premature, ip, count_time, ban_time, threshold, use_redis)
-- Instantiate objects
local logger = require "bunkerweb.logger":new("badbehavior")
@ -84,16 +91,28 @@ function badbehavior.increase(premature, ip, count_time, ban_time, threshold, us
end
-- Store local ban
if counter > threshold then
local ok, err = utils.add_ban(ip, "bad behavior", ban_time)
ok, err = utils.add_ban(ip, "bad behavior", ban_time)
if not ok then
logger:log(ngx.ERR, "(increase) can't save ban : " .. err)
return
end
logger:log(ngx.WARN,
"IP " .. ip .. " is banned for " .. ban_time .. "s (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
logger:log(
ngx.WARN,
"IP "
.. ip
.. " is banned for "
.. ban_time
.. "s ("
.. tostring(counter)
.. "/"
.. tostring(threshold)
.. ")"
)
end
logger:log(ngx.NOTICE,
"increased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
logger:log(
ngx.NOTICE,
"increased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")"
)
end
function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
@ -126,7 +145,7 @@ function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
-- Store local counter
if counter <= 0 then
counter = 0
local ok, err = datastore:delete("plugin_badbehavior_count_" .. ip)
datastore:delete("plugin_badbehavior_count_" .. ip)
else
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, counter, count_time)
if not ok then
@ -134,8 +153,10 @@ function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
return
end
end
logger:log(ngx.NOTICE,
"decreased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
logger:log(
ngx.NOTICE,
"decreased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")"
)
end
function badbehavior.redis_increase(ip, count_time, ban_time)
@ -168,9 +189,8 @@ function badbehavior.redis_increase(ip, count_time, ban_time)
return false, err
end
-- Execute LUA script
local counter, err = clusterstore:call("eval", redis_script, 2, "plugin_bad_behavior_" .. ip, "bans_ip" .. ip,
count_time,
ban_time)
local counter, err =
clusterstore:call("eval", redis_script, 2, "plugin_bad_behavior_" .. ip, "bans_ip" .. ip, count_time, ban_time)
if not counter then
clusterstore:close()
return false, err

View File

@ -1,7 +1,7 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local ipmatcher = require "resty.ipmatcher"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local blacklist = class("blacklist", plugin)
@ -78,7 +78,7 @@ function blacklist:init()
}
local i = 0
for kind, _ in pairs(blacklists) do
local f, err = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
local f, _ = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(blacklists[kind], line)
@ -102,7 +102,7 @@ function blacklist:access()
end
-- Check the caches
local checks = {
["IP"] = "ip" .. self.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr,
}
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
@ -113,14 +113,18 @@ function blacklist:access()
local already_cached = {
["IP"] = false,
["URI"] = false,
["UA"] = false
["UA"] = false,
}
for k, v in pairs(checks) do
local ok, cached = self:is_in_cache(v)
if not ok then
self.logger:log(ngx.ERR, "error while checking cache : " .. cached)
elseif cached and cached ~= "ok" then
return self:ret(true, k .. " is in cached blacklist (info : " .. cached .. ")", utils.get_deny_status(self.ctx))
return self:ret(
true,
k .. " is in cached blacklist (info : " .. cached .. ")",
utils.get_deny_status(self.ctx)
)
end
if ok and cached then
already_cached[k] = true
@ -131,18 +135,23 @@ function blacklist:access()
return self:ret(false, "lists is nil")
end
-- Perform checks
for k, v in pairs(checks) do
for k, _ in pairs(checks) do
if not already_cached[k] then
local ok, blacklisted = self:is_blacklisted(k)
if ok == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is blacklisted : " .. blacklisted)
else
-- luacheck: ignore 421
local ok, err = self:add_to_cache(self:kind_to_ele(k), blacklisted)
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 blacklisted (info : " .. blacklisted .. ")", utils.get_deny_status(self.ctx))
return self:ret(
true,
k .. " is blacklisted (info : " .. blacklisted .. ")",
utils.get_deny_status(self.ctx)
)
end
end
end
@ -205,11 +214,11 @@ function blacklist:is_blacklisted_ip()
end
if not match then
-- Check if IP is in blacklist
local ipm, err = ipmatcher.new(self.lists["IP"])
ipm, err = ipmatcher.new(self.lists["IP"])
if not ipm then
return nil, err
end
local match, err = ipm:match(self.ctx.bw.remote_addr)
match, err = ipm:match(self.ctx.bw.remote_addr)
if err then
return nil, err
end
@ -225,13 +234,14 @@ function blacklist:is_blacklisted_ip()
end
if check_rdns then
-- Get rDNS
-- luacheck: ignore 421
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
if rdns_list then
-- Check if rDNS is in ignore list
local ignore = false
for i, rdns in ipairs(rdns_list) do
for j, suffix in ipairs(self.lists["IGNORE_RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for _, rdns in ipairs(rdns_list) do
for _, suffix in ipairs(self.lists["IGNORE_RDNS"]) do
if rdns:sub(-#suffix) == suffix then
ignore = true
break
end
@ -239,9 +249,9 @@ function blacklist:is_blacklisted_ip()
end
-- Check if rDNS is in blacklist
if not ignore then
for i, rdns in ipairs(rdns_list) do
for j, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for _, rdns in ipairs(rdns_list) do
for _, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(-#suffix) == suffix then
return true, "rDNS " .. suffix
end
end
@ -259,7 +269,7 @@ function blacklist:is_blacklisted_ip()
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
else
local ignore = false
for i, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
for _, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
if ignore_asn == tostring(asn) then
ignore = true
break
@ -267,7 +277,7 @@ function blacklist:is_blacklisted_ip()
end
-- Check if ASN is in blacklist
if not ignore then
for i, bl_asn in ipairs(self.lists["ASN"]) do
for _, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
@ -283,7 +293,7 @@ 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
for _, ignore_uri in ipairs(self.lists["IGNORE_URI"]) do
if utils.regex_match(self.ctx.bw.uri, ignore_uri) then
ignore = true
break
@ -291,7 +301,7 @@ function blacklist:is_blacklisted_uri()
end
-- Check if URI is in blacklist
if not ignore then
for i, uri in ipairs(self.lists["URI"]) do
for _, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
@ -304,7 +314,7 @@ 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
for _, ignore_ua in ipairs(self.lists["IGNORE_USER_AGENT"]) do
if utils.regex_match(self.ctx.bw.http_user_agent, ignore_ua) then
ignore = true
break
@ -312,7 +322,7 @@ function blacklist:is_blacklisted_ua()
end
-- Check if UA is in blacklist
if not ignore then
for i, ua in ipairs(self.lists["USER_AGENT"]) do
for _, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end

View File

@ -1,8 +1,8 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local http = require "resty.http"
local cjson = require "cjson"
local class = require "middleclass"
local http = require "resty.http"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local bunkernet = class("bunkernet", plugin)
@ -49,12 +49,15 @@ function bunkernet:init_worker()
return self:ret(false, "missing instance ID")
end
-- Send ping request
local ok, err, status, data = self:ping()
local ok, err, status, _ = self:ping()
if not ok then
return self:ret(false, "error while sending request to API : " .. err)
end
if status ~= 200 then
return self:ret(false, "received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id)
return self:ret(
false,
"received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id
)
end
self.logger:log(ngx.NOTICE, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
return self:ret(true, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
@ -82,7 +85,7 @@ function bunkernet:init()
local ret = true
local i = 0
local db = {
ip = {}
ip = {},
}
local f, err = io.open("/var/cache/bunkerweb/bunkernet/ip.list", "r")
if not f then
@ -128,6 +131,7 @@ function bunkernet:access()
if db then
-- Check if is IP is present
if #db.ip > 0 then
-- luacheck: ignore 421
local present, err = utils.is_ip_in_networks(self.ctx.bw.remote_addr, db.ip)
if present == nil then
return self:ret(false, "can't check if ip is in db : " .. err)
@ -166,8 +170,9 @@ function bunkernet:log(bypass_checks)
return self:ret(true, "IP is not global")
end
-- TODO : check if IP has been reported recently
-- luacheck: ignore 212 431
local function report_callback(premature, obj, ip, reason, method, url, headers)
local ok, err, status, data = obj:report(ip, reason, method, url, headers)
local ok, err, status, _ = 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
@ -177,8 +182,16 @@ function bunkernet:log(bypass_checks)
end
end
local hdr, err = ngx.timer.at(0, report_callback, self, self.ctx.bw.remote_addr, reason, self.ctx.bw.request_method,
self.ctx.bw.request_uri, ngx.req.get_headers())
local hdr, err = ngx.timer.at(
0,
report_callback,
self,
self.ctx.bw.remote_addr,
reason,
self.ctx.bw.request_method,
self.ctx.bw.request_uri,
ngx.req.get_headers()
)
if not hdr then
return self:ret(false, "can't create report timer : " .. err)
end
@ -218,7 +231,7 @@ function bunkernet:request(method, url, data)
local all_data = {
id = self.bunkernet_id,
version = self.version,
integration = self.integration
integration = self.integration,
}
if data then
for k, v in pairs(data) do
@ -230,8 +243,8 @@ function bunkernet:request(method, url, data)
body = cjson.encode(all_data),
headers = {
["Content-Type"] = "application/json",
["User-Agent"] = "BunkerWeb/" .. self.version
}
["User-Agent"] = "BunkerWeb/" .. self.version,
},
})
httpc:close()
if not res then
@ -257,7 +270,7 @@ function bunkernet:report(ip, reason, method, url, headers)
reason = reason,
method = method,
url = url,
headers = headers
headers = headers,
}
return self:request("POST", "/report", data)
end

View File

@ -1,8 +1,8 @@
local class = require "middleclass"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local utils = require "bunkerweb.utils"
local cors = class("cors", plugin)
local cors = class("cors", plugin)
function cors:initialize(ctx)
-- Call parent initialize
@ -17,7 +17,7 @@ function cors:initialize(ctx)
["CORS_MAX_AGE"] = "Access-Control-Max-Age",
["CORS_ALLOW_CREDENTIALS"] = "Access-Control-Allow-Credentials",
["CORS_ALLOW_METHODS"] = "Access-Control-Allow-Methods",
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers"
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers",
}
end
@ -43,7 +43,12 @@ function cors:header()
ngx.header.Vary = "Origin"
end
-- Check if Origin is allowed
if self.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
if
self.ctx.bw.http_origin
and self.variables["CORS_DENY_REQUEST"] == "yes"
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
then
self.logger:log(ngx.WARN, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
end
@ -81,9 +86,17 @@ function cors:access()
return self:ret(true, "service doesn't use CORS")
end
-- Deny as soon as possible if needed
if self.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access",
utils.get_deny_status(self.ctx))
if
self.ctx.bw.http_origin
and self.variables["CORS_DENY_REQUEST"] == "yes"
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
then
return self:ret(
true,
"origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access",
utils.get_deny_status(self.ctx)
)
end
-- Send CORS policy with a 204 (no content) status
if self.ctx.bw.request_method == "OPTIONS" and self.ctx.bw.http_origin then

View File

@ -1,7 +1,7 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local cjson = require "cjson"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local country = class("country", plugin)
@ -16,17 +16,28 @@ function country:access()
return self:ret(true, "country not activated")
end
-- Check if IP is in cache
local ok, data = self:is_in_cache(self.ctx.bw.remote_addr)
local _, data = self:is_in_cache(self.ctx.bw.remote_addr)
if data then
data = cjson.decode(data)
if data.result == "ok" then
return self:ret(true,
"client IP " ..
self.ctx.bw.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")")
return self:ret(
true,
"client IP "
.. self.ctx.bw.remote_addr
.. " is in country cache (not blacklisted, country = "
.. data.country
.. ")"
)
end
return self:ret(true,
"client IP " .. self.ctx.bw.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")",
utils.get_deny_status(self.ctx))
return self:ret(
true,
"client IP "
.. self.ctx.bw.remote_addr
.. " is in country cache (blacklisted, country = "
.. data.country
.. ")",
utils.get_deny_status(self.ctx)
)
end
-- Don't go further if IP is not global
@ -39,50 +50,64 @@ function country:access()
end
-- Get the country of client
local country, err = utils.get_country(self.ctx.bw.remote_addr)
if not country then
local country_data, err = utils.get_country(self.ctx.bw.remote_addr)
if not country_data then
return self:ret(false, "can't get country of client IP " .. self.ctx.bw.remote_addr .. " : " .. err)
end
-- Process whitelist first
if self.variables["WHITELIST_COUNTRY"] ~= "" then
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch("%S+") do
if wh_country == country then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch "%S+" do
if wh_country == country_data then
-- luacheck: ignore 421
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ok")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is whitelisted (country = " .. country .. ")")
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is whitelisted (country = " .. country_data .. ")"
)
end
end
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
-- luacheck: ignore 421
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ko")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country .. ")",
utils.get_deny_status(self.ctx))
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
utils.get_deny_status(self.ctx)
)
end
-- And then blacklist
if self.variables["BLACKLIST_COUNTRY"] ~= "" then
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch("%S+") do
if bl_country == country then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch "%S+" do
if bl_country == country_data then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ko")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country .. ")",
utils.get_deny_status(self.ctx))
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",
utils.get_deny_status(self.ctx)
)
end
end
end
-- Country IP is not in blacklist
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ok")
if not ok then
return self:ret(false, "error while caching IP " .. self.ctx.bw.remote_addr .. " : " .. err)
end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not blacklisted (country = " .. country .. ")")
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is not blacklisted (country = " .. country_data .. ")"
)
end
function country:preread()
@ -97,9 +122,12 @@ function country:is_in_cache(ip)
return true, data
end
function country:add_to_cache(ip, country, result)
local ok, err = self.cachestore:set("plugin_country_" .. self.ctx.bw.server_name .. ip,
cjson.encode({ country = country, result = result }), 86400)
function country:add_to_cache(ip, country_data, result)
local ok, err = self.cachestore:set(
"plugin_country_" .. self.ctx.bw.server_name .. ip,
cjson.encode { country = country_data, result = result },
86400
)
if not ok then
return false, err
end

View File

@ -1,9 +1,23 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local resolver = require "resty.dns.resolver"
local utils = require "bunkerweb.utils"
local dnsbl = class("dnsbl", plugin)
local dnsbl = class("dnsbl", plugin)
local is_in_dnsbl = function(addr, server)
local request = resolver.arpa_str(addr):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "") .. "." .. server
local ips, err = utils.get_ips(request, false)
if not ips then
return nil, server, err
end
for _, ip in ipairs(ips) do
if ip:find "^127%.0%.0%." then
return true, server
end
end
return false, server
end
function dnsbl:initialize(ctx)
-- Call parent initialize
@ -26,14 +40,15 @@ function dnsbl:init_worker()
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, "127.0.0.2", server)
local thread = ngx.thread.spawn(is_in_dnsbl, "127.0.0.2", server)
threads[server] = thread
end
-- Wait for threads
for dnsbl, thread in pairs(threads) do
for data, thread in pairs(threads) do
-- luacheck: ignore 421
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)
self.logger:log(ngx.ERR, "error while waiting thread of " .. data .. " check : " .. result)
elseif result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
elseif not result then
@ -65,14 +80,17 @@ function dnsbl:access()
if cached == "ok" then
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
utils.get_deny_status(self.ctx))
return self:ret(
true,
"client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
utils.get_deny_status(self.ctx)
)
end
-- Loop on DNSBL list
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, self.ctx.bw.remote_addr, server)
local thread = ngx.thread.spawn(is_in_dnsbl, self.ctx.bw.remote_addr, server)
threads[server] = thread
end
-- Wait for threads
@ -82,7 +100,7 @@ function dnsbl:access()
while true do
-- Compute threads to wait
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
for _, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No server reported IP
@ -90,6 +108,7 @@ function dnsbl:access()
break
end
-- Wait for first thread
-- luacheck: ignore 421
local ok, result, server, err = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
@ -115,7 +134,7 @@ function dnsbl:access()
-- Kill other threads
if #threads > 0 then
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
for _, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
@ -159,18 +178,4 @@ function dnsbl:add_to_cache(ip, value)
return true
end
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, server, err
end
for i, ip in ipairs(ips) do
if ip:find("^127%.0%.0%.") then
return true, server
end
end
return false, server
end
return dnsbl

View File

@ -1,5 +1,5 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local template = nil
if ngx.shared.datastore then
template = require "resty.template"
@ -14,52 +14,52 @@ function errors:initialize(ctx)
self.default_errors = {
["400"] = {
title = "Bad Request",
text = "The server did not understand the request."
text = "The server did not understand the request.",
},
["401"] = {
title = "Not Authorized",
text = "Valid authentication credentials needed for the target resource."
text = "Valid authentication credentials needed for the target resource.",
},
["403"] = {
title = "Forbidden",
text = "Access is forbidden to the requested page."
text = "Access is forbidden to the requested page.",
},
["404"] = {
title = "Not Found",
text = "The server cannot find the requested page."
text = "The server cannot find the requested page.",
},
["405"] = {
title = "Method Not Allowed",
text = "The method specified in the request is not allowed."
text = "The method specified in the request is not allowed.",
},
["413"] = {
title = "Request Entity Too Large",
text = "The server will not accept the request, because the request entity is too large."
text = "The server will not accept the request, because the request entity is too large.",
},
["429"] = {
title = "Too Many Requests",
text = "Too many requests sent in a given amount of time, try again later."
text = "Too many requests sent in a given amount of time, try again later.",
},
["500"] = {
title = "Internal Server Error",
text = "The request was not completed. The server met an unexpected condition."
text = "The request was not completed. The server met an unexpected condition.",
},
["501"] = {
title = "Not Implemented",
text = "The request was not completed. The server did not support the functionality required."
text = "The request was not completed. The server did not support the functionality required.",
},
["502"] = {
title = "Bad Gateway",
text = "The request was not completed. The server received an invalid response from the upstream server."
text = "The request was not completed. The server received an invalid response from the upstream server.",
},
["503"] = {
title = "Service Unavailable",
text = "The request was not completed. The server is temporarily overloading or down."
text = "The request was not completed. The server is temporarily overloading or down.",
},
["504"] = {
title = "Gateway Timeout",
text = "The gateway has timed out."
}
text = "The gateway has timed out.",
},
}
end
@ -69,7 +69,7 @@ function errors:render_template(code)
title = code .. " - " .. self.default_errors[code].title,
error_title = self.default_errors[code].title,
error_code = code,
error_text = self.default_errors[code].text
error_text = self.default_errors[code].text,
})
end

View File

@ -1,9 +1,9 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local ipmatcher = require "resty.ipmatcher"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local greylist = class("greylist", plugin)
local greylist = class("greylist", plugin)
function greylist:initialize(ctx)
-- Call parent initialize
@ -22,7 +22,7 @@ function greylist:initialize(ctx)
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {}
["URI"] = {},
}
for kind, _ in pairs(kinds) do
for data in self.variables["GREYLIST_" .. kind]:gmatch("%S+") do
@ -67,7 +67,7 @@ function greylist:init()
}
local i = 0
for kind, _ in pairs(greylists) do
local f, err = io.open("/var/cache/bunkerweb/greylist/" .. kind .. ".list", "r")
local f, _ = io.open("/var/cache/bunkerweb/greylist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(greylists[kind], line)
@ -91,7 +91,7 @@ function greylist:access()
end
-- Check the caches
local checks = {
["IP"] = "ip" .. self.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr,
}
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
@ -102,7 +102,7 @@ function greylist:access()
local already_cached = {
["IP"] = false,
["URI"] = false,
["UA"] = false
["UA"] = false,
}
for k, v in pairs(checks) do
local ok, cached = self:is_in_cache(v)
@ -120,12 +120,13 @@ function greylist:access()
return self:ret(false, "lists is nil")
end
-- Perform checks
for k, v in pairs(checks) do
for k, _ in pairs(checks) do
if not already_cached[k] then
local ok, greylisted = self:is_greylisted(k)
if ok == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is greylisted : " .. greylisted)
else
-- luacheck: ignore 421
local ok, err = self:add_to_cache(self:kind_to_ele(k), greylisted)
if not ok then
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
@ -187,12 +188,13 @@ function greylist:is_greylisted_ip()
end
if check_rdns then
-- Get rDNS
-- luacheck: ignore 421
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
-- Check if rDNS is in greylist
if rdns_list then
for i, rdns in ipairs(rdns_list) do
for j, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for _, rdns in ipairs(rdns_list) do
for _, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(-#suffix) == suffix then
return true, "rDNS " .. suffix
end
end
@ -208,7 +210,7 @@ function greylist:is_greylisted_ip()
if not asn then
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
else
for i, bl_asn in ipairs(self.lists["ASN"]) do
for _, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
@ -222,7 +224,7 @@ end
function greylist:is_greylisted_uri()
-- Check if URI is in greylist
for i, uri in ipairs(self.lists["URI"]) do
for _, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
@ -233,7 +235,7 @@ end
function greylist:is_greylisted_ua()
-- Check if UA is in greylist
for i, ua in ipairs(self.lists["USER_AGENT"]) do
for _, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end

View File

@ -1,99 +1,110 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local headers = class("headers", plugin)
function headers:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "headers", ctx)
self.all_headers = {
["STRICT_TRANSPORT_SECURITY"] = "Strict-Transport-Security",
["CONTENT_SECURITY_POLICY"] = "Content-Security-Policy",
["REFERRER_POLICY"] = "Referrer-Policy",
["PERMISSIONS_POLICY"] = "Permissions-Policy",
["FEATURE_POLICY"] = "Feature-Policy",
["X_FRAME_OPTIONS"] = "X-Frame-Options",
["X_CONTENT_TYPE_OPTIONS"] = "X-Content-Type-Options",
["X_XSS_PROTECTION"] = "X-XSS-Protection"
}
-- Load data from datastore if needed
if ngx.get_phase() ~= "init" then
-- Get custom headers from datastore
local custom_headers, err = self.datastore:get("plugin_headers_custom_headers", true)
if not custom_headers then
self.logger:log(ngx.ERR, err)
return
end
self.custom_headers = {}
-- Extract global headers
if custom_headers.global then
for k, v in pairs(custom_headers.global) do
self.custom_headers[k] = v
end
end
-- Extract and overwrite if needed server headers
if custom_headers[self.ctx.bw.server_name] then
for k, v in pairs(custom_headers[self.ctx.bw.server_name]) do
self.custom_headers[k] = v
end
end
end
-- Call parent initialize
plugin.initialize(self, "headers", ctx)
self.all_headers = {
["STRICT_TRANSPORT_SECURITY"] = "Strict-Transport-Security",
["CONTENT_SECURITY_POLICY"] = "Content-Security-Policy",
["REFERRER_POLICY"] = "Referrer-Policy",
["PERMISSIONS_POLICY"] = "Permissions-Policy",
["FEATURE_POLICY"] = "Feature-Policy",
["X_FRAME_OPTIONS"] = "X-Frame-Options",
["X_CONTENT_TYPE_OPTIONS"] = "X-Content-Type-Options",
["X_XSS_PROTECTION"] = "X-XSS-Protection",
}
-- Load data from datastore if needed
if ngx.get_phase() ~= "init" then
-- Get custom headers from datastore
local custom_headers, err = self.datastore:get("plugin_headers_custom_headers", true)
if not custom_headers then
self.logger:log(ngx.ERR, err)
return
end
self.custom_headers = {}
-- Extract global headers
if custom_headers.global then
for k, v in pairs(custom_headers.global) do
self.custom_headers[k] = v
end
end
-- Extract and overwrite if needed server headers
if custom_headers[self.ctx.bw.server_name] then
for k, v in pairs(custom_headers[self.ctx.bw.server_name]) do
self.custom_headers[k] = v
end
end
end
end
function headers:init()
-- Get variables
local variables, err = utils.get_multiple_variables({ "CUSTOM_HEADER" })
if variables == nil then
return self:ret(false, err)
end
-- Store custom headers name and value
local data = {}
local i = 0
for srv, vars in pairs(variables) do
for var, value in pairs(vars) do
if data[srv] == nil then
data[srv] = {}
end
local m = utils.regex_match(value, "([\\w-]+): ([^,]+)")
if m then
data[srv][m[1]] = m[2]
end
i = i + 1
end
end
local ok, err = self.datastore:set("plugin_headers_custom_headers", data, nil, true)
if not ok then
return self:ret(false, err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " custom headers")
-- Get variables
local variables, err = utils.get_multiple_variables({ "CUSTOM_HEADER" })
if variables == nil then
return self:ret(false, err)
end
-- Store custom headers name and value
local data = {}
local i = 0
for srv, vars in pairs(variables) do
for _, value in pairs(vars) do
if data[srv] == nil then
data[srv] = {}
end
local m = utils.regex_match(value, "([\\w-]+): ([^,]+)")
if m then
data[srv][m[1]] = m[2]
end
i = i + 1
end
end
local ok
ok, err = self.datastore:set("plugin_headers_custom_headers", data, nil, true)
if not ok then
return self:ret(false, err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " custom headers")
end
function headers:header()
-- Override upstream headers if needed
local ssl = self.ctx.bw.scheme == "https"
for variable, header in pairs(self.all_headers) do
if ngx.header[header] == nil or (self.variables[variable] ~= "" and self.variables["KEEP_UPSTREAM_HEADERS"] ~= "*" and utils.regex_match(self.variables["KEEP_UPSTREAM_HEADERS"], "(^| )" .. header .. "($| )") == nil) then
if (header ~= "Strict-Transport-Security" or ssl) then
if header == "Content-Security-Policy" and self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
ngx.header["Content-Security-Policy-Report-Only"] = self.variables[variable]
else
ngx.header[header] = self.variables[variable]
end
end
end
end
-- Add custom headers
for header, value in pairs(self.custom_headers) do
ngx.header[header] = value
end
-- Remove headers
if self.variables["REMOVE_HEADERS"] ~= "" then
for header in self.variables["REMOVE_HEADERS"]:gmatch("%S+") do
ngx.header[header] = nil
end
end
return self:ret(true, "edited headers for request")
-- Override upstream headers if needed
local ssl = self.ctx.bw.scheme == "https"
for variable, header in pairs(self.all_headers) do
if
ngx.header[header] == nil
or (
self.variables[variable] ~= ""
and self.variables["KEEP_UPSTREAM_HEADERS"] ~= "*"
and utils.regex_match(self.variables["KEEP_UPSTREAM_HEADERS"], "(^| )" .. header .. "($| )") == nil
)
then
if header ~= "Strict-Transport-Security" or ssl then
if
header == "Content-Security-Policy"
and self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes"
then
ngx.header["Content-Security-Policy-Report-Only"] = self.variables[variable]
else
ngx.header[header] = self.variables[variable]
end
end
end
end
-- Add custom headers
for header, value in pairs(self.custom_headers) do
ngx.header[header] = value
end
-- Remove headers
if self.variables["REMOVE_HEADERS"] ~= "" then
for header in self.variables["REMOVE_HEADERS"]:gmatch("%S+") do
ngx.header[header] = nil
end
end
return self:ret(true, "edited headers for request")
end
return headers

View File

@ -1,6 +1,6 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local cjson = require "cjson"
local cjson = require "cjson"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local letsencrypt = class("letsencrypt", plugin)
@ -17,9 +17,12 @@ function letsencrypt:access()
return self:ret(true, "success")
end
-- luacheck: ignore 212
function letsencrypt:api(ctx)
if not string.match(ctx.bw.uri, "^/lets%-encrypt/challenge$") or
(ctx.bw.request_method ~= "POST" and ctx.bw.request_method ~= "DELETE") then
if
not string.match(ctx.bw.uri, "^/lets%-encrypt/challenge$")
or (ctx.bw.request_method ~= "POST" and ctx.bw.request_method ~= "DELETE")
then
return false, nil, nil
end
local acme_folder = "/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/"
@ -32,7 +35,9 @@ function letsencrypt:api(ctx)
if ctx.bw.request_method == "POST" then
local file, err = io.open(acme_folder .. data.token, "w+")
if not file then
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, { status = "error", msg = "can't write validation token : " .. err }
return true,
ngx.HTTP_INTERNAL_SERVER_ERROR,
{ status = "error", msg = "can't write validation token : " .. err }
end
file:write(data.validation)
file:close()
@ -40,7 +45,9 @@ function letsencrypt:api(ctx)
elseif ctx.bw.request_method == "DELETE" then
local ok, err = os.remove(acme_folder .. data.token)
if not ok then
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, { status = "error", msg = "can't remove validation token : " .. err }
return true,
ngx.HTTP_INTERNAL_SERVER_ERROR,
{ status = "error", msg = "can't remove validation token : " .. err }
end
return true, ngx.HTTP_OK, { status = "success", msg = "validation token removed" }
end

View File

@ -1,9 +1,40 @@
local class = require "middleclass"
local cjson = require "cjson"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local utils = require "bunkerweb.utils"
local limit = class("limit", plugin)
local limit = class("limit", plugin)
local limit_req_timestamps = function(rate_max, rate_time, timestamps)
-- Compute new timestamps
local updated = false
local new_timestamps = {}
local current_timestamp = os.time(os.date "!*t")
local delay = 0
if rate_time == "s" then
delay = 1
elseif rate_time == "m" then
delay = 60
elseif rate_time == "h" then
delay = 3600
elseif rate_time == "d" then
delay = 86400
end
-- Keep only timestamp within the delay
for _, timestamp in ipairs(timestamps) do
if current_timestamp - timestamp <= delay then
table.insert(new_timestamps, timestamp)
else
updated = true
end
end
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
if #new_timestamps <= rate_max then
table.insert(new_timestamps, current_timestamp)
updated = true
end
return updated, new_timestamps, delay
end
function limit:initialize(ctx)
-- Call parent initialize
@ -11,7 +42,6 @@ function limit:initialize(ctx)
-- Load rules if needed
if ngx.get_phase() ~= "init" and self:is_needed() then
-- Get all rules from datastore
local limited = false
local all_rules, err = self.datastore:get("plugin_limit_rules", true)
if not all_rules then
self.logger:log(ngx.ERR, err)
@ -93,19 +123,16 @@ function limit:access()
return self:ret(true, "limit request not enabled")
end
-- Check if URI is limited
local rate = nil
local uri = nil
local rate
for k, v in pairs(self.rules) do
if k ~= "/" and utils.regex_match(self.ctx.bw.uri, k) then
rate = v
uri = k
break
end
end
if not rate then
if self.rules["/"] then
rate = self.rules["/"]
uri = "/"
else
return self:ret(true, "no rule for " .. self.ctx.bw.uri)
end
@ -118,19 +145,37 @@ function limit:access()
end
-- Limit reached
if limited then
return self:ret(true,
"client IP " ..
self.ctx.bw.remote_addr ..
" is limited for URL " ..
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")",
ngx.HTTP_TOO_MANY_REQUESTS)
return self:ret(
true,
"client IP "
.. self.ctx.bw.remote_addr
.. " is limited for URL "
.. self.ctx.bw.uri
.. " (current rate = "
.. current_rate
.. "r/"
.. rate_time
.. " and max rate = "
.. rate
.. ")",
ngx.HTTP_TOO_MANY_REQUESTS
)
end
-- Limit not reached
return self:ret(true,
"client IP " ..
self.ctx.bw.remote_addr ..
" is not limited for URL " ..
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")")
return self:ret(
true,
"client IP "
.. self.ctx.bw.remote_addr
.. " is not limited for URL "
.. self.ctx.bw.uri
.. " (current rate = "
.. current_rate
.. "r/"
.. rate_time
.. " and max rate = "
.. rate
.. ")"
)
end
function limit:limit_req(rate_max, rate_time)
@ -143,9 +188,12 @@ function limit:limit_req(rate_max, rate_time)
else
timestamps = redis_timestamps
-- Save the new timestamps
-- luacheck: ignore 421
local ok, err = self.datastore:set(
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
cjson.encode(timestamps), delay)
cjson.encode(timestamps),
delay
)
if not ok then
return nil, "can't update timestamps : " .. err
end
@ -167,8 +215,8 @@ end
function limit:limit_req_local(rate_max, rate_time)
-- Get timestamps
local timestamps, err = self.datastore:get("plugin_limit_" ..
self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri)
local timestamps, err =
self.datastore:get("plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri)
if not timestamps and err ~= "not found" then
return nil, err
elseif err == "not found" then
@ -176,12 +224,15 @@ function limit:limit_req_local(rate_max, rate_time)
end
timestamps = cjson.decode(timestamps)
-- Compute new timestamps
local updated, new_timestamps, delay = self:limit_req_timestamps(rate_max, rate_time, timestamps)
local updated, new_timestamps, delay = limit_req_timestamps(rate_max, rate_time, timestamps)
-- Save new timestamps if needed
if updated then
-- luacheck: ignore 421
local ok, err = self.datastore:set(
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
cjson.encode(new_timestamps), delay)
cjson.encode(new_timestamps),
delay
)
if not ok then
return nil, err
end
@ -245,9 +296,15 @@ function limit:limit_req_redis(rate_max, rate_time)
return nil, err
end
-- Execute script
local timestamps, err = self.clusterstore:call("eval", redis_script, 1,
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri, rate_max, rate_time,
os.time(os.date("!*t")))
local timestamps, err = self.clusterstore:call(
"eval",
redis_script,
1,
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
rate_max,
rate_time,
os.time(os.date("!*t"))
)
if not timestamps then
self.clusterstore:close()
return nil, err
@ -257,35 +314,4 @@ function limit:limit_req_redis(rate_max, rate_time)
return timestamps, "success"
end
function limit:limit_req_timestamps(rate_max, rate_time, timestamps)
-- Compute new timestamps
local updated = false
local new_timestamps = {}
local current_timestamp = os.time(os.date("!*t"))
local delay = 0
if rate_time == "s" then
delay = 1
elseif rate_time == "m" then
delay = 60
elseif rate_time == "h" then
delay = 3600
elseif rate_time == "d" then
delay = 86400
end
-- Keep only timestamp within the delay
for i, timestamp in ipairs(timestamps) do
if current_timestamp - timestamp <= delay then
table.insert(new_timestamps, timestamp)
else
updated = true
end
end
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
if #new_timestamps <= rate_max then
table.insert(new_timestamps, current_timestamp)
updated = true
end
return updated, new_timestamps, delay
end
return limit

View File

@ -1,27 +1,27 @@
local class = require "middleclass"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local utils = require "bunkerweb.utils"
local misc = class("misc", plugin)
local misc = class("misc", plugin)
function misc:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "misc", ctx)
-- Call parent initialize
plugin.initialize(self, "misc", ctx)
end
function misc:access()
-- Check if method is valid
local method = self.ctx.bw.request_method
if not method or not utils.regex_match(method, "^[A-Z]+$") then
return self:ret(true, "method is not valid", ngx.HTTP_BAD_REQUEST)
end
-- Check if method is allowed
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
if method == allowed_method then
return self:ret(true, "method " .. method .. " is allowed")
end
end
return self:ret(true, "method " .. method .. " is not allowed", ngx.HTTP_NOT_ALLOWED)
-- Check if method is valid
local method = self.ctx.bw.request_method
if not method or not utils.regex_match(method, "^[A-Z]+$") then
return self:ret(true, "method is not valid", ngx.HTTP_BAD_REQUEST)
end
-- Check if method is allowed
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
if method == allowed_method then
return self:ret(true, "method " .. method .. " is allowed")
end
end
return self:ret(true, "method " .. method .. " is not allowed", ngx.HTTP_NOT_ALLOWED)
end
return misc

View File

@ -1,9 +1,9 @@
local class = require "middleclass"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local redis = class("redis", plugin)
local redis = class("redis", plugin)
function redis:initialize()
function redis:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "redis", ctx)
end

View File

@ -1,153 +1,155 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local reversescan = class("reversescan", plugin)
function reversescan:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "reversescan", ctx)
-- Call parent initialize
plugin.initialize(self, "reversescan", ctx)
end
function reversescan:access()
-- Check if access is needed
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
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(self.ctx.bw.remote_addr .. ":" .. port)
if not ok then
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 " .. self.ctx.bw.remote_addr
break
-- Perform scan in a thread
elseif not cached then
local thread = ngx.thread.spawn(self.scan, self.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(self.ctx))
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 " .. self.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(self.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(self.ctx))
end
-- Error case
return self:ret(false, ret_err)
end
-- No port opened
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr)
-- Check if access is needed
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
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(self.ctx.bw.remote_addr .. ":" .. port)
if not ok then
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 " .. self.ctx.bw.remote_addr
break
-- Perform scan in a thread
elseif not cached then
local thread = ngx.thread.spawn(
self.scan,
self.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 _, 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(self.ctx))
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 _, 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 " .. self.ctx.bw.remote_addr
break
end
end
-- Kill running threads
if #threads > 0 then
local wait_threads = {}
for _, 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(self.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(self.ctx))
end
-- Error case
return self:ret(false, ret_err)
end
-- No port opened
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr)
end
function reversescan:preread()
return self:access()
return self:access()
end
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 false, port
end
return true, port
local tcpsock = ngx.socket.tcp()
tcpsock:settimeout(timeout)
local ok, _ = tcpsock:connect(ip, port)
tcpsock:close()
if not ok then
return false, port
end
return true, port
end
function reversescan:is_in_cache(ip_port)
local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port)
if not ok then
return false, data
end
return true, data
local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port)
if not ok then
return false, data
end
return true, data
end
function reversescan:add_to_cache(ip_port, value)
local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400)
if not ok then
return false, err
end
return true
local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400)
if not ok then
return false, err
end
return true
end
return reversescan

View File

@ -1,118 +1,118 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local session = require "resty.session"
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local session = require "resty.session"
local utils = require "bunkerweb.utils"
local sessions = class("sessions", plugin)
function sessions:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "sessions", ctx)
-- Check if random cookie name and secrets are already generated
local is_random = {
"SESSIONS_SECRET",
"SESSIONS_NAME"
}
self.randoms = {}
for i, var in ipairs(is_random) do
if self.variables[var] == "random" then
local data, err = self.datastore:get("storage_sessions_" .. var)
if data then
self.randoms[var] = data
end
end
end
-- Call parent initialize
plugin.initialize(self, "sessions", ctx)
-- Check if random cookie name and secrets are already generated
local is_random = {
"SESSIONS_SECRET",
"SESSIONS_NAME",
}
self.randoms = {}
for _, var in ipairs(is_random) do
if self.variables[var] == "random" then
local data, _ = self.datastore:get("storage_sessions_" .. var)
if data then
self.randoms[var] = data
end
end
end
end
function sessions:set()
if self.is_loading or self.kind ~= "http" then
return self:ret(true, "set not needed")
end
local checks = {
["IP"] = self.ctx.bw.remote_addr,
["USER_AGENT"] = self.ctx.bw.http_user_agent or ""
}
self.ctx.bw.sessions_checks = {}
for check, value in pairs(checks) do
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
table.insert(self.ctx.bw.sessions_checks, { check, value })
end
end
return self:ret(true, "success")
if self.is_loading or self.kind ~= "http" then
return self:ret(true, "set not needed")
end
local checks = {
["IP"] = self.ctx.bw.remote_addr,
["USER_AGENT"] = self.ctx.bw.http_user_agent or "",
}
self.ctx.bw.sessions_checks = {}
for check, value in pairs(checks) do
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
table.insert(self.ctx.bw.sessions_checks, { check, value })
end
end
return self:ret(true, "success")
end
function sessions:init()
if self.is_loading or self.kind ~= "http" then
return self:ret(true, "init not needed")
end
-- Get redis vars
local redis_vars = {
["USE_REDIS"] = "",
["REDIS_HOST"] = "",
["REDIS_PORT"] = "",
["REDIS_DATABASE"] = "",
["REDIS_SSL"] = "",
["REDIS_TIMEOUT"] = "",
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = ""
}
for k, v in pairs(redis_vars) do
local value, err = utils.get_variable(k, false)
if value == nil then
return self:ret(false, "can't get " .. k .. " variable : " .. err)
end
redis_vars[k] = value
end
-- Init configuration
local config = {
secret = self.variables["SESSIONS_SECRET"],
cookie_name = self.variables["SESSIONS_NAME"],
idling_timeout = tonumber(self.variables["SESSIONS_IDLING_TIMEOUT"]),
rolling_timeout = tonumber(self.variables["SESSIONS_ROLLING_TIMEOUT"]),
absolute_timeout = tonumber(self.variables["SESSIONS_ABSOLUTE_TIMEOUT"])
}
if self.variables["SESSIONS_SECRET"] == "random" then
if self.randoms["SESSIONS_SECRET"] then
config.secret = self.randoms["SESSIONS_SECRET"]
else
config.secret = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_SECRET", config.secret)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if self.variables["SESSIONS_NAME"] == "random" then
if self.randoms["SESSIONS_NAME"] then
config.cookie_name = self.randoms["SESSIONS_NAME"]
else
config.cookie_name = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_NAME", config.cookie_name)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if redis_vars["USE_REDIS"] ~= "yes" then
config.storage = "cookie"
else
config.storage = "redis"
config.redis = {
prefix = "sessions_",
connect_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
pool = "bw-redis",
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
ssl = redis_vars["REDIS_SSL"] == "yes",
host = redis_vars["REDIS_HOST"],
port = tonumber(redis_vars["REDIS_PORT"]),
database = tonumber(redis_vars["REDIS_DATABASE"])
}
end
session.init(config)
return self:ret(true, "sessions init successful")
if self.is_loading or self.kind ~= "http" then
return self:ret(true, "init not needed")
end
-- Get redis vars
local redis_vars = {
["USE_REDIS"] = "",
["REDIS_HOST"] = "",
["REDIS_PORT"] = "",
["REDIS_DATABASE"] = "",
["REDIS_SSL"] = "",
["REDIS_TIMEOUT"] = "",
["REDIS_KEEPALIVE_IDLE"] = "",
["REDIS_KEEPALIVE_POOL"] = "",
}
for k, _ in pairs(redis_vars) do
local value, err = utils.get_variable(k, false)
if value == nil then
return self:ret(false, "can't get " .. k .. " variable : " .. err)
end
redis_vars[k] = value
end
-- Init configuration
local config = {
secret = self.variables["SESSIONS_SECRET"],
cookie_name = self.variables["SESSIONS_NAME"],
idling_timeout = tonumber(self.variables["SESSIONS_IDLING_TIMEOUT"]),
rolling_timeout = tonumber(self.variables["SESSIONS_ROLLING_TIMEOUT"]),
absolute_timeout = tonumber(self.variables["SESSIONS_ABSOLUTE_TIMEOUT"]),
}
if self.variables["SESSIONS_SECRET"] == "random" then
if self.randoms["SESSIONS_SECRET"] then
config.secret = self.randoms["SESSIONS_SECRET"]
else
config.secret = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_SECRET", config.secret)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if self.variables["SESSIONS_NAME"] == "random" then
if self.randoms["SESSIONS_NAME"] then
config.cookie_name = self.randoms["SESSIONS_NAME"]
else
config.cookie_name = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_NAME", config.cookie_name)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if redis_vars["USE_REDIS"] ~= "yes" then
config.storage = "cookie"
else
config.storage = "redis"
config.redis = {
prefix = "sessions_",
connect_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
pool = "bw-redis",
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
ssl = redis_vars["REDIS_SSL"] == "yes",
host = redis_vars["REDIS_HOST"],
port = tonumber(redis_vars["REDIS_PORT"]),
database = tonumber(redis_vars["REDIS_DATABASE"]),
}
end
session.init(config)
return self:ret(true, "sessions init successful")
end
return sessions

View File

@ -1,8 +1,8 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
local env = require "resty.env"
local ipmatcher = require "resty.ipmatcher"
local env = require "resty.env"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local whitelist = class("whitelist", plugin)
@ -23,7 +23,7 @@ function whitelist:initialize(ctx)
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {}
["URI"] = {},
}
for kind, _ in pairs(kinds) do
for data in self.variables["WHITELIST_" .. kind]:gmatch("%S+") do
@ -64,11 +64,11 @@ function whitelist:init()
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {}
["URI"] = {},
}
local i = 0
for kind, _ in pairs(whitelists) do
local f, err = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
local f, _ = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(whitelists[kind], line)
@ -123,13 +123,14 @@ function whitelist:access()
return self:ret(true, err, ngx.OK)
end
-- Perform checks
for k, v in pairs(already_cached) do
local ok
for k, _ in pairs(already_cached) do
if not already_cached[k] then
local ok, whitelisted = self:is_whitelisted(k)
ok, whitelisted = self:is_whitelisted(k)
if ok == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is whitelisted : " .. whitelisted)
else
local ok, err = self:add_to_cache(self:kind_to_ele(k), whitelisted)
ok, err = self:add_to_cache(self:kind_to_ele(k), whitelisted)
if not ok then
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
end
@ -163,7 +164,7 @@ end
function whitelist:check_cache()
-- Check the caches
local checks = {
["IP"] = "ip" .. self.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr,
}
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
@ -172,7 +173,7 @@ function whitelist:check_cache()
checks["URI"] = "uri" .. self.ctx.bw.uri
end
local already_cached = {}
for k, v in pairs(checks) do
for k, _ in pairs(checks) do
already_cached[k] = false
end
for k, v in pairs(checks) do
@ -242,14 +243,15 @@ function whitelist:is_whitelisted_ip()
end
if check_rdns then
-- Get rDNS
-- luacheck: ignore 421
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
-- Check if rDNS is in whitelist
if rdns_list then
local forward_check = nil
local rdns_suffix = nil
for i, rdns in ipairs(rdns_list) do
for j, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
for _, rdns in ipairs(rdns_list) do
for _, suffix in ipairs(self.lists["RDNS"]) do
if rdns:sub(-#suffix) == suffix then
forward_check = rdns
rdns_suffix = suffix
break
@ -262,12 +264,15 @@ function whitelist:is_whitelisted_ip()
if forward_check then
local ip_list, err = utils.get_ips(forward_check)
if ip_list then
for i, ip in ipairs(ip_list) do
for _, ip in ipairs(ip_list) do
if ip == self.ctx.bw.remote_addr then
return true, "rDNS " .. rdns_suffix
end
end
self.logger:log(ngx.WARN, "IP " .. self.ctx.bw.remote_addr .. " may spoof reverse DNS " .. forward_check)
self.logger:log(
ngx.WARN,
"IP " .. self.ctx.bw.remote_addr .. " may spoof reverse DNS " .. forward_check
)
else
self.logger:log(ngx.ERR, "error while getting rdns (forward check) : " .. err)
end
@ -283,7 +288,7 @@ function whitelist:is_whitelisted_ip()
if not asn then
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
else
for i, bl_asn in ipairs(self.lists["ASN"]) do
for _, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
@ -297,7 +302,7 @@ end
function whitelist:is_whitelisted_uri()
-- Check if URI is in whitelist
for i, uri in ipairs(self.lists["URI"]) do
for _, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
@ -308,7 +313,7 @@ end
function whitelist:is_whitelisted_ua()
-- Check if UA is in whitelist
for i, ua in ipairs(self.lists["USER_AGENT"]) do
for _, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end

4
stylua.toml Normal file
View File

@ -0,0 +1,4 @@
call_parentheses = "Input"
[sort_requires]
enabled = true