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 - id: prettier
name: Prettier Code Formatter 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 - repo: https://github.com/pycqa/flake8
rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6 # frozen: 6.1.0 rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6 # frozen: 6.1.0
hooks: 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 datastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils" local logger = require "bunkerweb.logger"
local logger = require "bunkerweb.logger" local process = require "ngx.process"
local cjson = require "cjson" local rsignal = require "resty.signal"
local upload = require "resty.upload" local upload = require "resty.upload"
local rsignal = require "resty.signal" local utils = require "bunkerweb.utils"
local process = require "ngx.process"
local api = class("api") local api = class("api")
api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} } api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
function api:initialize() function api:initialize()
self.datastore = datastore:new() self.datastore = datastore:new()
@ -26,6 +26,7 @@ function api:initialize()
end end
end end
-- luacheck: ignore 212
function api:log_cmd(cmd, status, stdout, stderr) function api:log_cmd(cmd, status, stdout, stderr)
local level = ngx.NOTICE local level = ngx.NOTICE
local prefix = "success" local prefix = "success"
@ -33,7 +34,7 @@ function api:log_cmd(cmd, status, stdout, stderr)
level = ngx.ERR level = ngx.ERR
prefix = "error" prefix = "error"
end 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 = " .. stdout)
self.logger:log(level, "stdout = " .. stderr) self.logger:log(level, "stdout = " .. stderr)
end end
@ -41,6 +42,7 @@ end
-- TODO : use this if we switch to OpenResty -- TODO : use this if we switch to OpenResty
function api:cmd(cmd) function api:cmd(cmd)
-- Non-blocking command -- Non-blocking command
-- luacheck: ignore 113
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, 10000) local ok, stdout, stderr, reason, status = shell.run(cmd, nil, 10000)
self.logger:log_cmd(cmd, status, stdout, stderr) self.logger:log_cmd(cmd, status, stdout, stderr)
-- Timeout -- Timeout
@ -51,6 +53,7 @@ function api:cmd(cmd)
return status == 0, reason, status return status == 0, reason, status
end end
-- luacheck: ignore 212
function api:response(http_status, api_status, msg) function api:response(http_status, api_status, msg)
local resp = {} local resp = {}
resp["status"] = api_status resp["status"] = api_status
@ -101,6 +104,7 @@ api.global.POST["^/confs$"] = function(self)
form:set_timeout(1000) form:set_timeout(1000)
local file = io.open(tmp, "w+") local file = io.open(tmp, "w+")
while true do while true do
-- luacheck: ignore 421
local typ, res, err = form:read() local typ, res, err = form:read()
if not typ then if not typ then
file:close() file:close()
@ -117,9 +121,9 @@ api.global.POST["^/confs$"] = function(self)
file:close() file:close()
local cmds = { local cmds = {
"rm -rf " .. destination .. "/*", "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) local status = os.execute(cmd)
if status ~= 0 then if status ~= 0 then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status)) return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
@ -176,17 +180,23 @@ end
api.global.GET["^/bans$"] = function(self) api.global.GET["^/bans$"] = function(self)
local data = {} 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 if k:find("^bans_ip_") then
local reason, err = self.datastore:get(k) local reason, err = self.datastore:get(k)
if err then if err then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", return self:response(
"can't access " .. k .. " from datastore : " .. reason) ngx.HTTP_INTERNAL_SERVER_ERROR,
"error",
"can't access " .. k .. " from datastore : " .. reason
)
end end
local ok, ttl = self.datastore:ttl(k) local ok, ttl = self.datastore:ttl(k)
if not ok then if not ok then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", return self:response(
"can't access ttl " .. k .. " from datastore : " .. ttl) ngx.HTTP_INTERNAL_SERVER_ERROR,
"error",
"can't access ttl " .. k .. " from datastore : " .. ttl
)
end end
local ban = { ip = k:sub(9, #k), reason = reason, exp = math.floor(ttl) } local ban = { ip = k:sub(9, #k), reason = reason, exp = math.floor(ttl) }
table.insert(data, ban) table.insert(data, ban)
@ -196,7 +206,7 @@ api.global.GET["^/bans$"] = function(self)
end end
api.global.GET["^/variables$"] = function(self) api.global.GET["^/variables$"] = function(self)
local variables, err = datastore:get('variables', true) local variables, err = datastore:get("variables", true)
if not variables then if not variables then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't access variables from datastore : " .. err) return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't access variables from datastore : " .. err)
end end
@ -219,9 +229,9 @@ function api:do_api_call()
if status ~= ngx.HTTP_OK then if status ~= ngx.HTTP_OK then
ret = false ret = false
end end
if (#resp["msg"] == 0) then if #resp["msg"] == 0 then
resp["msg"] = "" resp["msg"] = ""
elseif (type(resp["msg"]) == "table") then elseif type(resp["msg"]) == "table" then
resp["data"] = resp["msg"] resp["data"] = resp["msg"]
resp["msg"] = resp["status"] resp["msg"] = resp["status"]
end end
@ -231,10 +241,10 @@ function api:do_api_call()
end end
local list, err = self.datastore:get("plugins", true) local list, err = self.datastore:get("plugins", true)
if not list then 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) return false, resp["msg"], ngx.HTTP_INTERNAL_SERVER_ERROR, cjson.encode(resp)
end end
for i, plugin in ipairs(list) do for _, plugin in ipairs(list) do
if pcall(require, plugin.id .. "/" .. plugin.id) then if pcall(require, plugin.id .. "/" .. plugin.id) then
local plugin_lua = require(plugin.id .. "/" .. plugin.id) local plugin_lua = require(plugin.id .. "/" .. plugin.id)
if plugin_lua.api ~= nil then 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 clusterstore = require "bunkerweb.clusterstore"
local logger = require "bunkerweb.logger" local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils" local mlcache = require "resty.mlcache"
local class = require "middleclass" local utils = require "bunkerweb.utils"
local cachestore = class("cachestore") local cachestore = class("cachestore")
-- Instantiate mlcache object at module level (which will be cached when running init phase) -- Instantiate mlcache object at module level (which will be cached when running init phase)
-- TODO : custom settings -- TODO : custom settings
local shm = "cachestore" local shm = "cachestore"
local ipc_shm = "cachestore_ipc" local ipc_shm = "cachestore_ipc"
local shm_miss = "cachestore_miss" local shm_miss = "cachestore_miss"
local shm_locks = "cachestore_locks" local shm_locks = "cachestore_locks"
if not ngx.shared.cachestore then if not ngx.shared.cachestore then
shm = "cachestore_stream" shm = "cachestore_stream"
ipc_shm = "cachestore_ipc_stream" ipc_shm = "cachestore_ipc_stream"
shm_miss = "cachestore_miss_stream" shm_miss = "cachestore_miss_stream"
shm_locks = "cachestore_locks_stream" shm_locks = "cachestore_locks_stream"
end end
local cache, err = mlcache.new( local cache, err = mlcache.new("cachestore", shm, {
"cachestore", lru_size = 100,
shm, ttl = 30,
{ neg_ttl = 0.1,
lru_size = 100, shm_set_tries = 3,
ttl = 30, shm_miss = shm_miss,
neg_ttl = 0.1, shm_locks = shm_locks,
shm_set_tries = 3, resty_lock_opts = {
shm_miss = shm_miss, exptime = 30,
shm_locks = shm_locks, timeout = 5,
resty_lock_opts = { step = 0.001,
exptime = 30, ratio = 2,
timeout = 5, max_step = 0.5,
step = 0.001, },
ratio = 2, ipc_shm = ipc_shm,
max_step = 0.5 })
},
ipc_shm = ipc_shm
}
)
local module_logger = logger:new("CACHESTORE") local module_logger = logger:new("CACHESTORE")
if not cache then if not cache then
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err) module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
@ -57,10 +53,12 @@ function cachestore:initialize(use_redis, new_cs, ctx)
end end
function cachestore:get(key) function cachestore:get(key)
-- luacheck: ignore 432
local callback = function(key, cs) local callback = function(key, cs)
-- Connect to redis -- Connect to redis
-- luacheck: ignore 431
local clusterstore = cs or require "bunkerweb.clusterstore":new(false) local clusterstore = cs or require "bunkerweb.clusterstore":new(false)
local ok, err, reused = clusterstore:connect() local ok, err, _ = clusterstore:connect()
if not ok then if not ok then
return nil, "can't connect to redis : " .. err, nil return nil, "can't connect to redis : " .. err, nil
end end
@ -96,6 +94,7 @@ function cachestore:get(key)
local callback_no_miss = function() local callback_no_miss = function()
return nil, nil, -1 return nil, nil, -1
end end
-- luacheck: ignore 431
local value, err, hit_level local value, err, hit_level
if self.use_redis and utils.is_cosocket_available() then if self.use_redis and utils.is_cosocket_available() then
local cs = nil local cs = nil
@ -114,13 +113,14 @@ function cachestore:get(key)
end end
function cachestore:set(key, value, ex) function cachestore:set(key, value, ex)
-- luacheck: ignore 431
local ok, err
if self.use_redis and utils.is_cosocket_available() then 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 if not ok then
self.logger:log(ngx.ERR, err) self.logger:log(ngx.ERR, err)
end end
end end
local ok, err
if ex then if ex then
ok, err = self.cache:set(key, { ttl = ex }, value) ok, err = self.cache:set(key, { ttl = ex }, value)
else else
@ -134,13 +134,14 @@ end
function cachestore:set_redis(key, value, ex) function cachestore:set_redis(key, value, ex)
-- Connect to redis -- Connect to redis
local ok, err, reused = self.clusterstore:connect() -- luacheck: ignore 431
local ok, err, _ = self.clusterstore:connect()
if not ok then if not ok then
return false, "can't connect to redis : " .. err return false, "can't connect to redis : " .. err
end end
-- Set value with ttl -- Set value with ttl
local default_ex = ex or 30 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 if err then
self.clusterstore:close() self.clusterstore:close()
return false, "SET failed : " .. err return false, "SET failed : " .. err
@ -149,14 +150,16 @@ function cachestore:set_redis(key, value, ex)
return true return true
end 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 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 if not ok then
self.logger:log(ngx.ERR, err) self.logger:log(ngx.ERR, err)
end end
end end
local ok, err = self.cache:delete(key) ok, err = self.cache:delete(key)
if not ok then if not ok then
return false, err return false, err
end end
@ -165,12 +168,13 @@ end
function cachestore:del_redis(key) function cachestore:del_redis(key)
-- Connect to redis -- Connect to redis
-- luacheck: ignore 431
local ok, err = self.clusterstore:connect() local ok, err = self.clusterstore:connect()
if not ok then if not ok then
return false, "can't connect to redis : " .. err return false, "can't connect to redis : " .. err
end end
-- Set value with ttl -- Set value with ttl
local ok, err = self.clusterstore:del(key) local _, err = self.clusterstore:del(key)
if err then if err then
self.clusterstore:close() self.clusterstore:close()
return false, "DEL failed : " .. err return false, "DEL failed : " .. err

View File

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

View File

@ -1,11 +1,12 @@
local class = require "middleclass" local class = require "middleclass"
local lrucache = require "resty.lrucache" local lrucache = require "resty.lrucache"
local datastore = class("datastore") local datastore = class("datastore")
local lru, err = lrucache.new(100000) local lru, err = lrucache.new(100000)
if not lru then if not lru then
require "bunkerweb.logger":new("DATASTORE"):log(ngx.ERR, require "bunkerweb.logger"
"failed to instantiate LRU cache : " .. (err or "unknown error")) :new("DATASTORE")
:log(ngx.ERR, "failed to instantiate LRU cache : " .. (err or "unknown error"))
end end
function datastore:initialize() function datastore:initialize()
@ -16,11 +17,13 @@ function datastore:initialize()
end end
function datastore:get(key, worker) function datastore:get(key, worker)
-- luacheck: ignore 431
local value, err
if worker then if worker then
local value, err = lru:get(key) value, err = lru:get(key)
return value, err or "not found" return value, err or "not found"
end end
local value, err = self.dict:get(key) value, err = self.dict:get(key)
if not value and not err then if not value and not err then
err = "not found" err = "not found"
end end
@ -52,10 +55,11 @@ function datastore:keys(worker)
return self.dict:get_keys(0) return self.dict:get_keys(0)
end end
function datastore:ttl(key) function datastore:ttl(key, worker)
if worker then if worker then
return false, "not supported by LRU" return false, "not supported by LRU"
end end
-- luacheck: ignore 431
local ttl, err = self.dict:ttl(key) local ttl, err = self.dict:ttl(key)
if not ttl then if not ttl then
return false, err return false, err
@ -64,13 +68,13 @@ function datastore:ttl(key)
end end
function datastore:delete_all(pattern, worker) function datastore:delete_all(pattern, worker)
local keys = {} local keys
if worker then if worker then
keys = lru:keys(0) keys = lru:keys(0)
else else
keys = self.dict:get_keys(0) keys = self.dict:get_keys(0)
end end
for i, key in ipairs(keys) do for _, key in ipairs(keys) do
if key:match(pattern) then if key:match(pattern) then
self.dict:delete(key) self.dict:delete(key)
end end
@ -78,6 +82,7 @@ function datastore:delete_all(pattern, worker)
return true, "success" return true, "success"
end end
-- luacheck: ignore 212
function datastore:flush_lru() function datastore:flush_lru()
lru:flush_all() lru:flush_all()
end 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) helpers.load_plugin = function(json)
-- Open file -- Open file
local file, err, nb = io.open(json, "r") local file, err, nb = io.open(json, "r")
if not file then if not file then
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")" return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
end end
-- Decode JSON -- Decode JSON
local ok, plugin = pcall(cjson.decode, file:read("*a")) local ok, plugin = pcall(cjson.decode, file:read("*a"))
file:close() file:close()
if not ok then if not ok then
return false, "invalid JSON at " .. json .. " : " .. err return false, "invalid JSON at " .. json .. " : " .. err
end end
-- Check fields -- Check fields
local missing_fields = {} local missing_fields = {}
local required_fields = { "id", "name", "description", "version", "settings", "stream" } local required_fields = { "id", "name", "description", "version", "settings", "stream" }
for i, field in ipairs(required_fields) do for _, field in ipairs(required_fields) do
if plugin[field] == nil then if plugin[field] == nil then
table.insert(missing_fields, field) table.insert(missing_fields, field)
end end
end end
if #missing_fields > 0 then if #missing_fields > 0 then
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
end end
-- Try require -- Try require
local plugin_lua, err = helpers.require_plugin(plugin.id) local plugin_lua, err = helpers.require_plugin(plugin.id)
if plugin_lua == false then if plugin_lua == false then
return false, err return false, err
end end
-- Fill phases -- Fill phases
local phases = utils.get_phases() local phases = utils.get_phases()
plugin.phases = {} plugin.phases = {}
if plugin_lua then if plugin_lua then
for i, phase in ipairs(phases) do for _, phase in ipairs(phases) do
if plugin_lua[phase] ~= nil then if plugin_lua[phase] ~= nil then
table.insert(plugin.phases, phase) table.insert(plugin.phases, phase)
end end
end end
end end
-- Return plugin -- Return plugin
return true, plugin return true, plugin
end end
helpers.order_plugins = function(plugins) helpers.order_plugins = function(plugins)
-- Extract orders -- Extract orders
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r") local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
if not file then if not file then
return false, err .. " (nb = " .. tostring(nb) .. ")" return false, err .. " (nb = " .. tostring(nb) .. ")"
end end
local ok, orders = pcall(cjson.decode, file:read("*a")) local ok, orders = pcall(cjson.decode, file:read("*a"))
file:close() file:close()
if not ok then if not ok then
return false, "invalid order.json : " .. err return false, "invalid order.json : " .. err
end end
-- Compute plugins/id/phases table -- Compute plugins/id/phases table
local plugins_phases = {} local plugins_phases = {}
for i, plugin in ipairs(plugins) do for _, plugin in ipairs(plugins) do
plugins_phases[plugin.id] = {} plugins_phases[plugin.id] = {}
for j, phase in ipairs(plugin.phases) do for _, phase in ipairs(plugin.phases) do
plugins_phases[plugin.id][phase] = true plugins_phases[plugin.id][phase] = true
end end
end end
-- Order result -- Order result
local result_orders = {} local result_orders = {}
for i, phase in ipairs(utils.get_phases()) do for _, phase in ipairs(utils.get_phases()) do
result_orders[phase] = {} result_orders[phase] = {}
end end
-- Fill order first -- Fill order first
for phase, order in pairs(orders) do for phase, order in pairs(orders) do
for i, id in ipairs(order) do for _, id in ipairs(order) do
local plugin = plugins_phases[id] local plugin = plugins_phases[id]
if plugin and plugin[phase] then if plugin and plugin[phase] then
table.insert(result_orders[phase], id) table.insert(result_orders[phase], id)
plugin[phase] = nil plugin[phase] = nil
end end
end end
end end
-- Then append missing plugins to the end -- Then append missing plugins to the end
for i, phase in ipairs(utils.get_phases()) do for _, phase in ipairs(utils.get_phases()) do
for id, plugin in pairs(plugins_phases) do for id, plugin in pairs(plugins_phases) do
if plugin[phase] then if plugin[phase] then
table.insert(result_orders[phase], id) table.insert(result_orders[phase], id)
plugin[phase] = nil plugin[phase] = nil
end end
end end
end end
return true, result_orders return true, result_orders
end end
helpers.require_plugin = function(id) helpers.require_plugin = function(id)
-- Require call -- Require call
local ok, plugin_lua = pcall(require, id .. "/" .. id) local ok, plugin_lua = pcall(require, id .. "/" .. id)
if not ok then if not ok then
if plugin_lua:match("not found") then if plugin_lua:match("not found") then
return nil, "plugin " .. id .. " doesn't have LUA code" return nil, "plugin " .. id .. " doesn't have LUA code"
end end
return false, "require error for plugin " .. id .. " : " .. plugin_lua return false, "require error for plugin " .. id .. " : " .. plugin_lua
end end
-- New call -- New call
if plugin_lua.new == nil then if plugin_lua.new == nil then
return false, "missing new() method for plugin " .. id return false, "missing new() method for plugin " .. id
end end
-- Return plugin -- Return plugin
return plugin_lua, "require() call successful for plugin " .. id return plugin_lua, "require() call successful for plugin " .. id
end end
helpers.new_plugin = function(plugin_lua, ctx) helpers.new_plugin = function(plugin_lua, ctx)
-- Require call -- Require call
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx) local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx)
if not ok then if not ok then
return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj
end end
return true, plugin_obj return true, plugin_obj
end end
helpers.call_plugin = function(plugin, method) helpers.call_plugin = function(plugin, method)
-- Check if method is present -- Check if method is present
if plugin[method] == nil then if plugin[method] == nil then
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id() return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
end end
-- Call method -- Call method
local ok, ret = pcall(plugin[method], plugin) local ok, ret = pcall(plugin[method], plugin)
if not ok then if not ok then
return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret
end end
if ret == nil then if ret == nil then
return false, plugin:get_id() .. ":" .. method .. "() returned nil value" return false, plugin:get_id() .. ":" .. method .. "() returned nil value"
end end
-- Check values -- Check values
local missing_values = {} local missing_values = {}
local required_values = { "ret", "msg" } local required_values = { "ret", "msg" }
for i, value in ipairs(required_values) do for _, value in ipairs(required_values) do
if ret[value] == nil then if ret[value] == nil then
table.insert(missing_values, value) table.insert(missing_values, value)
end end
end end
if #missing_values > 0 then if #missing_values > 0 then
return false, "missing required return value(s) : " .. cjson.encode(missing_values) return false, "missing required return value(s) : " .. cjson.encode(missing_values)
end end
-- Return -- Return
return true, ret return true, ret
end end
helpers.fill_ctx = function() helpers.fill_ctx = function()
-- Return errors as table -- Return errors as table
local errors = {} local errors = {}
local ctx = ngx.ctx local ctx = ngx.ctx
-- Check if ctx is already filled -- Check if ctx is already filled
if not ctx.bw then if not ctx.bw then
-- Instantiate bw table -- Instantiate bw table
local data = {} local data = {}
-- Common vars -- Common vars
data.kind = "http" data.kind = "http"
if ngx.shared.datastore_stream then if ngx.shared.datastore_stream then
data.kind = "stream" data.kind = "stream"
end end
data.remote_addr = ngx.var.remote_addr data.remote_addr = ngx.var.remote_addr
data.server_name = ngx.var.server_name data.server_name = ngx.var.server_name
if data.kind == "http" then if data.kind == "http" then
data.uri = ngx.var.uri data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type data.http_content_type = ngx.var.http_content_type
data.http_content_length = ngx.var.http_content_length data.http_content_length = ngx.var.http_content_length
data.http_origin = ngx.var.http_origin data.http_origin = ngx.var.http_origin
data.http_version = ngx.req.http_version() data.http_version = ngx.req.http_version()
data.scheme = ngx.var.scheme data.scheme = ngx.var.scheme
end end
-- IP data : global -- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr) local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then if ip_is_global == nil then
table.insert(errors, "can't check if IP is global : " .. err) table.insert(errors, "can't check if IP is global : " .. err)
else else
data.ip_is_global = ip_is_global data.ip_is_global = ip_is_global
end end
-- IP data : v4 / v6 -- IP data : v4 / v6
data.ip_is_ipv4 = utils.is_ipv4(data.ip) data.ip_is_ipv4 = utils.is_ipv4(data.ip)
data.ip_is_ipv6 = utils.is_ipv6(data.ip) data.ip_is_ipv6 = utils.is_ipv6(data.ip)
-- Misc info -- Misc info
data.integration = utils.get_integration() data.integration = utils.get_integration()
data.version = utils.get_version() data.version = utils.get_version()
-- Fill ctx -- Fill ctx
ctx.bw = data ctx.bw = data
end end
-- Always create new objects for current phases in case of cosockets -- Always create new objects for current phases in case of cosockets
local use_redis, err = utils.get_variable("USE_REDIS", false) local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then if not use_redis then
table.insert(errors, "can't get variable from datastore : " .. err) table.insert(errors, "can't get variable from datastore : " .. err)
end end
ctx.bw.datastore = require "bunkerweb.datastore":new() ctx.bw.datastore = require "bunkerweb.datastore":new()
ctx.bw.clusterstore = require "bunkerweb.clusterstore":new() ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes") ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors, ctx return true, "ctx filled", errors, ctx
end end
function helpers.load_variables(all_variables, plugins) function helpers.load_variables(all_variables, plugins)
-- Extract settings from plugins and global ones -- Extract settings from plugins and global ones
local all_settings = {} local all_settings = {}
for i, plugin in ipairs(plugins) do for _, plugin in ipairs(plugins) do
if plugin.settings then if plugin.settings then
for setting, data in pairs(plugin.settings) do for setting, data in pairs(plugin.settings) do
all_settings[setting] = data all_settings[setting] = data
end end
end end
end end
local file = io.open("/usr/share/bunkerweb/settings.json") local file = io.open("/usr/share/bunkerweb/settings.json")
if not file then if not file then
return false, "can't open settings.json" return false, "can't open settings.json"
end end
local ok, settings = pcall(cjson.decode, file:read("*a")) local ok, settings = pcall(cjson.decode, file:read("*a"))
file:close() file:close()
if not ok then if not ok then
return false, "invalid settings.json : " .. err return false, "invalid settings.json : " .. settings
end end
for setting, data in pairs(settings) do for setting, data in pairs(settings) do
all_settings[setting] = data all_settings[setting] = data
end end
-- Extract vars -- Extract vars
local variables = { ["global"] = {} } local variables = { ["global"] = {} }
local multisite = all_variables["MULTISITE"] == "yes" local multisite = all_variables["MULTISITE"] == "yes"
local server_names = {} local server_names = {}
if multisite then if multisite then
for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do
variables[server_name] = {} variables[server_name] = {}
table.insert(server_names, server_name) table.insert(server_names, server_name)
end end
end end
for setting, data in pairs(all_settings) do for setting, data in pairs(all_settings) do
if all_variables[setting] then if all_variables[setting] then
variables["global"][setting] = all_variables[setting] variables["global"][setting] = all_variables[setting]
end end
if data.multiple then if data.multiple then
for variable, value in pairs(all_variables) do for variable, value in pairs(all_variables) do
local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))") local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))")
if multiple_setting then if multiple_setting then
if multisite and server_name and server_name:match("%S+_$") then if multisite and server_name and server_name:match("%S+_$") then
variables[server_name:sub(1, -2)][multiple_setting] = value variables[server_name:sub(1, -2)][multiple_setting] = value
else else
variables["global"][multiple_setting] = value variables["global"][multiple_setting] = value
end end
end end
end end
end end
if multisite then if multisite then
for i, server_name in ipairs(server_names) do for _, server_name in ipairs(server_names) do
local key = server_name .. "_" .. setting local key = server_name .. "_" .. setting
if all_variables[key] then if all_variables[key] then
variables[server_name][setting] = all_variables[key] variables[server_name][setting] = all_variables[key]
end end
end end
end end
end end
return true, variables return true, variables
end end
return helpers return helpers

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
local class = require "middleclass" local class = require "middleclass"
local plugin = require "bunkerweb.plugin" 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) function cors:initialize(ctx)
-- Call parent initialize -- Call parent initialize
@ -17,7 +17,7 @@ function cors:initialize(ctx)
["CORS_MAX_AGE"] = "Access-Control-Max-Age", ["CORS_MAX_AGE"] = "Access-Control-Max-Age",
["CORS_ALLOW_CREDENTIALS"] = "Access-Control-Allow-Credentials", ["CORS_ALLOW_CREDENTIALS"] = "Access-Control-Allow-Credentials",
["CORS_ALLOW_METHODS"] = "Access-Control-Allow-Methods", ["CORS_ALLOW_METHODS"] = "Access-Control-Allow-Methods",
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers" ["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers",
} }
end end
@ -43,7 +43,12 @@ function cors:header()
ngx.header.Vary = "Origin" ngx.header.Vary = "Origin"
end end
-- Check if Origin is allowed -- 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") 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") return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
end end
@ -81,9 +86,17 @@ function cors:access()
return self:ret(true, "service doesn't use CORS") return self:ret(true, "service doesn't use CORS")
end end
-- Deny as soon as possible if needed -- 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 if
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access", self.ctx.bw.http_origin
utils.get_deny_status(self.ctx)) 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 end
-- Send CORS policy with a 204 (no content) status -- Send CORS policy with a 204 (no content) status
if self.ctx.bw.request_method == "OPTIONS" and self.ctx.bw.http_origin then 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 cjson = require "cjson"
local plugin = require "bunkerweb.plugin" local class = require "middleclass"
local utils = require "bunkerweb.utils" local plugin = require "bunkerweb.plugin"
local cjson = require "cjson" local utils = require "bunkerweb.utils"
local country = class("country", plugin) local country = class("country", plugin)
@ -16,17 +16,28 @@ function country:access()
return self:ret(true, "country not activated") return self:ret(true, "country not activated")
end end
-- Check if IP is in cache -- 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 if data then
data = cjson.decode(data) data = cjson.decode(data)
if data.result == "ok" then if data.result == "ok" then
return self:ret(true, return self:ret(
"client IP " .. true,
self.ctx.bw.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")") "client IP "
.. self.ctx.bw.remote_addr
.. " is in country cache (not blacklisted, country = "
.. data.country
.. ")"
)
end end
return self:ret(true, return self:ret(
"client IP " .. self.ctx.bw.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")", true,
utils.get_deny_status(self.ctx)) "client IP "
.. self.ctx.bw.remote_addr
.. " is in country cache (blacklisted, country = "
.. data.country
.. ")",
utils.get_deny_status(self.ctx)
)
end end
-- Don't go further if IP is not global -- Don't go further if IP is not global
@ -39,50 +50,64 @@ function country:access()
end end
-- Get the country of client -- Get the country of client
local country, err = utils.get_country(self.ctx.bw.remote_addr) local country_data, err = utils.get_country(self.ctx.bw.remote_addr)
if not country then if not country_data then
return self:ret(false, "can't get country of client IP " .. self.ctx.bw.remote_addr .. " : " .. err) return self:ret(false, "can't get country of client IP " .. self.ctx.bw.remote_addr .. " : " .. err)
end end
-- Process whitelist first -- Process whitelist first
if self.variables["WHITELIST_COUNTRY"] ~= "" then if self.variables["WHITELIST_COUNTRY"] ~= "" then
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch("%S+") do for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch "%S+" do
if wh_country == country then if wh_country == country_data then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok") -- luacheck: ignore 421
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ok")
if not ok then if not ok then
return self:ret(false, "error while adding item to cache : " .. err) return self:ret(false, "error while adding item to cache : " .. err)
end 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
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 if not ok then
return self:ret(false, "error while adding item to cache : " .. err) return self:ret(false, "error while adding item to cache : " .. err)
end end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country .. ")", return self:ret(
utils.get_deny_status(self.ctx)) true,
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
utils.get_deny_status(self.ctx)
)
end end
-- And then blacklist -- And then blacklist
if self.variables["BLACKLIST_COUNTRY"] ~= "" then if self.variables["BLACKLIST_COUNTRY"] ~= "" then
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch("%S+") do for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch "%S+" do
if bl_country == country then if bl_country == country_data then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko") local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ko")
if not ok then if not ok then
return self:ret(false, "error while adding item to cache : " .. err) return self:ret(false, "error while adding item to cache : " .. err)
end end
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country .. ")", return self:ret(
utils.get_deny_status(self.ctx)) true,
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",
utils.get_deny_status(self.ctx)
)
end end
end end
end end
-- Country IP is not in blacklist -- 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 if not ok then
return self:ret(false, "error while caching IP " .. self.ctx.bw.remote_addr .. " : " .. err) return self:ret(false, "error while caching IP " .. self.ctx.bw.remote_addr .. " : " .. err)
end 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 end
function country:preread() function country:preread()
@ -97,9 +122,12 @@ function country:is_in_cache(ip)
return true, data return true, data
end end
function country:add_to_cache(ip, country, result) function country:add_to_cache(ip, country_data, result)
local ok, err = self.cachestore:set("plugin_country_" .. self.ctx.bw.server_name .. ip, local ok, err = self.cachestore:set(
cjson.encode({ country = country, result = result }), 86400) "plugin_country_" .. self.ctx.bw.server_name .. ip,
cjson.encode { country = country_data, result = result },
86400
)
if not ok then if not ok then
return false, err return false, err
end end

View File

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

View File

@ -1,5 +1,5 @@
local class = require "middleclass" local class = require "middleclass"
local plugin = require "bunkerweb.plugin" local plugin = require "bunkerweb.plugin"
local template = nil local template = nil
if ngx.shared.datastore then if ngx.shared.datastore then
template = require "resty.template" template = require "resty.template"
@ -14,52 +14,52 @@ function errors:initialize(ctx)
self.default_errors = { self.default_errors = {
["400"] = { ["400"] = {
title = "Bad Request", title = "Bad Request",
text = "The server did not understand the request." text = "The server did not understand the request.",
}, },
["401"] = { ["401"] = {
title = "Not Authorized", title = "Not Authorized",
text = "Valid authentication credentials needed for the target resource." text = "Valid authentication credentials needed for the target resource.",
}, },
["403"] = { ["403"] = {
title = "Forbidden", title = "Forbidden",
text = "Access is forbidden to the requested page." text = "Access is forbidden to the requested page.",
}, },
["404"] = { ["404"] = {
title = "Not Found", title = "Not Found",
text = "The server cannot find the requested page." text = "The server cannot find the requested page.",
}, },
["405"] = { ["405"] = {
title = "Method Not Allowed", 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"] = { ["413"] = {
title = "Request Entity Too Large", 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"] = { ["429"] = {
title = "Too Many Requests", 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"] = { ["500"] = {
title = "Internal Server Error", 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"] = { ["501"] = {
title = "Not Implemented", 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"] = { ["502"] = {
title = "Bad Gateway", 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"] = { ["503"] = {
title = "Service Unavailable", 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"] = { ["504"] = {
title = "Gateway Timeout", title = "Gateway Timeout",
text = "The gateway has timed out." text = "The gateway has timed out.",
} },
} }
end end
@ -69,7 +69,7 @@ function errors:render_template(code)
title = code .. " - " .. self.default_errors[code].title, title = code .. " - " .. self.default_errors[code].title,
error_title = self.default_errors[code].title, error_title = self.default_errors[code].title,
error_code = code, error_code = code,
error_text = self.default_errors[code].text error_text = self.default_errors[code].text,
}) })
end end

View File

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

View File

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

View File

@ -1,6 +1,6 @@
local class = require "middleclass" local cjson = require "cjson"
local plugin = require "bunkerweb.plugin" local class = require "middleclass"
local cjson = require "cjson" local plugin = require "bunkerweb.plugin"
local letsencrypt = class("letsencrypt", plugin) local letsencrypt = class("letsencrypt", plugin)
@ -17,9 +17,12 @@ function letsencrypt:access()
return self:ret(true, "success") return self:ret(true, "success")
end end
-- luacheck: ignore 212
function letsencrypt:api(ctx) function letsencrypt:api(ctx)
if not string.match(ctx.bw.uri, "^/lets%-encrypt/challenge$") or if
(ctx.bw.request_method ~= "POST" and ctx.bw.request_method ~= "DELETE") then 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 return false, nil, nil
end end
local acme_folder = "/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/" 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 if ctx.bw.request_method == "POST" then
local file, err = io.open(acme_folder .. data.token, "w+") local file, err = io.open(acme_folder .. data.token, "w+")
if not file then 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 end
file:write(data.validation) file:write(data.validation)
file:close() file:close()
@ -40,7 +45,9 @@ function letsencrypt:api(ctx)
elseif ctx.bw.request_method == "DELETE" then elseif ctx.bw.request_method == "DELETE" then
local ok, err = os.remove(acme_folder .. data.token) local ok, err = os.remove(acme_folder .. data.token)
if not ok then 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 end
return true, ngx.HTTP_OK, { status = "success", msg = "validation token removed" } return true, ngx.HTTP_OK, { status = "success", msg = "validation token removed" }
end 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 plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils" local utils = require "bunkerweb.utils"
local cjson = require "cjson"
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) function limit:initialize(ctx)
-- Call parent initialize -- Call parent initialize
@ -11,7 +42,6 @@ function limit:initialize(ctx)
-- Load rules if needed -- Load rules if needed
if ngx.get_phase() ~= "init" and self:is_needed() then if ngx.get_phase() ~= "init" and self:is_needed() then
-- Get all rules from datastore -- Get all rules from datastore
local limited = false
local all_rules, err = self.datastore:get("plugin_limit_rules", true) local all_rules, err = self.datastore:get("plugin_limit_rules", true)
if not all_rules then if not all_rules then
self.logger:log(ngx.ERR, err) self.logger:log(ngx.ERR, err)
@ -93,19 +123,16 @@ function limit:access()
return self:ret(true, "limit request not enabled") return self:ret(true, "limit request not enabled")
end end
-- Check if URI is limited -- Check if URI is limited
local rate = nil local rate
local uri = nil
for k, v in pairs(self.rules) do for k, v in pairs(self.rules) do
if k ~= "/" and utils.regex_match(self.ctx.bw.uri, k) then if k ~= "/" and utils.regex_match(self.ctx.bw.uri, k) then
rate = v rate = v
uri = k
break break
end end
end end
if not rate then if not rate then
if self.rules["/"] then if self.rules["/"] then
rate = self.rules["/"] rate = self.rules["/"]
uri = "/"
else else
return self:ret(true, "no rule for " .. self.ctx.bw.uri) return self:ret(true, "no rule for " .. self.ctx.bw.uri)
end end
@ -118,19 +145,37 @@ function limit:access()
end end
-- Limit reached -- Limit reached
if limited then if limited then
return self:ret(true, return self:ret(
"client IP " .. true,
self.ctx.bw.remote_addr .. "client IP "
" is limited for URL " .. .. self.ctx.bw.remote_addr
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", .. " is limited for URL "
ngx.HTTP_TOO_MANY_REQUESTS) .. self.ctx.bw.uri
.. " (current rate = "
.. current_rate
.. "r/"
.. rate_time
.. " and max rate = "
.. rate
.. ")",
ngx.HTTP_TOO_MANY_REQUESTS
)
end end
-- Limit not reached -- Limit not reached
return self:ret(true, return self:ret(
"client IP " .. true,
self.ctx.bw.remote_addr .. "client IP "
" is not limited for URL " .. .. self.ctx.bw.remote_addr
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")") .. " is not limited for URL "
.. self.ctx.bw.uri
.. " (current rate = "
.. current_rate
.. "r/"
.. rate_time
.. " and max rate = "
.. rate
.. ")"
)
end end
function limit:limit_req(rate_max, rate_time) function limit:limit_req(rate_max, rate_time)
@ -143,9 +188,12 @@ function limit:limit_req(rate_max, rate_time)
else else
timestamps = redis_timestamps timestamps = redis_timestamps
-- Save the new timestamps -- Save the new timestamps
-- luacheck: ignore 421
local ok, err = self.datastore:set( local ok, err = self.datastore:set(
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri, "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 if not ok then
return nil, "can't update timestamps : " .. err return nil, "can't update timestamps : " .. err
end end
@ -167,8 +215,8 @@ end
function limit:limit_req_local(rate_max, rate_time) function limit:limit_req_local(rate_max, rate_time)
-- Get timestamps -- Get timestamps
local timestamps, err = self.datastore:get("plugin_limit_" .. local timestamps, err =
self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri) 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 if not timestamps and err ~= "not found" then
return nil, err return nil, err
elseif err == "not found" then elseif err == "not found" then
@ -176,12 +224,15 @@ function limit:limit_req_local(rate_max, rate_time)
end end
timestamps = cjson.decode(timestamps) timestamps = cjson.decode(timestamps)
-- Compute new 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 -- Save new timestamps if needed
if updated then if updated then
-- luacheck: ignore 421
local ok, err = self.datastore:set( local ok, err = self.datastore:set(
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri, "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 if not ok then
return nil, err return nil, err
end end
@ -245,9 +296,15 @@ function limit:limit_req_redis(rate_max, rate_time)
return nil, err return nil, err
end end
-- Execute script -- Execute script
local timestamps, err = self.clusterstore:call("eval", redis_script, 1, local timestamps, err = self.clusterstore:call(
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri, rate_max, rate_time, "eval",
os.time(os.date("!*t"))) 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 if not timestamps then
self.clusterstore:close() self.clusterstore:close()
return nil, err return nil, err
@ -257,35 +314,4 @@ function limit:limit_req_redis(rate_max, rate_time)
return timestamps, "success" return timestamps, "success"
end 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 return limit

View File

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

View File

@ -1,9 +1,9 @@
local class = require "middleclass" local class = require "middleclass"
local plugin = require "bunkerweb.plugin" 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 -- Call parent initialize
plugin.initialize(self, "redis", ctx) plugin.initialize(self, "redis", ctx)
end end

View File

@ -1,153 +1,155 @@
local class = require "middleclass" local class = require "middleclass"
local plugin = require "bunkerweb.plugin" local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils" local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local reversescan = class("reversescan", plugin) local reversescan = class("reversescan", plugin)
function reversescan:initialize(ctx) function reversescan:initialize(ctx)
-- Call parent initialize -- Call parent initialize
plugin.initialize(self, "reversescan", ctx) plugin.initialize(self, "reversescan", ctx)
end end
function reversescan:access() function reversescan:access()
-- Check if access is needed -- Check if access is needed
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
return self:ret(true, "reverse scan not activated") return self:ret(true, "reverse scan not activated")
end end
-- Loop on ports -- Loop on ports
local threads = {} local threads = {}
local ret_threads = nil local ret_threads = nil
local ret_err = nil local ret_err = nil
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
-- Check if the scan is already cached -- Check if the scan is already cached
local ok, cached = self:is_in_cache(self.ctx.bw.remote_addr .. ":" .. port) local ok, cached = self:is_in_cache(self.ctx.bw.remote_addr .. ":" .. port)
if not ok then if not ok then
ret_threads = false ret_threads = false
ret_err = "error getting info from cachestore : " .. cached ret_err = "error getting info from cachestore : " .. cached
break break
-- Deny access if port opened -- Deny access if port opened
elseif cached == "open" then elseif cached == "open" then
ret_threads = true ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
break break
-- Perform scan in a thread -- Perform scan in a thread
elseif not cached then elseif not cached then
local thread = ngx.thread.spawn(self.scan, self.ctx.bw.remote_addr, tonumber(port), local thread = ngx.thread.spawn(
tonumber(self.variables["REVERSE_SCAN_TIMEOUT"])) self.scan,
threads[port] = thread self.ctx.bw.remote_addr,
end tonumber(port),
end tonumber(self.variables["REVERSE_SCAN_TIMEOUT"])
if ret_threads ~= nil then )
if #threads > 0 then threads[port] = thread
local wait_threads = {} end
for port, thread in pairs(threads) do end
table.insert(wait_threads, thread) if ret_threads ~= nil then
end if #threads > 0 then
utils.kill_all_threads(wait_threads) local wait_threads = {}
end for _, thread in pairs(threads) do
-- Open port case table.insert(wait_threads, thread)
if ret_threads then end
return self:ret(true, ret_err, utils.get_deny_status(self.ctx)) utils.kill_all_threads(wait_threads)
end end
-- Error case -- Open port case
return self:ret(false, ret_err) if ret_threads then
end return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
-- Check results of threads end
ret_threads = nil -- Error case
ret_err = nil return self:ret(false, ret_err)
local results = {} end
while true do -- Check results of threads
-- Compute threads to wait ret_threads = nil
local wait_threads = {} ret_err = nil
for port, thread in pairs(threads) do local results = {}
table.insert(wait_threads, thread) while true do
end -- Compute threads to wait
-- No port opened local wait_threads = {}
if #wait_threads == 0 then for _, thread in pairs(threads) do
break table.insert(wait_threads, thread)
end end
-- Wait for first thread -- No port opened
local ok, open, port = ngx.thread.wait(unpack(wait_threads)) if #wait_threads == 0 then
-- Error case break
if not ok then end
ret_threads = false -- Wait for first thread
ret_err = "error while waiting thread : " .. open local ok, open, port = ngx.thread.wait(unpack(wait_threads))
break -- Error case
end if not ok then
port = tostring(port) ret_threads = false
-- Remove thread from list ret_err = "error while waiting thread : " .. open
threads[port] = nil break
-- Add result to cache end
local result = "close" port = tostring(port)
if open then -- Remove thread from list
result = "open" threads[port] = nil
end -- Add result to cache
results[port] = result local result = "close"
-- Port is opened if open then
if open then result = "open"
ret_threads = true end
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr results[port] = result
break -- Port is opened
end if open then
end ret_threads = true
-- Kill running threads ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
if #threads > 0 then break
local wait_threads = {} end
for port, thread in pairs(threads) do end
table.insert(wait_threads, thread) -- Kill running threads
end if #threads > 0 then
utils.kill_all_threads(wait_threads) local wait_threads = {}
end for _, thread in pairs(threads) do
-- Cache results table.insert(wait_threads, thread)
for port, result in pairs(results) do end
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr .. ":" .. port, result) utils.kill_all_threads(wait_threads)
if not ok then end
return self:ret(false, "error while adding element to cache : " .. err) -- Cache results
end for port, result in pairs(results) do
end local ok, err = self:add_to_cache(self.ctx.bw.remote_addr .. ":" .. port, result)
if ret_threads ~= nil then if not ok then
-- Open port case return self:ret(false, "error while adding element to cache : " .. err)
if ret_threads then end
return self:ret(true, ret_err, utils.get_deny_status(self.ctx)) end
end if ret_threads ~= nil then
-- Error case -- Open port case
return self:ret(false, ret_err) if ret_threads then
end return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
-- No port opened end
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr) -- 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 end
function reversescan:preread() function reversescan:preread()
return self:access() return self:access()
end end
function reversescan.scan(ip, port, timeout) function reversescan.scan(ip, port, timeout)
local tcpsock = ngx.socket.tcp() local tcpsock = ngx.socket.tcp()
tcpsock:settimeout(timeout) tcpsock:settimeout(timeout)
local ok, err = tcpsock:connect(ip, port) local ok, _ = tcpsock:connect(ip, port)
tcpsock:close() tcpsock:close()
if not ok then if not ok then
return false, port return false, port
end end
return true, port return true, port
end end
function reversescan:is_in_cache(ip_port) function reversescan:is_in_cache(ip_port)
local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port) local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port)
if not ok then if not ok then
return false, data return false, data
end end
return true, data return true, data
end end
function reversescan:add_to_cache(ip_port, value) function reversescan:add_to_cache(ip_port, value)
local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400) local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400)
if not ok then if not ok then
return false, err return false, err
end end
return true return true
end end
return reversescan return reversescan

View File

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

View File

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

4
stylua.toml Normal file
View File

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