Add StyLua and luacheck to precommit config file and apply it
This commit is contained in:
parent
cd1f87b9a2
commit
77bfe2697f
|
@ -0,0 +1,2 @@
|
|||
globals = {"ngx", "delay", "unpack"}
|
||||
ignore = {"411"}
|
|
@ -26,6 +26,19 @@ repos:
|
|||
- id: prettier
|
||||
name: Prettier Code Formatter
|
||||
|
||||
- repo: https://github.com/JohnnyMorganz/StyLua
|
||||
rev: 27e6b388796604181e810ef05c9fb15a9f7a7769 # frozen: v0.18.2
|
||||
hooks:
|
||||
- id: stylua-github
|
||||
exclude: ^src/(bw/lua/middleclass.lua|common/core/antibot/captcha.lua)$
|
||||
|
||||
- repo: https://github.com/lunarmodules/luacheck
|
||||
rev: ababb6d403d634eb74d2c541035e9ede966e710d # frozen: v1.1.1
|
||||
hooks:
|
||||
- id: luacheck
|
||||
exclude: ^src/(bw/lua/middleclass.lua|common/core/antibot/captcha.lua)$
|
||||
args: ["--std", "min", "--codes", "--ranges", "--no-cache"]
|
||||
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 10f4af6dbcf93456ba7df762278ae61ba3120dc6 # frozen: 6.1.0
|
||||
hooks:
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
local class = require "middleclass"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local datastore = require "bunkerweb.datastore"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local cjson = require "cjson"
|
||||
local upload = require "resty.upload"
|
||||
local rsignal = require "resty.signal"
|
||||
local process = require "ngx.process"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local process = require "ngx.process"
|
||||
local rsignal = require "resty.signal"
|
||||
local upload = require "resty.upload"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local api = class("api")
|
||||
local api = class("api")
|
||||
|
||||
api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
|
||||
api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
|
||||
|
||||
function api:initialize()
|
||||
self.datastore = datastore:new()
|
||||
|
@ -26,6 +26,7 @@ function api:initialize()
|
|||
end
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function api:log_cmd(cmd, status, stdout, stderr)
|
||||
local level = ngx.NOTICE
|
||||
local prefix = "success"
|
||||
|
@ -33,7 +34,7 @@ function api:log_cmd(cmd, status, stdout, stderr)
|
|||
level = ngx.ERR
|
||||
prefix = "error"
|
||||
end
|
||||
self.logger:log(level, prefix .. " while running command " .. command)
|
||||
self.logger:log(level, prefix .. " while running command " .. cmd)
|
||||
self.logger:log(level, "stdout = " .. stdout)
|
||||
self.logger:log(level, "stdout = " .. stderr)
|
||||
end
|
||||
|
@ -41,6 +42,7 @@ end
|
|||
-- TODO : use this if we switch to OpenResty
|
||||
function api:cmd(cmd)
|
||||
-- Non-blocking command
|
||||
-- luacheck: ignore 113
|
||||
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, 10000)
|
||||
self.logger:log_cmd(cmd, status, stdout, stderr)
|
||||
-- Timeout
|
||||
|
@ -51,6 +53,7 @@ function api:cmd(cmd)
|
|||
return status == 0, reason, status
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function api:response(http_status, api_status, msg)
|
||||
local resp = {}
|
||||
resp["status"] = api_status
|
||||
|
@ -101,6 +104,7 @@ api.global.POST["^/confs$"] = function(self)
|
|||
form:set_timeout(1000)
|
||||
local file = io.open(tmp, "w+")
|
||||
while true do
|
||||
-- luacheck: ignore 421
|
||||
local typ, res, err = form:read()
|
||||
if not typ then
|
||||
file:close()
|
||||
|
@ -117,9 +121,9 @@ api.global.POST["^/confs$"] = function(self)
|
|||
file:close()
|
||||
local cmds = {
|
||||
"rm -rf " .. destination .. "/*",
|
||||
"tar xzf " .. tmp .. " -C " .. destination
|
||||
"tar xzf " .. tmp .. " -C " .. destination,
|
||||
}
|
||||
for i, cmd in ipairs(cmds) do
|
||||
for _, cmd in ipairs(cmds) do
|
||||
local status = os.execute(cmd)
|
||||
if status ~= 0 then
|
||||
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
|
||||
|
@ -176,17 +180,23 @@ end
|
|||
|
||||
api.global.GET["^/bans$"] = function(self)
|
||||
local data = {}
|
||||
for i, k in ipairs(self.datastore:keys()) do
|
||||
for _, k in ipairs(self.datastore:keys()) do
|
||||
if k:find("^bans_ip_") then
|
||||
local reason, err = self.datastore:get(k)
|
||||
if err then
|
||||
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error",
|
||||
"can't access " .. k .. " from datastore : " .. reason)
|
||||
return self:response(
|
||||
ngx.HTTP_INTERNAL_SERVER_ERROR,
|
||||
"error",
|
||||
"can't access " .. k .. " from datastore : " .. reason
|
||||
)
|
||||
end
|
||||
local ok, ttl = self.datastore:ttl(k)
|
||||
if not ok then
|
||||
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error",
|
||||
"can't access ttl " .. k .. " from datastore : " .. ttl)
|
||||
return self:response(
|
||||
ngx.HTTP_INTERNAL_SERVER_ERROR,
|
||||
"error",
|
||||
"can't access ttl " .. k .. " from datastore : " .. ttl
|
||||
)
|
||||
end
|
||||
local ban = { ip = k:sub(9, #k), reason = reason, exp = math.floor(ttl) }
|
||||
table.insert(data, ban)
|
||||
|
@ -196,7 +206,7 @@ api.global.GET["^/bans$"] = function(self)
|
|||
end
|
||||
|
||||
api.global.GET["^/variables$"] = function(self)
|
||||
local variables, err = datastore:get('variables', true)
|
||||
local variables, err = datastore:get("variables", true)
|
||||
if not variables then
|
||||
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't access variables from datastore : " .. err)
|
||||
end
|
||||
|
@ -219,9 +229,9 @@ function api:do_api_call()
|
|||
if status ~= ngx.HTTP_OK then
|
||||
ret = false
|
||||
end
|
||||
if (#resp["msg"] == 0) then
|
||||
if #resp["msg"] == 0 then
|
||||
resp["msg"] = ""
|
||||
elseif (type(resp["msg"]) == "table") then
|
||||
elseif type(resp["msg"]) == "table" then
|
||||
resp["data"] = resp["msg"]
|
||||
resp["msg"] = resp["status"]
|
||||
end
|
||||
|
@ -231,10 +241,10 @@ function api:do_api_call()
|
|||
end
|
||||
local list, err = self.datastore:get("plugins", true)
|
||||
if not list then
|
||||
local status, resp = self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't list loaded plugins : " .. err)
|
||||
local _, resp = self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't list loaded plugins : " .. err)
|
||||
return false, resp["msg"], ngx.HTTP_INTERNAL_SERVER_ERROR, cjson.encode(resp)
|
||||
end
|
||||
for i, plugin in ipairs(list) do
|
||||
for _, plugin in ipairs(list) do
|
||||
if pcall(require, plugin.id .. "/" .. plugin.id) then
|
||||
local plugin_lua = require(plugin.id .. "/" .. plugin.id)
|
||||
if plugin_lua.api ~= nil then
|
||||
|
|
|
@ -1,42 +1,38 @@
|
|||
local mlcache = require "resty.mlcache"
|
||||
local class = require "middleclass"
|
||||
local clusterstore = require "bunkerweb.clusterstore"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local cachestore = class("cachestore")
|
||||
local logger = require "bunkerweb.logger"
|
||||
local mlcache = require "resty.mlcache"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cachestore = class("cachestore")
|
||||
|
||||
-- Instantiate mlcache object at module level (which will be cached when running init phase)
|
||||
-- TODO : custom settings
|
||||
local shm = "cachestore"
|
||||
local ipc_shm = "cachestore_ipc"
|
||||
local shm_miss = "cachestore_miss"
|
||||
local shm_locks = "cachestore_locks"
|
||||
local shm = "cachestore"
|
||||
local ipc_shm = "cachestore_ipc"
|
||||
local shm_miss = "cachestore_miss"
|
||||
local shm_locks = "cachestore_locks"
|
||||
if not ngx.shared.cachestore then
|
||||
shm = "cachestore_stream"
|
||||
ipc_shm = "cachestore_ipc_stream"
|
||||
shm_miss = "cachestore_miss_stream"
|
||||
shm = "cachestore_stream"
|
||||
ipc_shm = "cachestore_ipc_stream"
|
||||
shm_miss = "cachestore_miss_stream"
|
||||
shm_locks = "cachestore_locks_stream"
|
||||
end
|
||||
local cache, err = mlcache.new(
|
||||
"cachestore",
|
||||
shm,
|
||||
{
|
||||
lru_size = 100,
|
||||
ttl = 30,
|
||||
neg_ttl = 0.1,
|
||||
shm_set_tries = 3,
|
||||
shm_miss = shm_miss,
|
||||
shm_locks = shm_locks,
|
||||
resty_lock_opts = {
|
||||
exptime = 30,
|
||||
timeout = 5,
|
||||
step = 0.001,
|
||||
ratio = 2,
|
||||
max_step = 0.5
|
||||
},
|
||||
ipc_shm = ipc_shm
|
||||
}
|
||||
)
|
||||
local cache, err = mlcache.new("cachestore", shm, {
|
||||
lru_size = 100,
|
||||
ttl = 30,
|
||||
neg_ttl = 0.1,
|
||||
shm_set_tries = 3,
|
||||
shm_miss = shm_miss,
|
||||
shm_locks = shm_locks,
|
||||
resty_lock_opts = {
|
||||
exptime = 30,
|
||||
timeout = 5,
|
||||
step = 0.001,
|
||||
ratio = 2,
|
||||
max_step = 0.5,
|
||||
},
|
||||
ipc_shm = ipc_shm,
|
||||
})
|
||||
local module_logger = logger:new("CACHESTORE")
|
||||
if not cache then
|
||||
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
|
||||
|
@ -57,10 +53,12 @@ function cachestore:initialize(use_redis, new_cs, ctx)
|
|||
end
|
||||
|
||||
function cachestore:get(key)
|
||||
-- luacheck: ignore 432
|
||||
local callback = function(key, cs)
|
||||
-- Connect to redis
|
||||
-- luacheck: ignore 431
|
||||
local clusterstore = cs or require "bunkerweb.clusterstore":new(false)
|
||||
local ok, err, reused = clusterstore:connect()
|
||||
local ok, err, _ = clusterstore:connect()
|
||||
if not ok then
|
||||
return nil, "can't connect to redis : " .. err, nil
|
||||
end
|
||||
|
@ -96,6 +94,7 @@ function cachestore:get(key)
|
|||
local callback_no_miss = function()
|
||||
return nil, nil, -1
|
||||
end
|
||||
-- luacheck: ignore 431
|
||||
local value, err, hit_level
|
||||
if self.use_redis and utils.is_cosocket_available() then
|
||||
local cs = nil
|
||||
|
@ -114,13 +113,14 @@ function cachestore:get(key)
|
|||
end
|
||||
|
||||
function cachestore:set(key, value, ex)
|
||||
-- luacheck: ignore 431
|
||||
local ok, err
|
||||
if self.use_redis and utils.is_cosocket_available() then
|
||||
local ok, err = self:set_redis(key, value, ex)
|
||||
ok, err = self:set_redis(key, value, ex)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
end
|
||||
local ok, err
|
||||
if ex then
|
||||
ok, err = self.cache:set(key, { ttl = ex }, value)
|
||||
else
|
||||
|
@ -134,13 +134,14 @@ end
|
|||
|
||||
function cachestore:set_redis(key, value, ex)
|
||||
-- Connect to redis
|
||||
local ok, err, reused = self.clusterstore:connect()
|
||||
-- luacheck: ignore 431
|
||||
local ok, err, _ = self.clusterstore:connect()
|
||||
if not ok then
|
||||
return false, "can't connect to redis : " .. err
|
||||
end
|
||||
-- Set value with ttl
|
||||
local default_ex = ex or 30
|
||||
local ok, err = self.clusterstore:call("set", key, value, "EX", default_ex)
|
||||
local _, err = self.clusterstore:call("set", key, value, "EX", default_ex)
|
||||
if err then
|
||||
self.clusterstore:close()
|
||||
return false, "SET failed : " .. err
|
||||
|
@ -149,14 +150,16 @@ function cachestore:set_redis(key, value, ex)
|
|||
return true
|
||||
end
|
||||
|
||||
function cachestore:delete(key, value, ex)
|
||||
function cachestore:delete(key)
|
||||
-- luacheck: ignore 431
|
||||
local ok, err
|
||||
if self.use_redis and utils.is_cosocket_available() then
|
||||
local ok, err = self.del_redis(key)
|
||||
ok, err = self:del_redis(key)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
end
|
||||
local ok, err = self.cache:delete(key)
|
||||
ok, err = self.cache:delete(key)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
|
@ -165,12 +168,13 @@ end
|
|||
|
||||
function cachestore:del_redis(key)
|
||||
-- Connect to redis
|
||||
-- luacheck: ignore 431
|
||||
local ok, err = self.clusterstore:connect()
|
||||
if not ok then
|
||||
return false, "can't connect to redis : " .. err
|
||||
end
|
||||
-- Set value with ttl
|
||||
local ok, err = self.clusterstore:del(key)
|
||||
local _, err = self.clusterstore:del(key)
|
||||
if err then
|
||||
self.clusterstore:close()
|
||||
return false, "DEL failed : " .. err
|
||||
|
|
|
@ -1,135 +1,138 @@
|
|||
local class = require "middleclass"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local redis = require "resty.redis"
|
||||
local class = require "middleclass"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local redis = require "resty.redis"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local clusterstore = class("clusterstore")
|
||||
|
||||
function clusterstore:initialize(pool)
|
||||
-- Instantiate logger
|
||||
self.logger = logger:new("CLUSTERSTORE")
|
||||
-- Get variables
|
||||
local variables = {
|
||||
["REDIS_HOST"] = "",
|
||||
["REDIS_PORT"] = "",
|
||||
["REDIS_DATABASE"] = "",
|
||||
["REDIS_SSL"] = "",
|
||||
["REDIS_TIMEOUT"] = "",
|
||||
["REDIS_KEEPALIVE_IDLE"] = "",
|
||||
["REDIS_KEEPALIVE_POOL"] = ""
|
||||
}
|
||||
-- Set them for later user
|
||||
self.variables = {}
|
||||
for k, v in pairs(variables) do
|
||||
local value, err = utils.get_variable(k, false)
|
||||
if value == nil then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
self.variables[k] = value
|
||||
end
|
||||
-- Don't instantiate a redis object for now
|
||||
self.redis_client = nil
|
||||
self.pool = pool == nil or pool
|
||||
-- Instantiate logger
|
||||
self.logger = logger:new("CLUSTERSTORE")
|
||||
-- Get variables
|
||||
local variables = {
|
||||
["REDIS_HOST"] = "",
|
||||
["REDIS_PORT"] = "",
|
||||
["REDIS_DATABASE"] = "",
|
||||
["REDIS_SSL"] = "",
|
||||
["REDIS_TIMEOUT"] = "",
|
||||
["REDIS_KEEPALIVE_IDLE"] = "",
|
||||
["REDIS_KEEPALIVE_POOL"] = "",
|
||||
}
|
||||
-- Set them for later user
|
||||
self.variables = {}
|
||||
for k, _ in pairs(variables) do
|
||||
local value, err = utils.get_variable(k, false)
|
||||
if value == nil then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
self.variables[k] = value
|
||||
end
|
||||
-- Don't instantiate a redis object for now
|
||||
self.redis_client = nil
|
||||
self.pool = pool == nil or pool
|
||||
end
|
||||
|
||||
function clusterstore:connect()
|
||||
-- Check if we are already connected
|
||||
if self.redis_client then
|
||||
return true, "already connected", self.redis_client:get_reused_times()
|
||||
end
|
||||
-- Instantiate object
|
||||
local redis_client, err = redis:new()
|
||||
if redis_client == nil then
|
||||
return false, err
|
||||
end
|
||||
-- Set timeouts
|
||||
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
|
||||
-- Connect
|
||||
local options = {
|
||||
ssl = self.variables["REDIS_SSL"] == "yes",
|
||||
}
|
||||
if self.pool then
|
||||
options.pool = "bw-redis"
|
||||
options.pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
|
||||
end
|
||||
local ok, err = redis_client:connect(self.variables["REDIS_HOST"], tonumber(self.variables["REDIS_PORT"]), options)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
self.redis_client = redis_client
|
||||
-- Select database if needed
|
||||
local times, err = self.redis_client:get_reused_times()
|
||||
if err then
|
||||
self:close()
|
||||
return false, err
|
||||
end
|
||||
if times == 0 then
|
||||
local select, err = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
|
||||
if err then
|
||||
self:close()
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
return true, "success", times
|
||||
-- Check if we are already connected
|
||||
if self.redis_client then
|
||||
return true, "already connected", self.redis_client:get_reused_times()
|
||||
end
|
||||
-- Instantiate object
|
||||
local redis_client, err = redis:new()
|
||||
if redis_client == nil then
|
||||
return false, err
|
||||
end
|
||||
-- Set timeouts
|
||||
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
|
||||
-- Connect
|
||||
local options = {
|
||||
ssl = self.variables["REDIS_SSL"] == "yes",
|
||||
}
|
||||
if self.pool then
|
||||
options.pool = "bw-redis"
|
||||
options.pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
|
||||
end
|
||||
local ok, err = redis_client:connect(self.variables["REDIS_HOST"], tonumber(self.variables["REDIS_PORT"]), options)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
self.redis_client = redis_client
|
||||
-- Select database if needed
|
||||
local times, err = self.redis_client:get_reused_times()
|
||||
if err then
|
||||
self:close()
|
||||
return false, err
|
||||
end
|
||||
if times == 0 then
|
||||
-- luacheck: ignore 421
|
||||
local _, err = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
|
||||
if err then
|
||||
self:close()
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
return true, "success", times
|
||||
end
|
||||
|
||||
function clusterstore:close()
|
||||
if self.redis_client then
|
||||
-- Equivalent to close but keep a pool of connections
|
||||
if self.pool then
|
||||
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]),
|
||||
tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
|
||||
self.redis_client = nil
|
||||
if not ok then
|
||||
require "bunkerweb.logger":new("clusterstore-close"):log(ngx.ERR, err)
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
-- Close
|
||||
local ok, err = self.redis_client:close()
|
||||
self.redis_client.redis_client = nil
|
||||
return ok, err
|
||||
end
|
||||
return false, "not connected"
|
||||
if self.redis_client then
|
||||
-- Equivalent to close but keep a pool of connections
|
||||
if self.pool then
|
||||
local ok, err = self.redis_client:set_keepalive(
|
||||
tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]),
|
||||
tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
|
||||
)
|
||||
self.redis_client = nil
|
||||
if not ok then
|
||||
require("bunkerweb.logger"):new("clusterstore-close"):log(ngx.ERR, err)
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
-- Close
|
||||
local ok, err = self.redis_client:close()
|
||||
self.redis_client.redis_client = nil
|
||||
return ok, err
|
||||
end
|
||||
return false, "not connected"
|
||||
end
|
||||
|
||||
function clusterstore:call(method, ...)
|
||||
-- Check if we are connected
|
||||
if not self.redis_client then
|
||||
return false, "not connected"
|
||||
end
|
||||
-- Call method
|
||||
return self.redis_client[method](self.redis_client, ...)
|
||||
-- Check if we are connected
|
||||
if not self.redis_client then
|
||||
return false, "not connected"
|
||||
end
|
||||
-- Call method
|
||||
return self.redis_client[method](self.redis_client, ...)
|
||||
end
|
||||
|
||||
function clusterstore:multi(calls)
|
||||
-- Check if we are connected
|
||||
if not self.redis_client then
|
||||
return false, "not connected"
|
||||
end
|
||||
-- Start transaction
|
||||
local ok, err = self.redis_client:multi()
|
||||
if not ok then
|
||||
return false, "multi() failed : " .. err
|
||||
end
|
||||
-- Loop on calls
|
||||
for i, call in ipairs(calls) do
|
||||
local method = call[1]
|
||||
local args = unpack(call[2])
|
||||
local ok, err = self.redis_client[method](self.redis_client, args)
|
||||
if not ok then
|
||||
return false, method + "() failed : " .. err
|
||||
end
|
||||
end
|
||||
-- Exec transaction
|
||||
local exec, err = self.redis_client:exec()
|
||||
if not exec then
|
||||
return false, "exec() failed : " .. err
|
||||
end
|
||||
if type(exec) ~= "table" then
|
||||
return false, "exec() result is not a table"
|
||||
end
|
||||
return true, "success", exec
|
||||
-- Check if we are connected
|
||||
if not self.redis_client then
|
||||
return false, "not connected"
|
||||
end
|
||||
-- Start transaction
|
||||
local ok, err = self.redis_client:multi()
|
||||
if not ok then
|
||||
return false, "multi() failed : " .. err
|
||||
end
|
||||
-- Loop on calls
|
||||
for _, call in ipairs(calls) do
|
||||
local method = call[1]
|
||||
local args = unpack(call[2])
|
||||
ok, err = self.redis_client[method](self.redis_client, args)
|
||||
if not ok then
|
||||
return false, method + "() failed : " .. err
|
||||
end
|
||||
end
|
||||
-- Exec transaction
|
||||
local exec, err = self.redis_client:exec()
|
||||
if not exec then
|
||||
return false, "exec() failed : " .. err
|
||||
end
|
||||
if type(exec) ~= "table" then
|
||||
return false, "exec() result is not a table"
|
||||
end
|
||||
return true, "success", exec
|
||||
end
|
||||
|
||||
return clusterstore
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
local class = require "middleclass"
|
||||
local lrucache = require "resty.lrucache"
|
||||
local class = require "middleclass"
|
||||
local lrucache = require "resty.lrucache"
|
||||
local datastore = class("datastore")
|
||||
|
||||
local lru, err = lrucache.new(100000)
|
||||
local lru, err = lrucache.new(100000)
|
||||
if not lru then
|
||||
require "bunkerweb.logger":new("DATASTORE"):log(ngx.ERR,
|
||||
"failed to instantiate LRU cache : " .. (err or "unknown error"))
|
||||
require "bunkerweb.logger"
|
||||
:new("DATASTORE")
|
||||
:log(ngx.ERR, "failed to instantiate LRU cache : " .. (err or "unknown error"))
|
||||
end
|
||||
|
||||
function datastore:initialize()
|
||||
|
@ -16,11 +17,13 @@ function datastore:initialize()
|
|||
end
|
||||
|
||||
function datastore:get(key, worker)
|
||||
-- luacheck: ignore 431
|
||||
local value, err
|
||||
if worker then
|
||||
local value, err = lru:get(key)
|
||||
value, err = lru:get(key)
|
||||
return value, err or "not found"
|
||||
end
|
||||
local value, err = self.dict:get(key)
|
||||
value, err = self.dict:get(key)
|
||||
if not value and not err then
|
||||
err = "not found"
|
||||
end
|
||||
|
@ -52,10 +55,11 @@ function datastore:keys(worker)
|
|||
return self.dict:get_keys(0)
|
||||
end
|
||||
|
||||
function datastore:ttl(key)
|
||||
function datastore:ttl(key, worker)
|
||||
if worker then
|
||||
return false, "not supported by LRU"
|
||||
end
|
||||
-- luacheck: ignore 431
|
||||
local ttl, err = self.dict:ttl(key)
|
||||
if not ttl then
|
||||
return false, err
|
||||
|
@ -64,13 +68,13 @@ function datastore:ttl(key)
|
|||
end
|
||||
|
||||
function datastore:delete_all(pattern, worker)
|
||||
local keys = {}
|
||||
local keys
|
||||
if worker then
|
||||
keys = lru:keys(0)
|
||||
else
|
||||
keys = self.dict:get_keys(0)
|
||||
end
|
||||
for i, key in ipairs(keys) do
|
||||
for _, key in ipairs(keys) do
|
||||
if key:match(pattern) then
|
||||
self.dict:delete(key)
|
||||
end
|
||||
|
@ -78,6 +82,7 @@ function datastore:delete_all(pattern, worker)
|
|||
return true, "success"
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function datastore:flush_lru()
|
||||
lru:flush_all()
|
||||
end
|
||||
|
|
|
@ -1,263 +1,263 @@
|
|||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local cjson = require "cjson"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local helpers = {}
|
||||
local helpers = {}
|
||||
|
||||
helpers.load_plugin = function(json)
|
||||
-- Open file
|
||||
local file, err, nb = io.open(json, "r")
|
||||
if not file then
|
||||
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
|
||||
end
|
||||
-- Decode JSON
|
||||
local ok, plugin = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid JSON at " .. json .. " : " .. err
|
||||
end
|
||||
-- Check fields
|
||||
local missing_fields = {}
|
||||
local required_fields = { "id", "name", "description", "version", "settings", "stream" }
|
||||
for i, field in ipairs(required_fields) do
|
||||
if plugin[field] == nil then
|
||||
table.insert(missing_fields, field)
|
||||
end
|
||||
end
|
||||
if #missing_fields > 0 then
|
||||
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
|
||||
end
|
||||
-- Try require
|
||||
local plugin_lua, err = helpers.require_plugin(plugin.id)
|
||||
if plugin_lua == false then
|
||||
return false, err
|
||||
end
|
||||
-- Fill phases
|
||||
local phases = utils.get_phases()
|
||||
plugin.phases = {}
|
||||
if plugin_lua then
|
||||
for i, phase in ipairs(phases) do
|
||||
if plugin_lua[phase] ~= nil then
|
||||
table.insert(plugin.phases, phase)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Return plugin
|
||||
return true, plugin
|
||||
helpers.load_plugin = function(json)
|
||||
-- Open file
|
||||
local file, err, nb = io.open(json, "r")
|
||||
if not file then
|
||||
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
|
||||
end
|
||||
-- Decode JSON
|
||||
local ok, plugin = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid JSON at " .. json .. " : " .. err
|
||||
end
|
||||
-- Check fields
|
||||
local missing_fields = {}
|
||||
local required_fields = { "id", "name", "description", "version", "settings", "stream" }
|
||||
for _, field in ipairs(required_fields) do
|
||||
if plugin[field] == nil then
|
||||
table.insert(missing_fields, field)
|
||||
end
|
||||
end
|
||||
if #missing_fields > 0 then
|
||||
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
|
||||
end
|
||||
-- Try require
|
||||
local plugin_lua, err = helpers.require_plugin(plugin.id)
|
||||
if plugin_lua == false then
|
||||
return false, err
|
||||
end
|
||||
-- Fill phases
|
||||
local phases = utils.get_phases()
|
||||
plugin.phases = {}
|
||||
if plugin_lua then
|
||||
for _, phase in ipairs(phases) do
|
||||
if plugin_lua[phase] ~= nil then
|
||||
table.insert(plugin.phases, phase)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Return plugin
|
||||
return true, plugin
|
||||
end
|
||||
|
||||
helpers.order_plugins = function(plugins)
|
||||
-- Extract orders
|
||||
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
|
||||
if not file then
|
||||
return false, err .. " (nb = " .. tostring(nb) .. ")"
|
||||
end
|
||||
local ok, orders = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid order.json : " .. err
|
||||
end
|
||||
-- Compute plugins/id/phases table
|
||||
local plugins_phases = {}
|
||||
for i, plugin in ipairs(plugins) do
|
||||
plugins_phases[plugin.id] = {}
|
||||
for j, phase in ipairs(plugin.phases) do
|
||||
plugins_phases[plugin.id][phase] = true
|
||||
end
|
||||
end
|
||||
-- Order result
|
||||
local result_orders = {}
|
||||
for i, phase in ipairs(utils.get_phases()) do
|
||||
result_orders[phase] = {}
|
||||
end
|
||||
-- Fill order first
|
||||
for phase, order in pairs(orders) do
|
||||
for i, id in ipairs(order) do
|
||||
local plugin = plugins_phases[id]
|
||||
if plugin and plugin[phase] then
|
||||
table.insert(result_orders[phase], id)
|
||||
plugin[phase] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Then append missing plugins to the end
|
||||
for i, phase in ipairs(utils.get_phases()) do
|
||||
for id, plugin in pairs(plugins_phases) do
|
||||
if plugin[phase] then
|
||||
table.insert(result_orders[phase], id)
|
||||
plugin[phase] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return true, result_orders
|
||||
helpers.order_plugins = function(plugins)
|
||||
-- Extract orders
|
||||
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
|
||||
if not file then
|
||||
return false, err .. " (nb = " .. tostring(nb) .. ")"
|
||||
end
|
||||
local ok, orders = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid order.json : " .. err
|
||||
end
|
||||
-- Compute plugins/id/phases table
|
||||
local plugins_phases = {}
|
||||
for _, plugin in ipairs(plugins) do
|
||||
plugins_phases[plugin.id] = {}
|
||||
for _, phase in ipairs(plugin.phases) do
|
||||
plugins_phases[plugin.id][phase] = true
|
||||
end
|
||||
end
|
||||
-- Order result
|
||||
local result_orders = {}
|
||||
for _, phase in ipairs(utils.get_phases()) do
|
||||
result_orders[phase] = {}
|
||||
end
|
||||
-- Fill order first
|
||||
for phase, order in pairs(orders) do
|
||||
for _, id in ipairs(order) do
|
||||
local plugin = plugins_phases[id]
|
||||
if plugin and plugin[phase] then
|
||||
table.insert(result_orders[phase], id)
|
||||
plugin[phase] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Then append missing plugins to the end
|
||||
for _, phase in ipairs(utils.get_phases()) do
|
||||
for id, plugin in pairs(plugins_phases) do
|
||||
if plugin[phase] then
|
||||
table.insert(result_orders[phase], id)
|
||||
plugin[phase] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return true, result_orders
|
||||
end
|
||||
|
||||
helpers.require_plugin = function(id)
|
||||
-- Require call
|
||||
local ok, plugin_lua = pcall(require, id .. "/" .. id)
|
||||
if not ok then
|
||||
if plugin_lua:match("not found") then
|
||||
return nil, "plugin " .. id .. " doesn't have LUA code"
|
||||
end
|
||||
return false, "require error for plugin " .. id .. " : " .. plugin_lua
|
||||
end
|
||||
-- New call
|
||||
if plugin_lua.new == nil then
|
||||
return false, "missing new() method for plugin " .. id
|
||||
end
|
||||
-- Return plugin
|
||||
return plugin_lua, "require() call successful for plugin " .. id
|
||||
-- Require call
|
||||
local ok, plugin_lua = pcall(require, id .. "/" .. id)
|
||||
if not ok then
|
||||
if plugin_lua:match("not found") then
|
||||
return nil, "plugin " .. id .. " doesn't have LUA code"
|
||||
end
|
||||
return false, "require error for plugin " .. id .. " : " .. plugin_lua
|
||||
end
|
||||
-- New call
|
||||
if plugin_lua.new == nil then
|
||||
return false, "missing new() method for plugin " .. id
|
||||
end
|
||||
-- Return plugin
|
||||
return plugin_lua, "require() call successful for plugin " .. id
|
||||
end
|
||||
|
||||
helpers.new_plugin = function(plugin_lua, ctx)
|
||||
-- Require call
|
||||
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx)
|
||||
if not ok then
|
||||
return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj
|
||||
end
|
||||
return true, plugin_obj
|
||||
helpers.new_plugin = function(plugin_lua, ctx)
|
||||
-- Require call
|
||||
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua, ctx)
|
||||
if not ok then
|
||||
return false, "new error for plugin " .. plugin_lua.name .. " : " .. plugin_obj
|
||||
end
|
||||
return true, plugin_obj
|
||||
end
|
||||
|
||||
helpers.call_plugin = function(plugin, method)
|
||||
-- Check if method is present
|
||||
if plugin[method] == nil then
|
||||
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
|
||||
end
|
||||
-- Call method
|
||||
local ok, ret = pcall(plugin[method], plugin)
|
||||
if not ok then
|
||||
return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret
|
||||
end
|
||||
if ret == nil then
|
||||
return false, plugin:get_id() .. ":" .. method .. "() returned nil value"
|
||||
end
|
||||
-- Check values
|
||||
local missing_values = {}
|
||||
local required_values = { "ret", "msg" }
|
||||
for i, value in ipairs(required_values) do
|
||||
if ret[value] == nil then
|
||||
table.insert(missing_values, value)
|
||||
end
|
||||
end
|
||||
if #missing_values > 0 then
|
||||
return false, "missing required return value(s) : " .. cjson.encode(missing_values)
|
||||
end
|
||||
-- Return
|
||||
return true, ret
|
||||
helpers.call_plugin = function(plugin, method)
|
||||
-- Check if method is present
|
||||
if plugin[method] == nil then
|
||||
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
|
||||
end
|
||||
-- Call method
|
||||
local ok, ret = pcall(plugin[method], plugin)
|
||||
if not ok then
|
||||
return false, plugin:get_id() .. ":" .. method .. "() failed : " .. ret
|
||||
end
|
||||
if ret == nil then
|
||||
return false, plugin:get_id() .. ":" .. method .. "() returned nil value"
|
||||
end
|
||||
-- Check values
|
||||
local missing_values = {}
|
||||
local required_values = { "ret", "msg" }
|
||||
for _, value in ipairs(required_values) do
|
||||
if ret[value] == nil then
|
||||
table.insert(missing_values, value)
|
||||
end
|
||||
end
|
||||
if #missing_values > 0 then
|
||||
return false, "missing required return value(s) : " .. cjson.encode(missing_values)
|
||||
end
|
||||
-- Return
|
||||
return true, ret
|
||||
end
|
||||
|
||||
helpers.fill_ctx = function()
|
||||
-- Return errors as table
|
||||
local errors = {}
|
||||
local ctx = ngx.ctx
|
||||
-- Check if ctx is already filled
|
||||
if not ctx.bw then
|
||||
-- Instantiate bw table
|
||||
local data = {}
|
||||
-- Common vars
|
||||
data.kind = "http"
|
||||
if ngx.shared.datastore_stream then
|
||||
data.kind = "stream"
|
||||
end
|
||||
data.remote_addr = ngx.var.remote_addr
|
||||
data.server_name = ngx.var.server_name
|
||||
if data.kind == "http" then
|
||||
data.uri = ngx.var.uri
|
||||
data.request_uri = ngx.var.request_uri
|
||||
data.request_method = ngx.var.request_method
|
||||
data.http_user_agent = ngx.var.http_user_agent
|
||||
data.http_host = ngx.var.http_host
|
||||
data.server_name = ngx.var.server_name
|
||||
data.http_content_type = ngx.var.http_content_type
|
||||
data.http_content_length = ngx.var.http_content_length
|
||||
data.http_origin = ngx.var.http_origin
|
||||
data.http_version = ngx.req.http_version()
|
||||
data.scheme = ngx.var.scheme
|
||||
end
|
||||
-- IP data : global
|
||||
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
|
||||
if ip_is_global == nil then
|
||||
table.insert(errors, "can't check if IP is global : " .. err)
|
||||
else
|
||||
data.ip_is_global = ip_is_global
|
||||
end
|
||||
-- IP data : v4 / v6
|
||||
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
|
||||
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
|
||||
-- Misc info
|
||||
data.integration = utils.get_integration()
|
||||
data.version = utils.get_version()
|
||||
-- Fill ctx
|
||||
ctx.bw = data
|
||||
end
|
||||
-- Always create new objects for current phases in case of cosockets
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
if not use_redis then
|
||||
table.insert(errors, "can't get variable from datastore : " .. err)
|
||||
end
|
||||
ctx.bw.datastore = require "bunkerweb.datastore":new()
|
||||
ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
|
||||
ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
|
||||
return true, "ctx filled", errors, ctx
|
||||
helpers.fill_ctx = function()
|
||||
-- Return errors as table
|
||||
local errors = {}
|
||||
local ctx = ngx.ctx
|
||||
-- Check if ctx is already filled
|
||||
if not ctx.bw then
|
||||
-- Instantiate bw table
|
||||
local data = {}
|
||||
-- Common vars
|
||||
data.kind = "http"
|
||||
if ngx.shared.datastore_stream then
|
||||
data.kind = "stream"
|
||||
end
|
||||
data.remote_addr = ngx.var.remote_addr
|
||||
data.server_name = ngx.var.server_name
|
||||
if data.kind == "http" then
|
||||
data.uri = ngx.var.uri
|
||||
data.request_uri = ngx.var.request_uri
|
||||
data.request_method = ngx.var.request_method
|
||||
data.http_user_agent = ngx.var.http_user_agent
|
||||
data.http_host = ngx.var.http_host
|
||||
data.server_name = ngx.var.server_name
|
||||
data.http_content_type = ngx.var.http_content_type
|
||||
data.http_content_length = ngx.var.http_content_length
|
||||
data.http_origin = ngx.var.http_origin
|
||||
data.http_version = ngx.req.http_version()
|
||||
data.scheme = ngx.var.scheme
|
||||
end
|
||||
-- IP data : global
|
||||
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
|
||||
if ip_is_global == nil then
|
||||
table.insert(errors, "can't check if IP is global : " .. err)
|
||||
else
|
||||
data.ip_is_global = ip_is_global
|
||||
end
|
||||
-- IP data : v4 / v6
|
||||
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
|
||||
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
|
||||
-- Misc info
|
||||
data.integration = utils.get_integration()
|
||||
data.version = utils.get_version()
|
||||
-- Fill ctx
|
||||
ctx.bw = data
|
||||
end
|
||||
-- Always create new objects for current phases in case of cosockets
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
if not use_redis then
|
||||
table.insert(errors, "can't get variable from datastore : " .. err)
|
||||
end
|
||||
ctx.bw.datastore = require "bunkerweb.datastore":new()
|
||||
ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
|
||||
ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
|
||||
return true, "ctx filled", errors, ctx
|
||||
end
|
||||
|
||||
function helpers.load_variables(all_variables, plugins)
|
||||
-- Extract settings from plugins and global ones
|
||||
local all_settings = {}
|
||||
for i, plugin in ipairs(plugins) do
|
||||
if plugin.settings then
|
||||
for setting, data in pairs(plugin.settings) do
|
||||
all_settings[setting] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
local file = io.open("/usr/share/bunkerweb/settings.json")
|
||||
if not file then
|
||||
return false, "can't open settings.json"
|
||||
end
|
||||
local ok, settings = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid settings.json : " .. err
|
||||
end
|
||||
for setting, data in pairs(settings) do
|
||||
all_settings[setting] = data
|
||||
end
|
||||
-- Extract vars
|
||||
local variables = { ["global"] = {} }
|
||||
local multisite = all_variables["MULTISITE"] == "yes"
|
||||
local server_names = {}
|
||||
if multisite then
|
||||
for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do
|
||||
variables[server_name] = {}
|
||||
table.insert(server_names, server_name)
|
||||
end
|
||||
end
|
||||
for setting, data in pairs(all_settings) do
|
||||
if all_variables[setting] then
|
||||
variables["global"][setting] = all_variables[setting]
|
||||
end
|
||||
if data.multiple then
|
||||
for variable, value in pairs(all_variables) do
|
||||
local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))")
|
||||
if multiple_setting then
|
||||
if multisite and server_name and server_name:match("%S+_$") then
|
||||
variables[server_name:sub(1, -2)][multiple_setting] = value
|
||||
else
|
||||
variables["global"][multiple_setting] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if multisite then
|
||||
for i, server_name in ipairs(server_names) do
|
||||
local key = server_name .. "_" .. setting
|
||||
if all_variables[key] then
|
||||
variables[server_name][setting] = all_variables[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true, variables
|
||||
-- Extract settings from plugins and global ones
|
||||
local all_settings = {}
|
||||
for _, plugin in ipairs(plugins) do
|
||||
if plugin.settings then
|
||||
for setting, data in pairs(plugin.settings) do
|
||||
all_settings[setting] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
local file = io.open("/usr/share/bunkerweb/settings.json")
|
||||
if not file then
|
||||
return false, "can't open settings.json"
|
||||
end
|
||||
local ok, settings = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid settings.json : " .. settings
|
||||
end
|
||||
for setting, data in pairs(settings) do
|
||||
all_settings[setting] = data
|
||||
end
|
||||
-- Extract vars
|
||||
local variables = { ["global"] = {} }
|
||||
local multisite = all_variables["MULTISITE"] == "yes"
|
||||
local server_names = {}
|
||||
if multisite then
|
||||
for server_name in all_variables["SERVER_NAME"]:gmatch("%S+") do
|
||||
variables[server_name] = {}
|
||||
table.insert(server_names, server_name)
|
||||
end
|
||||
end
|
||||
for setting, data in pairs(all_settings) do
|
||||
if all_variables[setting] then
|
||||
variables["global"][setting] = all_variables[setting]
|
||||
end
|
||||
if data.multiple then
|
||||
for variable, value in pairs(all_variables) do
|
||||
local _, server_name, multiple_setting = variable:match("((%S*_?)(" .. setting .. "_%d+))")
|
||||
if multiple_setting then
|
||||
if multisite and server_name and server_name:match("%S+_$") then
|
||||
variables[server_name:sub(1, -2)][multiple_setting] = value
|
||||
else
|
||||
variables["global"][multiple_setting] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if multisite then
|
||||
for _, server_name in ipairs(server_names) do
|
||||
local key = server_name .. "_" .. setting
|
||||
if all_variables[key] then
|
||||
variables[server_name][setting] = all_variables[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true, variables
|
||||
end
|
||||
|
||||
return helpers
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
local class = require "middleclass"
|
||||
local errlog = require "ngx.errlog"
|
||||
local class = require "middleclass"
|
||||
local logger = class("logger")
|
||||
|
||||
function logger:initialize(prefix)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local geoip = require "geoip.mmdb"
|
||||
|
||||
return {
|
||||
country_db = geoip.load_database("/var/cache/bunkerweb/country.mmdb"),
|
||||
asn_db = geoip.load_database("/var/cache/bunkerweb/asn.mmdb")
|
||||
country_db = geoip.load_database "/var/cache/bunkerweb/country.mmdb",
|
||||
asn_db = geoip.load_database "/var/cache/bunkerweb/asn.mmdb",
|
||||
}
|
||||
|
|
|
@ -1,87 +1,88 @@
|
|||
local class = require "middleclass"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local datastore = require "bunkerweb.datastore"
|
||||
local cachestore = require "bunkerweb.cachestore"
|
||||
local cachestore = require "bunkerweb.cachestore"
|
||||
local class = require "middleclass"
|
||||
local clusterstore = require "bunkerweb.clusterstore"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local plugin = class("plugin")
|
||||
local datastore = require "bunkerweb.datastore"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local plugin = class("plugin")
|
||||
|
||||
function plugin:initialize(id, ctx)
|
||||
-- Store common, values
|
||||
self.id = id
|
||||
local multisite = false
|
||||
local current_phase = ngx.get_phase()
|
||||
for i, check_phase in ipairs({ "set", "access", "content", "header_filter", "log", "preread", "log_stream",
|
||||
"log_default" }) do
|
||||
if current_phase == check_phase then
|
||||
multisite = true
|
||||
break
|
||||
end
|
||||
end
|
||||
self.is_request = multisite
|
||||
-- Store common objects
|
||||
self.logger = logger:new(self.id)
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
if not use_redis then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
self.use_redis = use_redis == "yes"
|
||||
if self.is_request then
|
||||
-- Store ctx
|
||||
self.ctx = ctx or ngx.ctx
|
||||
self.datastore = utils.get_ctx_obj("datastore", self.ctx) or datastore:new()
|
||||
self.cachestore = utils.get_ctx_obj("cachestore", self.ctx) or cachestore:new(use_redis == "yes", true, self.ctx)
|
||||
self.clusterstore = utils.get_ctx_obj("clusterstore", self.ctx) or clusterstore:new(false)
|
||||
else
|
||||
self.datastore = datastore:new()
|
||||
self.cachestore = cachestore:new(use_redis == "yes", true)
|
||||
self.clusterstore = clusterstore:new(false)
|
||||
end
|
||||
-- Get metadata
|
||||
local metadata, err = self.datastore:get("plugin_" .. id, true)
|
||||
if not metadata then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
-- Store variables
|
||||
self.variables = {}
|
||||
self.multiples = {}
|
||||
for k, v in pairs(metadata.settings) do
|
||||
local value, err = utils.get_variable(k, v.context == "multisite" and multisite)
|
||||
if value == nil then
|
||||
self.logger:log(ngx.ERR, "can't get " .. k .. " variable : " .. err)
|
||||
end
|
||||
self.variables[k] = value
|
||||
-- if v.multiple then
|
||||
-- local multiples, err = utils.get_multiple_variables(k)
|
||||
-- if not multiples then
|
||||
-- self.logger:log(ngx.ERR, "can't get " .. k .. " multiple variable : " .. err)
|
||||
-- self.multiples[k] = {}
|
||||
-- else
|
||||
-- self.multiples[k] = multiples
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
-- Is loading
|
||||
local is_loading, err = utils.get_variable("IS_LOADING", false)
|
||||
if is_loading == nil then
|
||||
self.logger:log(ngx.ERR, "can't get IS_LOADING variable : " .. err)
|
||||
end
|
||||
self.is_loading = is_loading == "yes"
|
||||
-- Kind of server
|
||||
self.kind = "http"
|
||||
if ngx.shared.datastore_stream then
|
||||
self.kind = "stream"
|
||||
end
|
||||
-- Store common, values
|
||||
self.id = id
|
||||
local multisite = false
|
||||
local current_phase = ngx.get_phase()
|
||||
for _, check_phase in ipairs {
|
||||
"set",
|
||||
"access",
|
||||
"content",
|
||||
"header_filter",
|
||||
"log",
|
||||
"preread",
|
||||
"log_stream",
|
||||
"log_default",
|
||||
} do
|
||||
if current_phase == check_phase then
|
||||
multisite = true
|
||||
break
|
||||
end
|
||||
end
|
||||
self.is_request = multisite
|
||||
-- Store common objects
|
||||
self.logger = logger:new(self.id)
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
if not use_redis then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
end
|
||||
self.use_redis = use_redis == "yes"
|
||||
if self.is_request then
|
||||
-- Store ctx
|
||||
self.ctx = ctx or ngx.ctx
|
||||
self.datastore = utils.get_ctx_obj("datastore", self.ctx) or datastore:new()
|
||||
self.cachestore = utils.get_ctx_obj("cachestore", self.ctx)
|
||||
or cachestore:new(use_redis == "yes", true, self.ctx)
|
||||
self.clusterstore = utils.get_ctx_obj("clusterstore", self.ctx) or clusterstore:new(false)
|
||||
else
|
||||
self.datastore = datastore:new()
|
||||
self.cachestore = cachestore:new(use_redis == "yes", true)
|
||||
self.clusterstore = clusterstore:new(false)
|
||||
end
|
||||
-- Get metadata
|
||||
local metadata, err = self.datastore:get("plugin_" .. id, true)
|
||||
if not metadata then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
-- Store variables
|
||||
self.variables = {}
|
||||
self.multiples = {}
|
||||
local value
|
||||
for k, v in pairs(metadata.settings) do
|
||||
value, err = utils.get_variable(k, v.context == "multisite" and multisite)
|
||||
if value == nil then
|
||||
self.logger:log(ngx.ERR, "can't get " .. k .. " variable : " .. err)
|
||||
end
|
||||
self.variables[k] = value
|
||||
end
|
||||
-- Is loading
|
||||
local is_loading, err = utils.get_variable("IS_LOADING", false)
|
||||
if is_loading == nil then
|
||||
self.logger:log(ngx.ERR, "can't get IS_LOADING variable : " .. err)
|
||||
end
|
||||
self.is_loading = is_loading == "yes"
|
||||
-- Kind of server
|
||||
self.kind = "http"
|
||||
if ngx.shared.datastore_stream then
|
||||
self.kind = "stream"
|
||||
end
|
||||
end
|
||||
|
||||
function plugin:get_id()
|
||||
return self.id
|
||||
return self.id
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function plugin:ret(ret, msg, status, redirect)
|
||||
return { ret = ret, msg = msg, status = status, redirect = redirect }
|
||||
return { ret = ret, msg = msg, status = status, redirect = redirect }
|
||||
end
|
||||
|
||||
return plugin
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
local cdatastore = require "bunkerweb.datastore"
|
||||
local mmdb = require "bunkerweb.mmdb"
|
||||
local clogger = require "bunkerweb.logger"
|
||||
local clogger = require "bunkerweb.logger"
|
||||
local mmdb = require "bunkerweb.mmdb"
|
||||
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local session = require "resty.session"
|
||||
local cjson = require "cjson"
|
||||
local cjson = require "cjson"
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local session = require "resty.session"
|
||||
|
||||
local logger = clogger:new("UTILS")
|
||||
local datastore = cdatastore:new()
|
||||
local logger = clogger:new("UTILS")
|
||||
local datastore = cdatastore:new()
|
||||
|
||||
local utils = {}
|
||||
local utils = {}
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
utils.get_variable = function(var, site_search)
|
||||
utils.get_variable = function(var, site_search)
|
||||
-- Default site search to true
|
||||
if site_search == nil then
|
||||
site_search = true
|
||||
end
|
||||
-- Get global value
|
||||
local variables, err = datastore:get('variables', true)
|
||||
local variables, err = datastore:get("variables", true)
|
||||
if not variables then
|
||||
return nil, "can't access variables from datastore : " .. err
|
||||
end
|
||||
|
@ -33,9 +33,9 @@ utils.get_variable = function(var, site_search)
|
|||
return value, "success"
|
||||
end
|
||||
|
||||
utils.has_variable = function(var, value)
|
||||
utils.has_variable = function(var, value)
|
||||
-- Get global variable
|
||||
local variables, err = datastore:get('variables', true)
|
||||
local variables, err = datastore:get("variables", true)
|
||||
if not variables then
|
||||
return nil, "can't access variables " .. var .. " from datastore : " .. err
|
||||
end
|
||||
|
@ -56,9 +56,9 @@ utils.has_variable = function(var, value)
|
|||
return variables["global"][var] == value, "success"
|
||||
end
|
||||
|
||||
utils.has_not_variable = function(var, value)
|
||||
utils.has_not_variable = function(var, value)
|
||||
-- Get global variable
|
||||
local variables, err = datastore:get('variables', true)
|
||||
local variables, err = datastore:get("variables", true)
|
||||
if not variables then
|
||||
return nil, "can't access variables " .. var .. " from datastore : " .. err
|
||||
end
|
||||
|
@ -80,9 +80,9 @@ utils.has_not_variable = function(var, value)
|
|||
end
|
||||
|
||||
utils.get_multiple_variables = function(vars)
|
||||
local variables, err = datastore:get('variables', true)
|
||||
local variables, err = datastore:get("variables", true)
|
||||
if not variables then
|
||||
return nil, "can't access variables " .. var .. " from datastore : " .. err
|
||||
return nil, "can't access variables " .. vars .. " from datastore : " .. err
|
||||
end
|
||||
local result = {}
|
||||
-- Loop on scoped vars
|
||||
|
@ -90,7 +90,7 @@ utils.get_multiple_variables = function(vars)
|
|||
result[scope] = {}
|
||||
-- Loop on vars
|
||||
for variable, value in pairs(scoped_vars) do
|
||||
for i, var in ipairs(vars) do
|
||||
for _, var in ipairs(vars) do
|
||||
if variable:find("^" .. var .. "_?[0-9]*$") then
|
||||
result[scope][variable] = value
|
||||
end
|
||||
|
@ -100,7 +100,7 @@ utils.get_multiple_variables = function(vars)
|
|||
return result
|
||||
end
|
||||
|
||||
utils.is_ip_in_networks = function(ip, networks)
|
||||
utils.is_ip_in_networks = function(ip, networks)
|
||||
-- Instantiate ipmatcher
|
||||
local ipm, err = ipmatcher.new(networks)
|
||||
if not ipm then
|
||||
|
@ -114,15 +114,15 @@ utils.is_ip_in_networks = function(ip, networks)
|
|||
return matched
|
||||
end
|
||||
|
||||
utils.is_ipv4 = function(ip)
|
||||
utils.is_ipv4 = function(ip)
|
||||
return ipmatcher.parse_ipv4(ip)
|
||||
end
|
||||
|
||||
utils.is_ipv6 = function(ip)
|
||||
utils.is_ipv6 = function(ip)
|
||||
return ipmatcher.parse_ipv6(ip)
|
||||
end
|
||||
|
||||
utils.ip_is_global = function(ip)
|
||||
utils.ip_is_global = function(ip)
|
||||
-- Reserved, non public IPs
|
||||
local reserved_ips = {
|
||||
"0.0.0.0/8",
|
||||
|
@ -154,7 +154,7 @@ utils.ip_is_global = function(ip)
|
|||
"2002::/16",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"ff00::/8"
|
||||
"ff00::/8",
|
||||
}
|
||||
-- Instantiate ipmatcher
|
||||
local ipm, err = ipmatcher.new(reserved_ips)
|
||||
|
@ -169,9 +169,9 @@ utils.ip_is_global = function(ip)
|
|||
return not matched, "success"
|
||||
end
|
||||
|
||||
utils.get_integration = function()
|
||||
utils.get_integration = function()
|
||||
-- Check if already in datastore
|
||||
local integration, err = datastore:get("misc_integration", true)
|
||||
local integration, _ = datastore:get("misc_integration", true)
|
||||
if integration then
|
||||
return integration
|
||||
end
|
||||
|
@ -193,12 +193,12 @@ utils.get_integration = function()
|
|||
integration = "autoconf"
|
||||
else
|
||||
-- Already present (e.g. : linux)
|
||||
local f, err = io.open("/usr/share/bunkerweb/INTEGRATION", "r")
|
||||
local f, _ = io.open("/usr/share/bunkerweb/INTEGRATION", "r")
|
||||
if f then
|
||||
integration = f:read("*a"):gsub("[\n\r]", "")
|
||||
f:close()
|
||||
else
|
||||
local f, err = io.open("/etc/os-release", "r")
|
||||
f, _ = io.open("/etc/os-release", "r")
|
||||
if f then
|
||||
local data = f:read("*a")
|
||||
f:close()
|
||||
|
@ -222,9 +222,9 @@ utils.get_integration = function()
|
|||
return integration
|
||||
end
|
||||
|
||||
utils.get_version = function()
|
||||
utils.get_version = function()
|
||||
-- Check if already in datastore
|
||||
local version, err = datastore:get("misc_version", true)
|
||||
local version, _ = datastore:get("misc_version", true)
|
||||
if version then
|
||||
return version
|
||||
end
|
||||
|
@ -244,7 +244,7 @@ utils.get_version = function()
|
|||
return version
|
||||
end
|
||||
|
||||
utils.get_reason = function(ctx)
|
||||
utils.get_reason = function(ctx)
|
||||
-- ngx.ctx
|
||||
if ctx.bw.reason then
|
||||
return ctx.bw.reason
|
||||
|
@ -258,7 +258,7 @@ utils.get_reason = function(ctx)
|
|||
return "modsecurity"
|
||||
end
|
||||
-- datastore ban
|
||||
local banned, err = datastore:get("bans_ip_" .. ngx.var.remote_addr)
|
||||
local banned, _ = datastore:get("bans_ip_" .. ngx.var.remote_addr)
|
||||
if banned then
|
||||
return banned
|
||||
end
|
||||
|
@ -269,9 +269,9 @@ utils.get_reason = function(ctx)
|
|||
return nil
|
||||
end
|
||||
|
||||
utils.get_resolvers = function()
|
||||
utils.get_resolvers = function()
|
||||
-- Get resolvers from datastore if existing
|
||||
local resolvers, err = datastore:get("misc_resolvers", true)
|
||||
local resolvers, _ = datastore:get("misc_resolvers", true)
|
||||
if resolvers then
|
||||
return resolvers
|
||||
end
|
||||
|
@ -282,7 +282,7 @@ utils.get_resolvers = function()
|
|||
return "unknown"
|
||||
end
|
||||
-- Make table for resolver1 resolver2 ... string
|
||||
local resolvers = {}
|
||||
resolvers = {}
|
||||
for str_resolver in variables["global"]["DNS_RESOLVERS"]:gmatch("%S+") do
|
||||
table.insert(resolvers, str_resolver)
|
||||
end
|
||||
|
@ -294,7 +294,7 @@ utils.get_resolvers = function()
|
|||
return resolvers
|
||||
end
|
||||
|
||||
utils.get_rdns = function(ip)
|
||||
utils.get_rdns = function(ip)
|
||||
-- Check cache
|
||||
local cachestore = utils.new_cachestore()
|
||||
local ok, value = cachestore:get("rdns_" .. ip)
|
||||
|
@ -312,7 +312,7 @@ utils.get_rdns = function(ip)
|
|||
local rdns, err = resolver:new {
|
||||
nameservers = resolvers,
|
||||
retrans = 1,
|
||||
timeout = 1000
|
||||
timeout = 1000,
|
||||
}
|
||||
if not rdns then
|
||||
return false, err
|
||||
|
@ -330,21 +330,21 @@ utils.get_rdns = function(ip)
|
|||
ret_err = answers.errstr
|
||||
end
|
||||
-- Extract all PTR
|
||||
for i, answer in ipairs(answers) do
|
||||
for _, answer in ipairs(answers) do
|
||||
if answer.ptrdname then
|
||||
table.insert(ptrs, answer.ptrdname)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Save to cache
|
||||
local ok, err = cachestore:set("rdns_" .. ip, cjson.encode(ptrs), 3600)
|
||||
ok, err = cachestore:set("rdns_" .. ip, cjson.encode(ptrs), 3600)
|
||||
if not ok then
|
||||
logger:log(ngx.ERR, "can't set rdns into cachestore : " .. err)
|
||||
end
|
||||
return ptrs, ret_err
|
||||
end
|
||||
|
||||
utils.get_ips = function(fqdn, ipv6)
|
||||
utils.get_ips = function(fqdn, ipv6)
|
||||
-- Check cache
|
||||
local cachestore = utils.new_cachestore()
|
||||
local ok, value = cachestore:get("dns_" .. fqdn)
|
||||
|
@ -366,7 +366,7 @@ utils.get_ips = function(fqdn, ipv6)
|
|||
local res, err = resolver:new {
|
||||
nameservers = resolvers,
|
||||
retrans = 1,
|
||||
timeout = 1000
|
||||
timeout = 1000,
|
||||
}
|
||||
if not res then
|
||||
return false, err
|
||||
|
@ -374,6 +374,7 @@ utils.get_ips = function(fqdn, ipv6)
|
|||
-- Get query types : AAAA and A if using IPv6 / only A if not using IPv6
|
||||
local qtypes = {}
|
||||
if ipv6 then
|
||||
-- luacheck: ignore 421
|
||||
local use_ipv6, err = utils.get_variable("USE_IPV6", false)
|
||||
if not use_ipv6 then
|
||||
logger:log(ngx.ERR, "can't get USE_IPV6 variable " .. err)
|
||||
|
@ -386,9 +387,10 @@ utils.get_ips = function(fqdn, ipv6)
|
|||
local res_answers = {}
|
||||
local res_errors = {}
|
||||
local ans_errors = {}
|
||||
for i, qtype in ipairs(qtypes) do
|
||||
local answers
|
||||
for _, qtype in ipairs(qtypes) do
|
||||
-- Query FQDN
|
||||
local answers, err = res:query(fqdn, { qtype = qtype }, {})
|
||||
answers, err = res:query(fqdn, { qtype = qtype }, {})
|
||||
local qtype_str = qtype == res.TYPE_AAAA and "AAAA" or "A"
|
||||
if not answers then
|
||||
res_errors[qtype_str] = err
|
||||
|
@ -403,22 +405,23 @@ utils.get_ips = function(fqdn, ipv6)
|
|||
end
|
||||
-- Extract all IPs
|
||||
local ips = {}
|
||||
for i, answers in ipairs(res_answers) do
|
||||
for j, answer in ipairs(answers) do
|
||||
-- luacheck: ignore 421
|
||||
for _, answers in ipairs(res_answers) do
|
||||
for _, answer in ipairs(answers) do
|
||||
if answer.address then
|
||||
table.insert(ips, answer.address)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Save to cache
|
||||
local ok, err = cachestore:set("dns_" .. fqdn, cjson.encode(ips), 3600)
|
||||
ok, err = cachestore:set("dns_" .. fqdn, cjson.encode(ips), 3600)
|
||||
if not ok then
|
||||
logger:log(ngx.ERR, "can't set dns into cachestore : " .. err)
|
||||
end
|
||||
return ips, cjson.encode(res_errors) .. " " .. cjson.encode(ans_errors)
|
||||
end
|
||||
|
||||
utils.get_country = function(ip)
|
||||
utils.get_country = function(ip)
|
||||
-- Check if mmdb is loaded
|
||||
if not mmdb.country_db then
|
||||
return false, "mmdb country not loaded"
|
||||
|
@ -434,7 +437,7 @@ utils.get_country = function(ip)
|
|||
return result.country.iso_code, "success"
|
||||
end
|
||||
|
||||
utils.get_asn = function(ip)
|
||||
utils.get_asn = function(ip)
|
||||
-- Check if mmdp is loaded
|
||||
if not mmdb.asn_db then
|
||||
return false, "mmdb asn not loaded"
|
||||
|
@ -450,22 +453,28 @@ utils.get_asn = function(ip)
|
|||
return result.autonomous_system_number, "success"
|
||||
end
|
||||
|
||||
utils.rand = function(nb, no_numbers)
|
||||
utils.rand = function(nb, no_numbers)
|
||||
local charset = {}
|
||||
-- lowers, uppers and numbers
|
||||
if not no_numbers then
|
||||
for i = 48, 57 do table.insert(charset, string.char(i)) end
|
||||
for i = 48, 57 do
|
||||
table.insert(charset, string.char(i))
|
||||
end
|
||||
end
|
||||
for i = 65, 90 do
|
||||
table.insert(charset, string.char(i))
|
||||
end
|
||||
for i = 97, 122 do
|
||||
table.insert(charset, string.char(i))
|
||||
end
|
||||
for i = 65, 90 do table.insert(charset, string.char(i)) end
|
||||
for i = 97, 122 do table.insert(charset, string.char(i)) end
|
||||
local result = ""
|
||||
for i = 1, nb do
|
||||
for _ = 1, nb do
|
||||
result = result .. charset[math.random(1, #charset)]
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
utils.get_deny_status = function(ctx)
|
||||
utils.get_deny_status = function(ctx)
|
||||
-- Stream case
|
||||
if ctx.bw and ctx.bw.kind == "stream" then
|
||||
return 444
|
||||
|
@ -479,10 +488,10 @@ utils.get_deny_status = function(ctx)
|
|||
return tonumber(variables["global"]["DENY_HTTP_STATUS"])
|
||||
end
|
||||
|
||||
utils.check_session = function(ctx)
|
||||
local _session, err, exists, refreshed = session.start({ audience = "metadata" })
|
||||
utils.check_session = function(ctx)
|
||||
local _session, _, exists, _ = session.start({ audience = "metadata" })
|
||||
if exists then
|
||||
for i, check in ipairs(ctx.bw.sessions_checks) do
|
||||
for _, check in ipairs(ctx.bw.sessions_checks) do
|
||||
local key = check[1]
|
||||
local value = check[2]
|
||||
if _session:get(key) ~= value then
|
||||
|
@ -496,7 +505,7 @@ utils.check_session = function(ctx)
|
|||
end
|
||||
end
|
||||
else
|
||||
for i, check in ipairs(ctx.bw.sessions_checks) do
|
||||
for _, check in ipairs(ctx.bw.sessions_checks) do
|
||||
_session:set(check[1], check[2])
|
||||
end
|
||||
local ok, err = _session:save()
|
||||
|
@ -509,7 +518,7 @@ utils.check_session = function(ctx)
|
|||
return true, exists
|
||||
end
|
||||
|
||||
utils.get_session = function(audience, ctx)
|
||||
utils.get_session = function(audience, ctx)
|
||||
-- Check session
|
||||
if not ctx.bw.sessions_is_checked then
|
||||
local ok, err = utils.check_session(ctx)
|
||||
|
@ -518,14 +527,15 @@ utils.get_session = function(audience, ctx)
|
|||
end
|
||||
end
|
||||
-- Open session with specific audience
|
||||
local _session, err, exists = session.open({ audience = audience })
|
||||
local _session, err, _ = session.open({ audience = audience })
|
||||
if err then
|
||||
logger:log(ngx.INFO, "session:open() error : " .. err)
|
||||
end
|
||||
return _session
|
||||
end
|
||||
|
||||
utils.get_session_data = function(_session, site, ctx)
|
||||
-- luacheck: ignore 214
|
||||
utils.get_session_data = function(_session, site, ctx)
|
||||
local site_only = site == nil or site
|
||||
local data = _session:get_data()
|
||||
if site_only then
|
||||
|
@ -534,7 +544,8 @@ utils.get_session_data = function(_session, site, ctx)
|
|||
return data
|
||||
end
|
||||
|
||||
utils.set_session_data = function(_session, data, site, ctx)
|
||||
-- luacheck: ignore 214
|
||||
utils.set_session_data = function(_session, data, site, ctx)
|
||||
local site_only = site == nil or site
|
||||
if site_only then
|
||||
local all_data = _session:get_data()
|
||||
|
@ -546,7 +557,7 @@ utils.set_session_data = function(_session, data, site, ctx)
|
|||
return _session:save()
|
||||
end
|
||||
|
||||
utils.is_banned = function(ip)
|
||||
utils.is_banned = function(ip)
|
||||
-- Check on local datastore
|
||||
local reason, err = datastore:get("bans_ip_" .. ip)
|
||||
if not reason and err ~= "not found" then
|
||||
|
@ -599,7 +610,7 @@ utils.is_banned = function(ip)
|
|||
elseif data[1] ~= ngx.null then
|
||||
clusterstore:close()
|
||||
-- Update local cache
|
||||
local ok, err = datastore:set("bans_ip_" .. ip, data[1], data[2])
|
||||
ok, err = datastore:set("bans_ip_" .. ip, data[1], data[2])
|
||||
if not ok then
|
||||
return nil, "datastore:set() error : " .. err
|
||||
end
|
||||
|
@ -609,7 +620,7 @@ utils.is_banned = function(ip)
|
|||
return false, "not banned"
|
||||
end
|
||||
|
||||
utils.add_ban = function(ip, reason, ttl)
|
||||
utils.add_ban = function(ip, reason, ttl)
|
||||
-- Set on local datastore
|
||||
local ok, err = datastore:set("bans_ip_" .. ip, reason, ttl)
|
||||
if not ok then
|
||||
|
@ -624,12 +635,12 @@ utils.add_ban = function(ip, reason, ttl)
|
|||
end
|
||||
-- Connect
|
||||
local clusterstore = require "bunkerweb.clusterstore":new()
|
||||
local ok, err = clusterstore:connect()
|
||||
ok, err = clusterstore:connect()
|
||||
if not ok then
|
||||
return false, "can't connect to redis server : " .. err
|
||||
end
|
||||
-- SET call
|
||||
local ok, err = clusterstore:call("set", "bans_ip_" .. ip, reason, "EX", ttl)
|
||||
ok, err = clusterstore:call("set", "bans_ip_" .. ip, reason, "EX", ttl)
|
||||
if not ok then
|
||||
clusterstore:close()
|
||||
return false, "redis SET failed : " .. err
|
||||
|
@ -638,7 +649,7 @@ utils.add_ban = function(ip, reason, ttl)
|
|||
return true, "success"
|
||||
end
|
||||
|
||||
utils.new_cachestore = function()
|
||||
utils.new_cachestore = function()
|
||||
-- Check if redis is used
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
if not use_redis then
|
||||
|
@ -650,7 +661,7 @@ utils.new_cachestore = function()
|
|||
return require "bunkerweb.cachestore":new(use_redis, true)
|
||||
end
|
||||
|
||||
utils.regex_match = function(str, regex, options)
|
||||
utils.regex_match = function(str, regex, options)
|
||||
local all_options = "o"
|
||||
if options then
|
||||
all_options = all_options .. options
|
||||
|
@ -663,7 +674,7 @@ utils.regex_match = function(str, regex, options)
|
|||
return match
|
||||
end
|
||||
|
||||
utils.get_phases = function()
|
||||
utils.get_phases = function()
|
||||
return {
|
||||
"init",
|
||||
"init_worker",
|
||||
|
@ -673,18 +684,18 @@ utils.get_phases = function()
|
|||
"log",
|
||||
"preread",
|
||||
"log_stream",
|
||||
"log_default"
|
||||
"log_default",
|
||||
}
|
||||
end
|
||||
|
||||
utils.is_cosocket_available = function()
|
||||
utils.is_cosocket_available = function()
|
||||
local phases = {
|
||||
"timer",
|
||||
"access",
|
||||
"preread"
|
||||
"preread",
|
||||
}
|
||||
local current_phase = ngx.get_phase()
|
||||
for i, phase in ipairs(phases) do
|
||||
for _, phase in ipairs(phases) do
|
||||
if current_phase == phase then
|
||||
return true
|
||||
end
|
||||
|
@ -692,8 +703,8 @@ utils.is_cosocket_available = function()
|
|||
return false
|
||||
end
|
||||
|
||||
utils.kill_all_threads = function(threads)
|
||||
for i, thread in ipairs(threads) do
|
||||
utils.kill_all_threads = function(threads)
|
||||
for _, thread in ipairs(threads) do
|
||||
local ok, err = ngx.thread.kill(thread)
|
||||
if not ok then
|
||||
logger:log(ngx.ERR, "error while killing thread : " .. err)
|
||||
|
@ -701,7 +712,7 @@ utils.kill_all_threads = function(threads)
|
|||
end
|
||||
end
|
||||
|
||||
utils.get_ctx_obj = function(obj)
|
||||
utils.get_ctx_obj = function(obj)
|
||||
if ngx.ctx and ngx.ctx.bw then
|
||||
return ngx.ctx.bw[obj]
|
||||
end
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local captcha = require "antibot.captcha"
|
||||
local base64 = require "base64"
|
||||
local sha256 = require "resty.sha256"
|
||||
local str = require "resty.string"
|
||||
local http = require "resty.http"
|
||||
local base64 = require "base64"
|
||||
local captcha = require "antibot.captcha"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local http = require "resty.http"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local sha256 = require "resty.sha256"
|
||||
local str = require "resty.string"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local template = nil
|
||||
if ngx.shared.datastore then
|
||||
template = require "resty.template"
|
||||
|
@ -51,40 +51,41 @@ function antibot:header()
|
|||
return self:ret(true, "Not antibot uri")
|
||||
end
|
||||
|
||||
|
||||
local header = "Content-Security-Policy"
|
||||
if self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
|
||||
header = header .. "-Report-Only"
|
||||
end
|
||||
|
||||
if self.session_data.type == "recaptcha" then
|
||||
ngx.header[header] =
|
||||
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
|
||||
self.session_data.nonce_script ..
|
||||
"' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' http: https:; img-src https://www.gstatic.com/recaptcha/ 'self' data:; frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/; style-src 'self' 'nonce-" ..
|
||||
self.session_data.nonce_style ..
|
||||
"'; font-src 'self' https://fonts.gstatic.com data:; base-uri 'self';"
|
||||
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
|
||||
.. self.session_data.nonce_script
|
||||
.. "' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' http: https:;"
|
||||
.. " img-src https://www.gstatic.com/recaptcha/ 'self' data:; "
|
||||
.. " frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/;"
|
||||
.. " style-src 'self' 'nonce-"
|
||||
.. self.session_data.nonce_style
|
||||
.. "'; font-src 'self' https://fonts.gstatic.com data:; base-uri 'self';"
|
||||
elseif self.session_data.type == "hcaptcha" then
|
||||
ngx.header[header] =
|
||||
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
|
||||
self.session_data.nonce_script ..
|
||||
"' https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' http: https:; img-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; style-src 'self' 'nonce-" ..
|
||||
self.session_data.nonce_style ..
|
||||
"' https://hcaptcha.com https://*.hcaptcha.com; connect-src https://hcaptcha.com https://*.hcaptcha.com; font-src 'self' data:; base-uri 'self';"
|
||||
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
|
||||
.. self.session_data.nonce_script
|
||||
.. "' https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' http: https:; img-src 'self' data:;"
|
||||
.. " frame-src https://hcaptcha.com https://*.hcaptcha.com; style-src 'self' 'nonce-"
|
||||
.. self.session_data.nonce_style
|
||||
.. "' https://hcaptcha.com https://*.hcaptcha.com; connect-src https://hcaptcha.com https://*.hcaptcha.com; "
|
||||
.. " font-src 'self' data:; base-uri 'self';"
|
||||
elseif self.session_data.type == "turnstile" then
|
||||
ngx.header[header] =
|
||||
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
|
||||
self.session_data.nonce_script ..
|
||||
"' https://challenges.cloudflare.com 'unsafe-inline' http: https:; img-src 'self' data:; frame-src https://challenges.cloudflare.com; style-src 'self' 'nonce-" ..
|
||||
self.session_data.nonce_style ..
|
||||
"'; font-src 'self' data:; base-uri 'self';"
|
||||
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
|
||||
.. self.session_data.nonce_script
|
||||
.. "' https://challenges.cloudflare.com 'unsafe-inline' http: https:; img-src 'self' data:;"
|
||||
.. " frame-src https://challenges.cloudflare.com; style-src 'self' 'nonce-"
|
||||
.. self.session_data.nonce_style
|
||||
.. "'; font-src 'self' data:; base-uri 'self';"
|
||||
else
|
||||
ngx.header[header] =
|
||||
"default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-" ..
|
||||
self.session_data.nonce_script ..
|
||||
"' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-" ..
|
||||
self.session_data.nonce_style ..
|
||||
"'; font-src 'self' data:; base-uri 'self';"
|
||||
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
|
||||
.. self.session_data.nonce_script
|
||||
.. "' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-"
|
||||
.. self.session_data.nonce_style
|
||||
.. "'; font-src 'self' data:; base-uri 'self';"
|
||||
end
|
||||
return self:ret(true, "Successfully overridden CSP header")
|
||||
end
|
||||
|
@ -138,6 +139,7 @@ function antibot:access()
|
|||
|
||||
-- Check challenge
|
||||
if self.ctx.bw.request_method == "POST" then
|
||||
-- luacheck: ignore 421
|
||||
local ok, err, redirect = self:check_challenge()
|
||||
local set_ok, set_err = self:set_session_data()
|
||||
if not set_ok then
|
||||
|
@ -152,7 +154,7 @@ function antibot:access()
|
|||
return self:ret(true, "check challenge redirect : " .. redirect, nil, redirect)
|
||||
end
|
||||
self:prepare_challenge()
|
||||
local ok, err = self:set_session_data()
|
||||
ok, err = self:set_session_data()
|
||||
if not ok then
|
||||
return self:ret(false, "can't save session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
|
||||
end
|
||||
|
@ -215,7 +217,9 @@ function antibot:check_session()
|
|||
return
|
||||
end
|
||||
-- Check if new prepare is needed
|
||||
if not resolved and (time_resolve > time or time - time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"])) then
|
||||
if
|
||||
not resolved and (time_resolve > time or time - time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"]))
|
||||
then
|
||||
self.session_data = {}
|
||||
self.session_updated = true
|
||||
return
|
||||
|
@ -312,7 +316,7 @@ function antibot:check_challenge()
|
|||
return nil, "challenge not prepared"
|
||||
end
|
||||
|
||||
local resolved = false
|
||||
local resolved
|
||||
|
||||
self.session_data.prepared = false
|
||||
self.session_updated = true
|
||||
|
@ -364,12 +368,15 @@ function antibot:check_challenge()
|
|||
end
|
||||
local res, err = httpc:request_uri("https://www.google.com/recaptcha/api/siteverify", {
|
||||
method = "POST",
|
||||
body = "secret=" ..
|
||||
self.variables["ANTIBOT_RECAPTCHA_SECRET"] ..
|
||||
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
|
||||
body = "secret="
|
||||
.. self.variables["ANTIBOT_RECAPTCHA_SECRET"]
|
||||
.. "&response="
|
||||
.. args["token"]
|
||||
.. "&remoteip="
|
||||
.. self.ctx.bw.remote_addr,
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if not res then
|
||||
|
@ -400,12 +407,15 @@ function antibot:check_challenge()
|
|||
end
|
||||
local res, err = httpc:request_uri("https://hcaptcha.com/siteverify", {
|
||||
method = "POST",
|
||||
body = "secret=" ..
|
||||
self.variables["ANTIBOT_HCAPTCHA_SECRET"] ..
|
||||
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
|
||||
body = "secret="
|
||||
.. self.variables["ANTIBOT_HCAPTCHA_SECRET"]
|
||||
.. "&response="
|
||||
.. args["token"]
|
||||
.. "&remoteip="
|
||||
.. self.ctx.bw.remote_addr,
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if not res then
|
||||
|
@ -413,7 +423,7 @@ function antibot:check_challenge()
|
|||
end
|
||||
local ok, hdata = pcall(cjson.decode, res.body)
|
||||
if not ok then
|
||||
return nil, "error while decoding JSON from hCaptcha API : " .. data, nil
|
||||
return nil, "error while decoding JSON from hCaptcha API : " .. hdata, nil
|
||||
end
|
||||
if not hdata.success then
|
||||
return false, "client failed challenge", nil
|
||||
|
@ -436,12 +446,15 @@ function antibot:check_challenge()
|
|||
end
|
||||
local res, err = httpc:request_uri("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
||||
method = "POST",
|
||||
body = "secret=" ..
|
||||
self.variables["ANTIBOT_TURNSTILE_SECRET"] ..
|
||||
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
|
||||
body = "secret="
|
||||
.. self.variables["ANTIBOT_TURNSTILE_SECRET"]
|
||||
.. "&response="
|
||||
.. args["token"]
|
||||
.. "&remoteip="
|
||||
.. self.ctx.bw.remote_addr,
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if not res then
|
||||
|
@ -449,7 +462,7 @@ function antibot:check_challenge()
|
|||
end
|
||||
local ok, tdata = pcall(cjson.decode, res.body)
|
||||
if not ok then
|
||||
return nil, "error while decoding JSON from Turnstile API : " .. data, nil
|
||||
return nil, "error while decoding JSON from Turnstile API : " .. tdata, nil
|
||||
end
|
||||
if not tdata.success then
|
||||
return false, "client failed challenge", nil
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local badbehavior = class("badbehavior", plugin)
|
||||
|
||||
|
@ -23,14 +23,20 @@ function badbehavior:log()
|
|||
return self:ret(true, "not increasing counter")
|
||||
end
|
||||
-- Check if we are already banned
|
||||
local banned, err = self.datastore:get("bans_ip_" .. self.ctx.bw.remote_addr)
|
||||
local banned, _ = self.datastore:get("bans_ip_" .. self.ctx.bw.remote_addr)
|
||||
if banned then
|
||||
return self:ret(true, "already banned")
|
||||
end
|
||||
-- Call increase function later and with cosocket enabled
|
||||
local ok, err = ngx.timer.at(0, badbehavior.increase, self.ctx.bw.remote_addr,
|
||||
tonumber(self.variables["BAD_BEHAVIOR_COUNT_TIME"]), tonumber(self.variables["BAD_BEHAVIOR_BAN_TIME"]),
|
||||
tonumber(self.variables["BAD_BEHAVIOR_THRESHOLD"]), self.use_redis)
|
||||
local ok, err = ngx.timer.at(
|
||||
0,
|
||||
badbehavior.increase,
|
||||
self.ctx.bw.remote_addr,
|
||||
tonumber(self.variables["BAD_BEHAVIOR_COUNT_TIME"]),
|
||||
tonumber(self.variables["BAD_BEHAVIOR_BAN_TIME"]),
|
||||
tonumber(self.variables["BAD_BEHAVIOR_THRESHOLD"]),
|
||||
self.use_redis
|
||||
)
|
||||
if not ok then
|
||||
return self:ret(false, "can't create increase timer : " .. err)
|
||||
end
|
||||
|
@ -45,6 +51,7 @@ function badbehavior:log_stream()
|
|||
return self:log()
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function badbehavior.increase(premature, ip, count_time, ban_time, threshold, use_redis)
|
||||
-- Instantiate objects
|
||||
local logger = require "bunkerweb.logger":new("badbehavior")
|
||||
|
@ -84,16 +91,28 @@ function badbehavior.increase(premature, ip, count_time, ban_time, threshold, us
|
|||
end
|
||||
-- Store local ban
|
||||
if counter > threshold then
|
||||
local ok, err = utils.add_ban(ip, "bad behavior", ban_time)
|
||||
ok, err = utils.add_ban(ip, "bad behavior", ban_time)
|
||||
if not ok then
|
||||
logger:log(ngx.ERR, "(increase) can't save ban : " .. err)
|
||||
return
|
||||
end
|
||||
logger:log(ngx.WARN,
|
||||
"IP " .. ip .. " is banned for " .. ban_time .. "s (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
|
||||
logger:log(
|
||||
ngx.WARN,
|
||||
"IP "
|
||||
.. ip
|
||||
.. " is banned for "
|
||||
.. ban_time
|
||||
.. "s ("
|
||||
.. tostring(counter)
|
||||
.. "/"
|
||||
.. tostring(threshold)
|
||||
.. ")"
|
||||
)
|
||||
end
|
||||
logger:log(ngx.NOTICE,
|
||||
"increased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
|
||||
logger:log(
|
||||
ngx.NOTICE,
|
||||
"increased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")"
|
||||
)
|
||||
end
|
||||
|
||||
function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
|
||||
|
@ -126,7 +145,7 @@ function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
|
|||
-- Store local counter
|
||||
if counter <= 0 then
|
||||
counter = 0
|
||||
local ok, err = datastore:delete("plugin_badbehavior_count_" .. ip)
|
||||
datastore:delete("plugin_badbehavior_count_" .. ip)
|
||||
else
|
||||
local ok, err = datastore:set("plugin_badbehavior_count_" .. ip, counter, count_time)
|
||||
if not ok then
|
||||
|
@ -134,8 +153,10 @@ function badbehavior.decrease(premature, ip, count_time, threshold, use_redis)
|
|||
return
|
||||
end
|
||||
end
|
||||
logger:log(ngx.NOTICE,
|
||||
"decreased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")")
|
||||
logger:log(
|
||||
ngx.NOTICE,
|
||||
"decreased counter for IP " .. ip .. " (" .. tostring(counter) .. "/" .. tostring(threshold) .. ")"
|
||||
)
|
||||
end
|
||||
|
||||
function badbehavior.redis_increase(ip, count_time, ban_time)
|
||||
|
@ -168,9 +189,8 @@ function badbehavior.redis_increase(ip, count_time, ban_time)
|
|||
return false, err
|
||||
end
|
||||
-- Execute LUA script
|
||||
local counter, err = clusterstore:call("eval", redis_script, 2, "plugin_bad_behavior_" .. ip, "bans_ip" .. ip,
|
||||
count_time,
|
||||
ban_time)
|
||||
local counter, err =
|
||||
clusterstore:call("eval", redis_script, 2, "plugin_bad_behavior_" .. ip, "bans_ip" .. ip, count_time, ban_time)
|
||||
if not counter then
|
||||
clusterstore:close()
|
||||
return false, err
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local blacklist = class("blacklist", plugin)
|
||||
|
||||
|
@ -78,7 +78,7 @@ function blacklist:init()
|
|||
}
|
||||
local i = 0
|
||||
for kind, _ in pairs(blacklists) do
|
||||
local f, err = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
|
||||
local f, _ = io.open("/var/cache/bunkerweb/blacklist/" .. kind .. ".list", "r")
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
table.insert(blacklists[kind], line)
|
||||
|
@ -102,7 +102,7 @@ function blacklist:access()
|
|||
end
|
||||
-- Check the caches
|
||||
local checks = {
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr,
|
||||
}
|
||||
if self.ctx.bw.http_user_agent then
|
||||
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
|
||||
|
@ -113,14 +113,18 @@ function blacklist:access()
|
|||
local already_cached = {
|
||||
["IP"] = false,
|
||||
["URI"] = false,
|
||||
["UA"] = false
|
||||
["UA"] = false,
|
||||
}
|
||||
for k, v in pairs(checks) do
|
||||
local ok, cached = self:is_in_cache(v)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error while checking cache : " .. cached)
|
||||
elseif cached and cached ~= "ok" then
|
||||
return self:ret(true, k .. " is in cached blacklist (info : " .. cached .. ")", utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
k .. " is in cached blacklist (info : " .. cached .. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
if ok and cached then
|
||||
already_cached[k] = true
|
||||
|
@ -131,18 +135,23 @@ function blacklist:access()
|
|||
return self:ret(false, "lists is nil")
|
||||
end
|
||||
-- Perform checks
|
||||
for k, v in pairs(checks) do
|
||||
for k, _ in pairs(checks) do
|
||||
if not already_cached[k] then
|
||||
local ok, blacklisted = self:is_blacklisted(k)
|
||||
if ok == nil then
|
||||
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is blacklisted : " .. blacklisted)
|
||||
else
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self:add_to_cache(self:kind_to_ele(k), blacklisted)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
|
||||
end
|
||||
if blacklisted ~= "ok" then
|
||||
return self:ret(true, k .. " is blacklisted (info : " .. blacklisted .. ")", utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
k .. " is blacklisted (info : " .. blacklisted .. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -205,11 +214,11 @@ function blacklist:is_blacklisted_ip()
|
|||
end
|
||||
if not match then
|
||||
-- Check if IP is in blacklist
|
||||
local ipm, err = ipmatcher.new(self.lists["IP"])
|
||||
ipm, err = ipmatcher.new(self.lists["IP"])
|
||||
if not ipm then
|
||||
return nil, err
|
||||
end
|
||||
local match, err = ipm:match(self.ctx.bw.remote_addr)
|
||||
match, err = ipm:match(self.ctx.bw.remote_addr)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
|
@ -225,13 +234,14 @@ function blacklist:is_blacklisted_ip()
|
|||
end
|
||||
if check_rdns then
|
||||
-- Get rDNS
|
||||
-- luacheck: ignore 421
|
||||
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
|
||||
if rdns_list then
|
||||
-- Check if rDNS is in ignore list
|
||||
local ignore = false
|
||||
for i, rdns in ipairs(rdns_list) do
|
||||
for j, suffix in ipairs(self.lists["IGNORE_RDNS"]) do
|
||||
if rdns:sub(- #suffix) == suffix then
|
||||
for _, rdns in ipairs(rdns_list) do
|
||||
for _, suffix in ipairs(self.lists["IGNORE_RDNS"]) do
|
||||
if rdns:sub(-#suffix) == suffix then
|
||||
ignore = true
|
||||
break
|
||||
end
|
||||
|
@ -239,9 +249,9 @@ function blacklist:is_blacklisted_ip()
|
|||
end
|
||||
-- Check if rDNS is in blacklist
|
||||
if not ignore then
|
||||
for i, rdns in ipairs(rdns_list) do
|
||||
for j, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(- #suffix) == suffix then
|
||||
for _, rdns in ipairs(rdns_list) do
|
||||
for _, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(-#suffix) == suffix then
|
||||
return true, "rDNS " .. suffix
|
||||
end
|
||||
end
|
||||
|
@ -259,7 +269,7 @@ function blacklist:is_blacklisted_ip()
|
|||
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
|
||||
else
|
||||
local ignore = false
|
||||
for i, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
|
||||
for _, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
|
||||
if ignore_asn == tostring(asn) then
|
||||
ignore = true
|
||||
break
|
||||
|
@ -267,7 +277,7 @@ function blacklist:is_blacklisted_ip()
|
|||
end
|
||||
-- Check if ASN is in blacklist
|
||||
if not ignore then
|
||||
for i, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
for _, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
if bl_asn == tostring(asn) then
|
||||
return true, "ASN " .. bl_asn
|
||||
end
|
||||
|
@ -283,7 +293,7 @@ end
|
|||
function blacklist:is_blacklisted_uri()
|
||||
-- Check if URI is in ignore list
|
||||
local ignore = false
|
||||
for i, ignore_uri in ipairs(self.lists["IGNORE_URI"]) do
|
||||
for _, ignore_uri in ipairs(self.lists["IGNORE_URI"]) do
|
||||
if utils.regex_match(self.ctx.bw.uri, ignore_uri) then
|
||||
ignore = true
|
||||
break
|
||||
|
@ -291,7 +301,7 @@ function blacklist:is_blacklisted_uri()
|
|||
end
|
||||
-- Check if URI is in blacklist
|
||||
if not ignore then
|
||||
for i, uri in ipairs(self.lists["URI"]) do
|
||||
for _, uri in ipairs(self.lists["URI"]) do
|
||||
if utils.regex_match(self.ctx.bw.uri, uri) then
|
||||
return true, "URI " .. uri
|
||||
end
|
||||
|
@ -304,7 +314,7 @@ end
|
|||
function blacklist:is_blacklisted_ua()
|
||||
-- Check if UA is in ignore list
|
||||
local ignore = false
|
||||
for i, ignore_ua in ipairs(self.lists["IGNORE_USER_AGENT"]) do
|
||||
for _, ignore_ua in ipairs(self.lists["IGNORE_USER_AGENT"]) do
|
||||
if utils.regex_match(self.ctx.bw.http_user_agent, ignore_ua) then
|
||||
ignore = true
|
||||
break
|
||||
|
@ -312,7 +322,7 @@ function blacklist:is_blacklisted_ua()
|
|||
end
|
||||
-- Check if UA is in blacklist
|
||||
if not ignore then
|
||||
for i, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
for _, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
|
||||
return true, "UA " .. ua
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local http = require "resty.http"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local http = require "resty.http"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local bunkernet = class("bunkernet", plugin)
|
||||
|
||||
|
@ -49,12 +49,15 @@ function bunkernet:init_worker()
|
|||
return self:ret(false, "missing instance ID")
|
||||
end
|
||||
-- Send ping request
|
||||
local ok, err, status, data = self:ping()
|
||||
local ok, err, status, _ = self:ping()
|
||||
if not ok then
|
||||
return self:ret(false, "error while sending request to API : " .. err)
|
||||
end
|
||||
if status ~= 200 then
|
||||
return self:ret(false, "received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id)
|
||||
return self:ret(
|
||||
false,
|
||||
"received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id
|
||||
)
|
||||
end
|
||||
self.logger:log(ngx.NOTICE, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
|
||||
return self:ret(true, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
|
||||
|
@ -82,7 +85,7 @@ function bunkernet:init()
|
|||
local ret = true
|
||||
local i = 0
|
||||
local db = {
|
||||
ip = {}
|
||||
ip = {},
|
||||
}
|
||||
local f, err = io.open("/var/cache/bunkerweb/bunkernet/ip.list", "r")
|
||||
if not f then
|
||||
|
@ -128,6 +131,7 @@ function bunkernet:access()
|
|||
if db then
|
||||
-- Check if is IP is present
|
||||
if #db.ip > 0 then
|
||||
-- luacheck: ignore 421
|
||||
local present, err = utils.is_ip_in_networks(self.ctx.bw.remote_addr, db.ip)
|
||||
if present == nil then
|
||||
return self:ret(false, "can't check if ip is in db : " .. err)
|
||||
|
@ -166,8 +170,9 @@ function bunkernet:log(bypass_checks)
|
|||
return self:ret(true, "IP is not global")
|
||||
end
|
||||
-- TODO : check if IP has been reported recently
|
||||
-- luacheck: ignore 212 431
|
||||
local function report_callback(premature, obj, ip, reason, method, url, headers)
|
||||
local ok, err, status, data = obj:report(ip, reason, method, url, headers)
|
||||
local ok, err, status, _ = obj:report(ip, reason, method, url, headers)
|
||||
if status == 429 then
|
||||
obj.logger:log(ngx.WARN, "bunkernet API is rate limiting us")
|
||||
elseif not ok then
|
||||
|
@ -177,8 +182,16 @@ function bunkernet:log(bypass_checks)
|
|||
end
|
||||
end
|
||||
|
||||
local hdr, err = ngx.timer.at(0, report_callback, self, self.ctx.bw.remote_addr, reason, self.ctx.bw.request_method,
|
||||
self.ctx.bw.request_uri, ngx.req.get_headers())
|
||||
local hdr, err = ngx.timer.at(
|
||||
0,
|
||||
report_callback,
|
||||
self,
|
||||
self.ctx.bw.remote_addr,
|
||||
reason,
|
||||
self.ctx.bw.request_method,
|
||||
self.ctx.bw.request_uri,
|
||||
ngx.req.get_headers()
|
||||
)
|
||||
if not hdr then
|
||||
return self:ret(false, "can't create report timer : " .. err)
|
||||
end
|
||||
|
@ -218,7 +231,7 @@ function bunkernet:request(method, url, data)
|
|||
local all_data = {
|
||||
id = self.bunkernet_id,
|
||||
version = self.version,
|
||||
integration = self.integration
|
||||
integration = self.integration,
|
||||
}
|
||||
if data then
|
||||
for k, v in pairs(data) do
|
||||
|
@ -230,8 +243,8 @@ function bunkernet:request(method, url, data)
|
|||
body = cjson.encode(all_data),
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
["User-Agent"] = "BunkerWeb/" .. self.version
|
||||
}
|
||||
["User-Agent"] = "BunkerWeb/" .. self.version,
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if not res then
|
||||
|
@ -257,7 +270,7 @@ function bunkernet:report(ip, reason, method, url, headers)
|
|||
reason = reason,
|
||||
method = method,
|
||||
url = url,
|
||||
headers = headers
|
||||
headers = headers,
|
||||
}
|
||||
return self:request("POST", "/report", data)
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
local class = require "middleclass"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local cors = class("cors", plugin)
|
||||
local cors = class("cors", plugin)
|
||||
|
||||
function cors:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
@ -17,7 +17,7 @@ function cors:initialize(ctx)
|
|||
["CORS_MAX_AGE"] = "Access-Control-Max-Age",
|
||||
["CORS_ALLOW_CREDENTIALS"] = "Access-Control-Allow-Credentials",
|
||||
["CORS_ALLOW_METHODS"] = "Access-Control-Allow-Methods",
|
||||
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers"
|
||||
["CORS_ALLOW_HEADERS"] = "Access-Control-Allow-Headers",
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,12 @@ function cors:header()
|
|||
ngx.header.Vary = "Origin"
|
||||
end
|
||||
-- Check if Origin is allowed
|
||||
if self.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
|
||||
if
|
||||
self.ctx.bw.http_origin
|
||||
and self.variables["CORS_DENY_REQUEST"] == "yes"
|
||||
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
|
||||
and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
|
||||
then
|
||||
self.logger:log(ngx.WARN, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
|
||||
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed")
|
||||
end
|
||||
|
@ -81,9 +86,17 @@ function cors:access()
|
|||
return self:ret(true, "service doesn't use CORS")
|
||||
end
|
||||
-- Deny as soon as possible if needed
|
||||
if self.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
|
||||
return self:ret(true, "origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access",
|
||||
utils.get_deny_status(self.ctx))
|
||||
if
|
||||
self.ctx.bw.http_origin
|
||||
and self.variables["CORS_DENY_REQUEST"] == "yes"
|
||||
and self.variables["CORS_ALLOW_ORIGIN"] ~= "*"
|
||||
and not utils.regex_match(self.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"])
|
||||
then
|
||||
return self:ret(
|
||||
true,
|
||||
"origin " .. self.ctx.bw.http_origin .. " is not allowed, denying access",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
-- Send CORS policy with a 204 (no content) status
|
||||
if self.ctx.bw.request_method == "OPTIONS" and self.ctx.bw.http_origin then
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local country = class("country", plugin)
|
||||
|
||||
|
@ -16,17 +16,28 @@ function country:access()
|
|||
return self:ret(true, "country not activated")
|
||||
end
|
||||
-- Check if IP is in cache
|
||||
local ok, data = self:is_in_cache(self.ctx.bw.remote_addr)
|
||||
local _, data = self:is_in_cache(self.ctx.bw.remote_addr)
|
||||
if data then
|
||||
data = cjson.decode(data)
|
||||
if data.result == "ok" then
|
||||
return self:ret(true,
|
||||
"client IP " ..
|
||||
self.ctx.bw.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")")
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
.. self.ctx.bw.remote_addr
|
||||
.. " is in country cache (not blacklisted, country = "
|
||||
.. data.country
|
||||
.. ")"
|
||||
)
|
||||
end
|
||||
return self:ret(true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")",
|
||||
utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
.. self.ctx.bw.remote_addr
|
||||
.. " is in country cache (blacklisted, country = "
|
||||
.. data.country
|
||||
.. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
|
||||
-- Don't go further if IP is not global
|
||||
|
@ -39,50 +50,64 @@ function country:access()
|
|||
end
|
||||
|
||||
-- Get the country of client
|
||||
local country, err = utils.get_country(self.ctx.bw.remote_addr)
|
||||
if not country then
|
||||
local country_data, err = utils.get_country(self.ctx.bw.remote_addr)
|
||||
if not country_data then
|
||||
return self:ret(false, "can't get country of client IP " .. self.ctx.bw.remote_addr .. " : " .. err)
|
||||
end
|
||||
|
||||
-- Process whitelist first
|
||||
if self.variables["WHITELIST_COUNTRY"] ~= "" then
|
||||
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch("%S+") do
|
||||
if wh_country == country then
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
|
||||
for wh_country in self.variables["WHITELIST_COUNTRY"]:gmatch "%S+" do
|
||||
if wh_country == country_data then
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ok")
|
||||
if not ok then
|
||||
return self:ret(false, "error while adding item to cache : " .. err)
|
||||
end
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is whitelisted (country = " .. country .. ")")
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is whitelisted (country = " .. country_data .. ")"
|
||||
)
|
||||
end
|
||||
end
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ko")
|
||||
if not ok then
|
||||
return self:ret(false, "error while adding item to cache : " .. err)
|
||||
end
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country .. ")",
|
||||
utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country_data .. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
|
||||
-- And then blacklist
|
||||
if self.variables["BLACKLIST_COUNTRY"] ~= "" then
|
||||
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch("%S+") do
|
||||
if bl_country == country then
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
|
||||
for bl_country in self.variables["BLACKLIST_COUNTRY"]:gmatch "%S+" do
|
||||
if bl_country == country_data then
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ko")
|
||||
if not ok then
|
||||
return self:ret(false, "error while adding item to cache : " .. err)
|
||||
end
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country .. ")",
|
||||
utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country_data .. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Country IP is not in blacklist
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country_data, "ok")
|
||||
if not ok then
|
||||
return self:ret(false, "error while caching IP " .. self.ctx.bw.remote_addr .. " : " .. err)
|
||||
end
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not blacklisted (country = " .. country .. ")")
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is not blacklisted (country = " .. country_data .. ")"
|
||||
)
|
||||
end
|
||||
|
||||
function country:preread()
|
||||
|
@ -97,9 +122,12 @@ function country:is_in_cache(ip)
|
|||
return true, data
|
||||
end
|
||||
|
||||
function country:add_to_cache(ip, country, result)
|
||||
local ok, err = self.cachestore:set("plugin_country_" .. self.ctx.bw.server_name .. ip,
|
||||
cjson.encode({ country = country, result = result }), 86400)
|
||||
function country:add_to_cache(ip, country_data, result)
|
||||
local ok, err = self.cachestore:set(
|
||||
"plugin_country_" .. self.ctx.bw.server_name .. ip,
|
||||
cjson.encode { country = country_data, result = result },
|
||||
86400
|
||||
)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local resolver = require "resty.dns.resolver"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local dnsbl = class("dnsbl", plugin)
|
||||
local dnsbl = class("dnsbl", plugin)
|
||||
|
||||
local is_in_dnsbl = function(addr, server)
|
||||
local request = resolver.arpa_str(addr):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "") .. "." .. server
|
||||
local ips, err = utils.get_ips(request, false)
|
||||
if not ips then
|
||||
return nil, server, err
|
||||
end
|
||||
for _, ip in ipairs(ips) do
|
||||
if ip:find "^127%.0%.0%." then
|
||||
return true, server
|
||||
end
|
||||
end
|
||||
return false, server
|
||||
end
|
||||
|
||||
function dnsbl:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
@ -26,14 +40,15 @@ function dnsbl:init_worker()
|
|||
local threads = {}
|
||||
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
|
||||
-- Create thread
|
||||
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, "127.0.0.2", server)
|
||||
local thread = ngx.thread.spawn(is_in_dnsbl, "127.0.0.2", server)
|
||||
threads[server] = thread
|
||||
end
|
||||
-- Wait for threads
|
||||
for dnsbl, thread in pairs(threads) do
|
||||
for data, thread in pairs(threads) do
|
||||
-- luacheck: ignore 421
|
||||
local ok, result, server, err = ngx.thread.wait(thread)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error while waiting thread of " .. dnsbl .. " check : " .. result)
|
||||
self.logger:log(ngx.ERR, "error while waiting thread of " .. data .. " check : " .. result)
|
||||
elseif result == nil then
|
||||
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
|
||||
elseif not result then
|
||||
|
@ -65,14 +80,17 @@ function dnsbl:access()
|
|||
if cached == "ok" then
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
|
||||
end
|
||||
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
|
||||
utils.get_deny_status(self.ctx))
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
|
||||
utils.get_deny_status(self.ctx)
|
||||
)
|
||||
end
|
||||
-- Loop on DNSBL list
|
||||
local threads = {}
|
||||
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
|
||||
-- Create thread
|
||||
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, self.ctx.bw.remote_addr, server)
|
||||
local thread = ngx.thread.spawn(is_in_dnsbl, self.ctx.bw.remote_addr, server)
|
||||
threads[server] = thread
|
||||
end
|
||||
-- Wait for threads
|
||||
|
@ -82,7 +100,7 @@ function dnsbl:access()
|
|||
while true do
|
||||
-- Compute threads to wait
|
||||
local wait_threads = {}
|
||||
for dnsbl, thread in pairs(threads) do
|
||||
for _, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
-- No server reported IP
|
||||
|
@ -90,6 +108,7 @@ function dnsbl:access()
|
|||
break
|
||||
end
|
||||
-- Wait for first thread
|
||||
-- luacheck: ignore 421
|
||||
local ok, result, server, err = ngx.thread.wait(unpack(wait_threads))
|
||||
-- Error case
|
||||
if not ok then
|
||||
|
@ -115,7 +134,7 @@ function dnsbl:access()
|
|||
-- Kill other threads
|
||||
if #threads > 0 then
|
||||
local wait_threads = {}
|
||||
for dnsbl, thread in pairs(threads) do
|
||||
for _, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
utils.kill_all_threads(wait_threads)
|
||||
|
@ -159,18 +178,4 @@ function dnsbl:add_to_cache(ip, value)
|
|||
return true
|
||||
end
|
||||
|
||||
function dnsbl:is_in_dnsbl(ip, server)
|
||||
local request = resolver.arpa_str(ip):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "") .. "." .. server
|
||||
local ips, err = utils.get_ips(request, false)
|
||||
if not ips then
|
||||
return nil, server, err
|
||||
end
|
||||
for i, ip in ipairs(ips) do
|
||||
if ip:find("^127%.0%.0%.") then
|
||||
return true, server
|
||||
end
|
||||
end
|
||||
return false, server
|
||||
end
|
||||
|
||||
return dnsbl
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local template = nil
|
||||
if ngx.shared.datastore then
|
||||
template = require "resty.template"
|
||||
|
@ -14,52 +14,52 @@ function errors:initialize(ctx)
|
|||
self.default_errors = {
|
||||
["400"] = {
|
||||
title = "Bad Request",
|
||||
text = "The server did not understand the request."
|
||||
text = "The server did not understand the request.",
|
||||
},
|
||||
["401"] = {
|
||||
title = "Not Authorized",
|
||||
text = "Valid authentication credentials needed for the target resource."
|
||||
text = "Valid authentication credentials needed for the target resource.",
|
||||
},
|
||||
["403"] = {
|
||||
title = "Forbidden",
|
||||
text = "Access is forbidden to the requested page."
|
||||
text = "Access is forbidden to the requested page.",
|
||||
},
|
||||
["404"] = {
|
||||
title = "Not Found",
|
||||
text = "The server cannot find the requested page."
|
||||
text = "The server cannot find the requested page.",
|
||||
},
|
||||
["405"] = {
|
||||
title = "Method Not Allowed",
|
||||
text = "The method specified in the request is not allowed."
|
||||
text = "The method specified in the request is not allowed.",
|
||||
},
|
||||
["413"] = {
|
||||
title = "Request Entity Too Large",
|
||||
text = "The server will not accept the request, because the request entity is too large."
|
||||
text = "The server will not accept the request, because the request entity is too large.",
|
||||
},
|
||||
["429"] = {
|
||||
title = "Too Many Requests",
|
||||
text = "Too many requests sent in a given amount of time, try again later."
|
||||
text = "Too many requests sent in a given amount of time, try again later.",
|
||||
},
|
||||
["500"] = {
|
||||
title = "Internal Server Error",
|
||||
text = "The request was not completed. The server met an unexpected condition."
|
||||
text = "The request was not completed. The server met an unexpected condition.",
|
||||
},
|
||||
["501"] = {
|
||||
title = "Not Implemented",
|
||||
text = "The request was not completed. The server did not support the functionality required."
|
||||
text = "The request was not completed. The server did not support the functionality required.",
|
||||
},
|
||||
["502"] = {
|
||||
title = "Bad Gateway",
|
||||
text = "The request was not completed. The server received an invalid response from the upstream server."
|
||||
text = "The request was not completed. The server received an invalid response from the upstream server.",
|
||||
},
|
||||
["503"] = {
|
||||
title = "Service Unavailable",
|
||||
text = "The request was not completed. The server is temporarily overloading or down."
|
||||
text = "The request was not completed. The server is temporarily overloading or down.",
|
||||
},
|
||||
["504"] = {
|
||||
title = "Gateway Timeout",
|
||||
text = "The gateway has timed out."
|
||||
}
|
||||
text = "The gateway has timed out.",
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -69,7 +69,7 @@ function errors:render_template(code)
|
|||
title = code .. " - " .. self.default_errors[code].title,
|
||||
error_title = self.default_errors[code].title,
|
||||
error_code = code,
|
||||
error_text = self.default_errors[code].text
|
||||
error_text = self.default_errors[code].text,
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local greylist = class("greylist", plugin)
|
||||
local greylist = class("greylist", plugin)
|
||||
|
||||
function greylist:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
@ -22,7 +22,7 @@ function greylist:initialize(ctx)
|
|||
["RDNS"] = {},
|
||||
["ASN"] = {},
|
||||
["USER_AGENT"] = {},
|
||||
["URI"] = {}
|
||||
["URI"] = {},
|
||||
}
|
||||
for kind, _ in pairs(kinds) do
|
||||
for data in self.variables["GREYLIST_" .. kind]:gmatch("%S+") do
|
||||
|
@ -67,7 +67,7 @@ function greylist:init()
|
|||
}
|
||||
local i = 0
|
||||
for kind, _ in pairs(greylists) do
|
||||
local f, err = io.open("/var/cache/bunkerweb/greylist/" .. kind .. ".list", "r")
|
||||
local f, _ = io.open("/var/cache/bunkerweb/greylist/" .. kind .. ".list", "r")
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
table.insert(greylists[kind], line)
|
||||
|
@ -91,7 +91,7 @@ function greylist:access()
|
|||
end
|
||||
-- Check the caches
|
||||
local checks = {
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr,
|
||||
}
|
||||
if self.ctx.bw.http_user_agent then
|
||||
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
|
||||
|
@ -102,7 +102,7 @@ function greylist:access()
|
|||
local already_cached = {
|
||||
["IP"] = false,
|
||||
["URI"] = false,
|
||||
["UA"] = false
|
||||
["UA"] = false,
|
||||
}
|
||||
for k, v in pairs(checks) do
|
||||
local ok, cached = self:is_in_cache(v)
|
||||
|
@ -120,12 +120,13 @@ function greylist:access()
|
|||
return self:ret(false, "lists is nil")
|
||||
end
|
||||
-- Perform checks
|
||||
for k, v in pairs(checks) do
|
||||
for k, _ in pairs(checks) do
|
||||
if not already_cached[k] then
|
||||
local ok, greylisted = self:is_greylisted(k)
|
||||
if ok == nil then
|
||||
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is greylisted : " .. greylisted)
|
||||
else
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self:add_to_cache(self:kind_to_ele(k), greylisted)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
|
||||
|
@ -187,12 +188,13 @@ function greylist:is_greylisted_ip()
|
|||
end
|
||||
if check_rdns then
|
||||
-- Get rDNS
|
||||
-- luacheck: ignore 421
|
||||
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
|
||||
-- Check if rDNS is in greylist
|
||||
if rdns_list then
|
||||
for i, rdns in ipairs(rdns_list) do
|
||||
for j, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(- #suffix) == suffix then
|
||||
for _, rdns in ipairs(rdns_list) do
|
||||
for _, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(-#suffix) == suffix then
|
||||
return true, "rDNS " .. suffix
|
||||
end
|
||||
end
|
||||
|
@ -208,7 +210,7 @@ function greylist:is_greylisted_ip()
|
|||
if not asn then
|
||||
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
|
||||
else
|
||||
for i, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
for _, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
if bl_asn == tostring(asn) then
|
||||
return true, "ASN " .. bl_asn
|
||||
end
|
||||
|
@ -222,7 +224,7 @@ end
|
|||
|
||||
function greylist:is_greylisted_uri()
|
||||
-- Check if URI is in greylist
|
||||
for i, uri in ipairs(self.lists["URI"]) do
|
||||
for _, uri in ipairs(self.lists["URI"]) do
|
||||
if utils.regex_match(self.ctx.bw.uri, uri) then
|
||||
return true, "URI " .. uri
|
||||
end
|
||||
|
@ -233,7 +235,7 @@ end
|
|||
|
||||
function greylist:is_greylisted_ua()
|
||||
-- Check if UA is in greylist
|
||||
for i, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
for _, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
|
||||
return true, "UA " .. ua
|
||||
end
|
||||
|
|
|
@ -1,99 +1,110 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local headers = class("headers", plugin)
|
||||
|
||||
function headers:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "headers", ctx)
|
||||
self.all_headers = {
|
||||
["STRICT_TRANSPORT_SECURITY"] = "Strict-Transport-Security",
|
||||
["CONTENT_SECURITY_POLICY"] = "Content-Security-Policy",
|
||||
["REFERRER_POLICY"] = "Referrer-Policy",
|
||||
["PERMISSIONS_POLICY"] = "Permissions-Policy",
|
||||
["FEATURE_POLICY"] = "Feature-Policy",
|
||||
["X_FRAME_OPTIONS"] = "X-Frame-Options",
|
||||
["X_CONTENT_TYPE_OPTIONS"] = "X-Content-Type-Options",
|
||||
["X_XSS_PROTECTION"] = "X-XSS-Protection"
|
||||
}
|
||||
-- Load data from datastore if needed
|
||||
if ngx.get_phase() ~= "init" then
|
||||
-- Get custom headers from datastore
|
||||
local custom_headers, err = self.datastore:get("plugin_headers_custom_headers", true)
|
||||
if not custom_headers then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
self.custom_headers = {}
|
||||
-- Extract global headers
|
||||
if custom_headers.global then
|
||||
for k, v in pairs(custom_headers.global) do
|
||||
self.custom_headers[k] = v
|
||||
end
|
||||
end
|
||||
-- Extract and overwrite if needed server headers
|
||||
if custom_headers[self.ctx.bw.server_name] then
|
||||
for k, v in pairs(custom_headers[self.ctx.bw.server_name]) do
|
||||
self.custom_headers[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "headers", ctx)
|
||||
self.all_headers = {
|
||||
["STRICT_TRANSPORT_SECURITY"] = "Strict-Transport-Security",
|
||||
["CONTENT_SECURITY_POLICY"] = "Content-Security-Policy",
|
||||
["REFERRER_POLICY"] = "Referrer-Policy",
|
||||
["PERMISSIONS_POLICY"] = "Permissions-Policy",
|
||||
["FEATURE_POLICY"] = "Feature-Policy",
|
||||
["X_FRAME_OPTIONS"] = "X-Frame-Options",
|
||||
["X_CONTENT_TYPE_OPTIONS"] = "X-Content-Type-Options",
|
||||
["X_XSS_PROTECTION"] = "X-XSS-Protection",
|
||||
}
|
||||
-- Load data from datastore if needed
|
||||
if ngx.get_phase() ~= "init" then
|
||||
-- Get custom headers from datastore
|
||||
local custom_headers, err = self.datastore:get("plugin_headers_custom_headers", true)
|
||||
if not custom_headers then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
return
|
||||
end
|
||||
self.custom_headers = {}
|
||||
-- Extract global headers
|
||||
if custom_headers.global then
|
||||
for k, v in pairs(custom_headers.global) do
|
||||
self.custom_headers[k] = v
|
||||
end
|
||||
end
|
||||
-- Extract and overwrite if needed server headers
|
||||
if custom_headers[self.ctx.bw.server_name] then
|
||||
for k, v in pairs(custom_headers[self.ctx.bw.server_name]) do
|
||||
self.custom_headers[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function headers:init()
|
||||
-- Get variables
|
||||
local variables, err = utils.get_multiple_variables({ "CUSTOM_HEADER" })
|
||||
if variables == nil then
|
||||
return self:ret(false, err)
|
||||
end
|
||||
-- Store custom headers name and value
|
||||
local data = {}
|
||||
local i = 0
|
||||
for srv, vars in pairs(variables) do
|
||||
for var, value in pairs(vars) do
|
||||
if data[srv] == nil then
|
||||
data[srv] = {}
|
||||
end
|
||||
local m = utils.regex_match(value, "([\\w-]+): ([^,]+)")
|
||||
if m then
|
||||
data[srv][m[1]] = m[2]
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
local ok, err = self.datastore:set("plugin_headers_custom_headers", data, nil, true)
|
||||
if not ok then
|
||||
return self:ret(false, err)
|
||||
end
|
||||
return self:ret(true, "successfully loaded " .. tostring(i) .. " custom headers")
|
||||
-- Get variables
|
||||
local variables, err = utils.get_multiple_variables({ "CUSTOM_HEADER" })
|
||||
if variables == nil then
|
||||
return self:ret(false, err)
|
||||
end
|
||||
-- Store custom headers name and value
|
||||
local data = {}
|
||||
local i = 0
|
||||
for srv, vars in pairs(variables) do
|
||||
for _, value in pairs(vars) do
|
||||
if data[srv] == nil then
|
||||
data[srv] = {}
|
||||
end
|
||||
local m = utils.regex_match(value, "([\\w-]+): ([^,]+)")
|
||||
if m then
|
||||
data[srv][m[1]] = m[2]
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
local ok
|
||||
ok, err = self.datastore:set("plugin_headers_custom_headers", data, nil, true)
|
||||
if not ok then
|
||||
return self:ret(false, err)
|
||||
end
|
||||
return self:ret(true, "successfully loaded " .. tostring(i) .. " custom headers")
|
||||
end
|
||||
|
||||
function headers:header()
|
||||
-- Override upstream headers if needed
|
||||
local ssl = self.ctx.bw.scheme == "https"
|
||||
for variable, header in pairs(self.all_headers) do
|
||||
if ngx.header[header] == nil or (self.variables[variable] ~= "" and self.variables["KEEP_UPSTREAM_HEADERS"] ~= "*" and utils.regex_match(self.variables["KEEP_UPSTREAM_HEADERS"], "(^| )" .. header .. "($| )") == nil) then
|
||||
if (header ~= "Strict-Transport-Security" or ssl) then
|
||||
if header == "Content-Security-Policy" and self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
|
||||
ngx.header["Content-Security-Policy-Report-Only"] = self.variables[variable]
|
||||
else
|
||||
ngx.header[header] = self.variables[variable]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Add custom headers
|
||||
for header, value in pairs(self.custom_headers) do
|
||||
ngx.header[header] = value
|
||||
end
|
||||
-- Remove headers
|
||||
if self.variables["REMOVE_HEADERS"] ~= "" then
|
||||
for header in self.variables["REMOVE_HEADERS"]:gmatch("%S+") do
|
||||
ngx.header[header] = nil
|
||||
end
|
||||
end
|
||||
return self:ret(true, "edited headers for request")
|
||||
-- Override upstream headers if needed
|
||||
local ssl = self.ctx.bw.scheme == "https"
|
||||
for variable, header in pairs(self.all_headers) do
|
||||
if
|
||||
ngx.header[header] == nil
|
||||
or (
|
||||
self.variables[variable] ~= ""
|
||||
and self.variables["KEEP_UPSTREAM_HEADERS"] ~= "*"
|
||||
and utils.regex_match(self.variables["KEEP_UPSTREAM_HEADERS"], "(^| )" .. header .. "($| )") == nil
|
||||
)
|
||||
then
|
||||
if header ~= "Strict-Transport-Security" or ssl then
|
||||
if
|
||||
header == "Content-Security-Policy"
|
||||
and self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes"
|
||||
then
|
||||
ngx.header["Content-Security-Policy-Report-Only"] = self.variables[variable]
|
||||
else
|
||||
ngx.header[header] = self.variables[variable]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Add custom headers
|
||||
for header, value in pairs(self.custom_headers) do
|
||||
ngx.header[header] = value
|
||||
end
|
||||
-- Remove headers
|
||||
if self.variables["REMOVE_HEADERS"] ~= "" then
|
||||
for header in self.variables["REMOVE_HEADERS"]:gmatch("%S+") do
|
||||
ngx.header[header] = nil
|
||||
end
|
||||
end
|
||||
return self:ret(true, "edited headers for request")
|
||||
end
|
||||
|
||||
return headers
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local cjson = require "cjson"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
|
||||
local letsencrypt = class("letsencrypt", plugin)
|
||||
|
||||
|
@ -17,9 +17,12 @@ function letsencrypt:access()
|
|||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
-- luacheck: ignore 212
|
||||
function letsencrypt:api(ctx)
|
||||
if not string.match(ctx.bw.uri, "^/lets%-encrypt/challenge$") or
|
||||
(ctx.bw.request_method ~= "POST" and ctx.bw.request_method ~= "DELETE") then
|
||||
if
|
||||
not string.match(ctx.bw.uri, "^/lets%-encrypt/challenge$")
|
||||
or (ctx.bw.request_method ~= "POST" and ctx.bw.request_method ~= "DELETE")
|
||||
then
|
||||
return false, nil, nil
|
||||
end
|
||||
local acme_folder = "/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/"
|
||||
|
@ -32,7 +35,9 @@ function letsencrypt:api(ctx)
|
|||
if ctx.bw.request_method == "POST" then
|
||||
local file, err = io.open(acme_folder .. data.token, "w+")
|
||||
if not file then
|
||||
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, { status = "error", msg = "can't write validation token : " .. err }
|
||||
return true,
|
||||
ngx.HTTP_INTERNAL_SERVER_ERROR,
|
||||
{ status = "error", msg = "can't write validation token : " .. err }
|
||||
end
|
||||
file:write(data.validation)
|
||||
file:close()
|
||||
|
@ -40,7 +45,9 @@ function letsencrypt:api(ctx)
|
|||
elseif ctx.bw.request_method == "DELETE" then
|
||||
local ok, err = os.remove(acme_folder .. data.token)
|
||||
if not ok then
|
||||
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, { status = "error", msg = "can't remove validation token : " .. err }
|
||||
return true,
|
||||
ngx.HTTP_INTERNAL_SERVER_ERROR,
|
||||
{ status = "error", msg = "can't remove validation token : " .. err }
|
||||
end
|
||||
return true, ngx.HTTP_OK, { status = "success", msg = "validation token removed" }
|
||||
end
|
||||
|
|
|
@ -1,9 +1,40 @@
|
|||
local class = require "middleclass"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cjson = require "cjson"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local limit = class("limit", plugin)
|
||||
local limit = class("limit", plugin)
|
||||
|
||||
local limit_req_timestamps = function(rate_max, rate_time, timestamps)
|
||||
-- Compute new timestamps
|
||||
local updated = false
|
||||
local new_timestamps = {}
|
||||
local current_timestamp = os.time(os.date "!*t")
|
||||
local delay = 0
|
||||
if rate_time == "s" then
|
||||
delay = 1
|
||||
elseif rate_time == "m" then
|
||||
delay = 60
|
||||
elseif rate_time == "h" then
|
||||
delay = 3600
|
||||
elseif rate_time == "d" then
|
||||
delay = 86400
|
||||
end
|
||||
-- Keep only timestamp within the delay
|
||||
for _, timestamp in ipairs(timestamps) do
|
||||
if current_timestamp - timestamp <= delay then
|
||||
table.insert(new_timestamps, timestamp)
|
||||
else
|
||||
updated = true
|
||||
end
|
||||
end
|
||||
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
|
||||
if #new_timestamps <= rate_max then
|
||||
table.insert(new_timestamps, current_timestamp)
|
||||
updated = true
|
||||
end
|
||||
return updated, new_timestamps, delay
|
||||
end
|
||||
|
||||
function limit:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
|
@ -11,7 +42,6 @@ function limit:initialize(ctx)
|
|||
-- Load rules if needed
|
||||
if ngx.get_phase() ~= "init" and self:is_needed() then
|
||||
-- Get all rules from datastore
|
||||
local limited = false
|
||||
local all_rules, err = self.datastore:get("plugin_limit_rules", true)
|
||||
if not all_rules then
|
||||
self.logger:log(ngx.ERR, err)
|
||||
|
@ -93,19 +123,16 @@ function limit:access()
|
|||
return self:ret(true, "limit request not enabled")
|
||||
end
|
||||
-- Check if URI is limited
|
||||
local rate = nil
|
||||
local uri = nil
|
||||
local rate
|
||||
for k, v in pairs(self.rules) do
|
||||
if k ~= "/" and utils.regex_match(self.ctx.bw.uri, k) then
|
||||
rate = v
|
||||
uri = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if not rate then
|
||||
if self.rules["/"] then
|
||||
rate = self.rules["/"]
|
||||
uri = "/"
|
||||
else
|
||||
return self:ret(true, "no rule for " .. self.ctx.bw.uri)
|
||||
end
|
||||
|
@ -118,19 +145,37 @@ function limit:access()
|
|||
end
|
||||
-- Limit reached
|
||||
if limited then
|
||||
return self:ret(true,
|
||||
"client IP " ..
|
||||
self.ctx.bw.remote_addr ..
|
||||
" is limited for URL " ..
|
||||
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")",
|
||||
ngx.HTTP_TOO_MANY_REQUESTS)
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
.. self.ctx.bw.remote_addr
|
||||
.. " is limited for URL "
|
||||
.. self.ctx.bw.uri
|
||||
.. " (current rate = "
|
||||
.. current_rate
|
||||
.. "r/"
|
||||
.. rate_time
|
||||
.. " and max rate = "
|
||||
.. rate
|
||||
.. ")",
|
||||
ngx.HTTP_TOO_MANY_REQUESTS
|
||||
)
|
||||
end
|
||||
-- Limit not reached
|
||||
return self:ret(true,
|
||||
"client IP " ..
|
||||
self.ctx.bw.remote_addr ..
|
||||
" is not limited for URL " ..
|
||||
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")")
|
||||
return self:ret(
|
||||
true,
|
||||
"client IP "
|
||||
.. self.ctx.bw.remote_addr
|
||||
.. " is not limited for URL "
|
||||
.. self.ctx.bw.uri
|
||||
.. " (current rate = "
|
||||
.. current_rate
|
||||
.. "r/"
|
||||
.. rate_time
|
||||
.. " and max rate = "
|
||||
.. rate
|
||||
.. ")"
|
||||
)
|
||||
end
|
||||
|
||||
function limit:limit_req(rate_max, rate_time)
|
||||
|
@ -143,9 +188,12 @@ function limit:limit_req(rate_max, rate_time)
|
|||
else
|
||||
timestamps = redis_timestamps
|
||||
-- Save the new timestamps
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self.datastore:set(
|
||||
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
|
||||
cjson.encode(timestamps), delay)
|
||||
cjson.encode(timestamps),
|
||||
delay
|
||||
)
|
||||
if not ok then
|
||||
return nil, "can't update timestamps : " .. err
|
||||
end
|
||||
|
@ -167,8 +215,8 @@ end
|
|||
|
||||
function limit:limit_req_local(rate_max, rate_time)
|
||||
-- Get timestamps
|
||||
local timestamps, err = self.datastore:get("plugin_limit_" ..
|
||||
self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri)
|
||||
local timestamps, err =
|
||||
self.datastore:get("plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri)
|
||||
if not timestamps and err ~= "not found" then
|
||||
return nil, err
|
||||
elseif err == "not found" then
|
||||
|
@ -176,12 +224,15 @@ function limit:limit_req_local(rate_max, rate_time)
|
|||
end
|
||||
timestamps = cjson.decode(timestamps)
|
||||
-- Compute new timestamps
|
||||
local updated, new_timestamps, delay = self:limit_req_timestamps(rate_max, rate_time, timestamps)
|
||||
local updated, new_timestamps, delay = limit_req_timestamps(rate_max, rate_time, timestamps)
|
||||
-- Save new timestamps if needed
|
||||
if updated then
|
||||
-- luacheck: ignore 421
|
||||
local ok, err = self.datastore:set(
|
||||
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
|
||||
cjson.encode(new_timestamps), delay)
|
||||
cjson.encode(new_timestamps),
|
||||
delay
|
||||
)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
@ -245,9 +296,15 @@ function limit:limit_req_redis(rate_max, rate_time)
|
|||
return nil, err
|
||||
end
|
||||
-- Execute script
|
||||
local timestamps, err = self.clusterstore:call("eval", redis_script, 1,
|
||||
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri, rate_max, rate_time,
|
||||
os.time(os.date("!*t")))
|
||||
local timestamps, err = self.clusterstore:call(
|
||||
"eval",
|
||||
redis_script,
|
||||
1,
|
||||
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
|
||||
rate_max,
|
||||
rate_time,
|
||||
os.time(os.date("!*t"))
|
||||
)
|
||||
if not timestamps then
|
||||
self.clusterstore:close()
|
||||
return nil, err
|
||||
|
@ -257,35 +314,4 @@ function limit:limit_req_redis(rate_max, rate_time)
|
|||
return timestamps, "success"
|
||||
end
|
||||
|
||||
function limit:limit_req_timestamps(rate_max, rate_time, timestamps)
|
||||
-- Compute new timestamps
|
||||
local updated = false
|
||||
local new_timestamps = {}
|
||||
local current_timestamp = os.time(os.date("!*t"))
|
||||
local delay = 0
|
||||
if rate_time == "s" then
|
||||
delay = 1
|
||||
elseif rate_time == "m" then
|
||||
delay = 60
|
||||
elseif rate_time == "h" then
|
||||
delay = 3600
|
||||
elseif rate_time == "d" then
|
||||
delay = 86400
|
||||
end
|
||||
-- Keep only timestamp within the delay
|
||||
for i, timestamp in ipairs(timestamps) do
|
||||
if current_timestamp - timestamp <= delay then
|
||||
table.insert(new_timestamps, timestamp)
|
||||
else
|
||||
updated = true
|
||||
end
|
||||
end
|
||||
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
|
||||
if #new_timestamps <= rate_max then
|
||||
table.insert(new_timestamps, current_timestamp)
|
||||
updated = true
|
||||
end
|
||||
return updated, new_timestamps, delay
|
||||
end
|
||||
|
||||
return limit
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
local class = require "middleclass"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local misc = class("misc", plugin)
|
||||
local misc = class("misc", plugin)
|
||||
|
||||
function misc:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "misc", ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "misc", ctx)
|
||||
end
|
||||
|
||||
function misc:access()
|
||||
-- Check if method is valid
|
||||
local method = self.ctx.bw.request_method
|
||||
if not method or not utils.regex_match(method, "^[A-Z]+$") then
|
||||
return self:ret(true, "method is not valid", ngx.HTTP_BAD_REQUEST)
|
||||
end
|
||||
-- Check if method is allowed
|
||||
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
|
||||
if method == allowed_method then
|
||||
return self:ret(true, "method " .. method .. " is allowed")
|
||||
end
|
||||
end
|
||||
return self:ret(true, "method " .. method .. " is not allowed", ngx.HTTP_NOT_ALLOWED)
|
||||
-- Check if method is valid
|
||||
local method = self.ctx.bw.request_method
|
||||
if not method or not utils.regex_match(method, "^[A-Z]+$") then
|
||||
return self:ret(true, "method is not valid", ngx.HTTP_BAD_REQUEST)
|
||||
end
|
||||
-- Check if method is allowed
|
||||
for allowed_method in self.variables["ALLOWED_METHODS"]:gmatch("[^|]+") do
|
||||
if method == allowed_method then
|
||||
return self:ret(true, "method " .. method .. " is allowed")
|
||||
end
|
||||
end
|
||||
return self:ret(true, "method " .. method .. " is not allowed", ngx.HTTP_NOT_ALLOWED)
|
||||
end
|
||||
|
||||
return misc
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
local class = require "middleclass"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
|
||||
local redis = class("redis", plugin)
|
||||
local redis = class("redis", plugin)
|
||||
|
||||
function redis:initialize()
|
||||
function redis:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "redis", ctx)
|
||||
end
|
||||
|
|
|
@ -1,153 +1,155 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local cachestore = require "bunkerweb.cachestore"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local reversescan = class("reversescan", plugin)
|
||||
|
||||
function reversescan:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "reversescan", ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "reversescan", ctx)
|
||||
end
|
||||
|
||||
function reversescan:access()
|
||||
-- Check if access is needed
|
||||
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
|
||||
return self:ret(true, "reverse scan not activated")
|
||||
end
|
||||
-- Loop on ports
|
||||
local threads = {}
|
||||
local ret_threads = nil
|
||||
local ret_err = nil
|
||||
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
|
||||
-- Check if the scan is already cached
|
||||
local ok, cached = self:is_in_cache(self.ctx.bw.remote_addr .. ":" .. port)
|
||||
if not ok then
|
||||
ret_threads = false
|
||||
ret_err = "error getting info from cachestore : " .. cached
|
||||
break
|
||||
-- Deny access if port opened
|
||||
elseif cached == "open" then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
break
|
||||
-- Perform scan in a thread
|
||||
elseif not cached then
|
||||
local thread = ngx.thread.spawn(self.scan, self.ctx.bw.remote_addr, tonumber(port),
|
||||
tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
|
||||
threads[port] = thread
|
||||
end
|
||||
end
|
||||
if ret_threads ~= nil then
|
||||
if #threads > 0 then
|
||||
local wait_threads = {}
|
||||
for port, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
utils.kill_all_threads(wait_threads)
|
||||
end
|
||||
-- Open port case
|
||||
if ret_threads then
|
||||
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
|
||||
end
|
||||
-- Error case
|
||||
return self:ret(false, ret_err)
|
||||
end
|
||||
-- Check results of threads
|
||||
ret_threads = nil
|
||||
ret_err = nil
|
||||
local results = {}
|
||||
while true do
|
||||
-- Compute threads to wait
|
||||
local wait_threads = {}
|
||||
for port, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
-- No port opened
|
||||
if #wait_threads == 0 then
|
||||
break
|
||||
end
|
||||
-- Wait for first thread
|
||||
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
|
||||
-- Error case
|
||||
if not ok then
|
||||
ret_threads = false
|
||||
ret_err = "error while waiting thread : " .. open
|
||||
break
|
||||
end
|
||||
port = tostring(port)
|
||||
-- Remove thread from list
|
||||
threads[port] = nil
|
||||
-- Add result to cache
|
||||
local result = "close"
|
||||
if open then
|
||||
result = "open"
|
||||
end
|
||||
results[port] = result
|
||||
-- Port is opened
|
||||
if open then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Kill running threads
|
||||
if #threads > 0 then
|
||||
local wait_threads = {}
|
||||
for port, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
utils.kill_all_threads(wait_threads)
|
||||
end
|
||||
-- Cache results
|
||||
for port, result in pairs(results) do
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr .. ":" .. port, result)
|
||||
if not ok then
|
||||
return self:ret(false, "error while adding element to cache : " .. err)
|
||||
end
|
||||
end
|
||||
if ret_threads ~= nil then
|
||||
-- Open port case
|
||||
if ret_threads then
|
||||
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
|
||||
end
|
||||
-- Error case
|
||||
return self:ret(false, ret_err)
|
||||
end
|
||||
-- No port opened
|
||||
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr)
|
||||
-- Check if access is needed
|
||||
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
|
||||
return self:ret(true, "reverse scan not activated")
|
||||
end
|
||||
-- Loop on ports
|
||||
local threads = {}
|
||||
local ret_threads = nil
|
||||
local ret_err = nil
|
||||
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
|
||||
-- Check if the scan is already cached
|
||||
local ok, cached = self:is_in_cache(self.ctx.bw.remote_addr .. ":" .. port)
|
||||
if not ok then
|
||||
ret_threads = false
|
||||
ret_err = "error getting info from cachestore : " .. cached
|
||||
break
|
||||
-- Deny access if port opened
|
||||
elseif cached == "open" then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
break
|
||||
-- Perform scan in a thread
|
||||
elseif not cached then
|
||||
local thread = ngx.thread.spawn(
|
||||
self.scan,
|
||||
self.ctx.bw.remote_addr,
|
||||
tonumber(port),
|
||||
tonumber(self.variables["REVERSE_SCAN_TIMEOUT"])
|
||||
)
|
||||
threads[port] = thread
|
||||
end
|
||||
end
|
||||
if ret_threads ~= nil then
|
||||
if #threads > 0 then
|
||||
local wait_threads = {}
|
||||
for _, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
utils.kill_all_threads(wait_threads)
|
||||
end
|
||||
-- Open port case
|
||||
if ret_threads then
|
||||
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
|
||||
end
|
||||
-- Error case
|
||||
return self:ret(false, ret_err)
|
||||
end
|
||||
-- Check results of threads
|
||||
ret_threads = nil
|
||||
ret_err = nil
|
||||
local results = {}
|
||||
while true do
|
||||
-- Compute threads to wait
|
||||
local wait_threads = {}
|
||||
for _, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
-- No port opened
|
||||
if #wait_threads == 0 then
|
||||
break
|
||||
end
|
||||
-- Wait for first thread
|
||||
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
|
||||
-- Error case
|
||||
if not ok then
|
||||
ret_threads = false
|
||||
ret_err = "error while waiting thread : " .. open
|
||||
break
|
||||
end
|
||||
port = tostring(port)
|
||||
-- Remove thread from list
|
||||
threads[port] = nil
|
||||
-- Add result to cache
|
||||
local result = "close"
|
||||
if open then
|
||||
result = "open"
|
||||
end
|
||||
results[port] = result
|
||||
-- Port is opened
|
||||
if open then
|
||||
ret_threads = true
|
||||
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Kill running threads
|
||||
if #threads > 0 then
|
||||
local wait_threads = {}
|
||||
for _, thread in pairs(threads) do
|
||||
table.insert(wait_threads, thread)
|
||||
end
|
||||
utils.kill_all_threads(wait_threads)
|
||||
end
|
||||
-- Cache results
|
||||
for port, result in pairs(results) do
|
||||
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr .. ":" .. port, result)
|
||||
if not ok then
|
||||
return self:ret(false, "error while adding element to cache : " .. err)
|
||||
end
|
||||
end
|
||||
if ret_threads ~= nil then
|
||||
-- Open port case
|
||||
if ret_threads then
|
||||
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
|
||||
end
|
||||
-- Error case
|
||||
return self:ret(false, ret_err)
|
||||
end
|
||||
-- No port opened
|
||||
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr)
|
||||
end
|
||||
|
||||
function reversescan:preread()
|
||||
return self:access()
|
||||
return self:access()
|
||||
end
|
||||
|
||||
function reversescan.scan(ip, port, timeout)
|
||||
local tcpsock = ngx.socket.tcp()
|
||||
tcpsock:settimeout(timeout)
|
||||
local ok, err = tcpsock:connect(ip, port)
|
||||
tcpsock:close()
|
||||
if not ok then
|
||||
return false, port
|
||||
end
|
||||
return true, port
|
||||
local tcpsock = ngx.socket.tcp()
|
||||
tcpsock:settimeout(timeout)
|
||||
local ok, _ = tcpsock:connect(ip, port)
|
||||
tcpsock:close()
|
||||
if not ok then
|
||||
return false, port
|
||||
end
|
||||
return true, port
|
||||
end
|
||||
|
||||
function reversescan:is_in_cache(ip_port)
|
||||
local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port)
|
||||
if not ok then
|
||||
return false, data
|
||||
end
|
||||
return true, data
|
||||
local ok, data = self.cachestore:get("plugin_reverse_scan_" .. ip_port)
|
||||
if not ok then
|
||||
return false, data
|
||||
end
|
||||
return true, data
|
||||
end
|
||||
|
||||
function reversescan:add_to_cache(ip_port, value)
|
||||
local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
return true
|
||||
local ok, err = self.cachestore:set("plugin_reverse_scan_" .. ip_port, value, 86400)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return reversescan
|
||||
|
|
|
@ -1,118 +1,118 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local session = require "resty.session"
|
||||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local session = require "resty.session"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local sessions = class("sessions", plugin)
|
||||
|
||||
function sessions:initialize(ctx)
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "sessions", ctx)
|
||||
-- Check if random cookie name and secrets are already generated
|
||||
local is_random = {
|
||||
"SESSIONS_SECRET",
|
||||
"SESSIONS_NAME"
|
||||
}
|
||||
self.randoms = {}
|
||||
for i, var in ipairs(is_random) do
|
||||
if self.variables[var] == "random" then
|
||||
local data, err = self.datastore:get("storage_sessions_" .. var)
|
||||
if data then
|
||||
self.randoms[var] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Call parent initialize
|
||||
plugin.initialize(self, "sessions", ctx)
|
||||
-- Check if random cookie name and secrets are already generated
|
||||
local is_random = {
|
||||
"SESSIONS_SECRET",
|
||||
"SESSIONS_NAME",
|
||||
}
|
||||
self.randoms = {}
|
||||
for _, var in ipairs(is_random) do
|
||||
if self.variables[var] == "random" then
|
||||
local data, _ = self.datastore:get("storage_sessions_" .. var)
|
||||
if data then
|
||||
self.randoms[var] = data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sessions:set()
|
||||
if self.is_loading or self.kind ~= "http" then
|
||||
return self:ret(true, "set not needed")
|
||||
end
|
||||
local checks = {
|
||||
["IP"] = self.ctx.bw.remote_addr,
|
||||
["USER_AGENT"] = self.ctx.bw.http_user_agent or ""
|
||||
}
|
||||
self.ctx.bw.sessions_checks = {}
|
||||
for check, value in pairs(checks) do
|
||||
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
|
||||
table.insert(self.ctx.bw.sessions_checks, { check, value })
|
||||
end
|
||||
end
|
||||
return self:ret(true, "success")
|
||||
if self.is_loading or self.kind ~= "http" then
|
||||
return self:ret(true, "set not needed")
|
||||
end
|
||||
local checks = {
|
||||
["IP"] = self.ctx.bw.remote_addr,
|
||||
["USER_AGENT"] = self.ctx.bw.http_user_agent or "",
|
||||
}
|
||||
self.ctx.bw.sessions_checks = {}
|
||||
for check, value in pairs(checks) do
|
||||
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
|
||||
table.insert(self.ctx.bw.sessions_checks, { check, value })
|
||||
end
|
||||
end
|
||||
return self:ret(true, "success")
|
||||
end
|
||||
|
||||
function sessions:init()
|
||||
if self.is_loading or self.kind ~= "http" then
|
||||
return self:ret(true, "init not needed")
|
||||
end
|
||||
-- Get redis vars
|
||||
local redis_vars = {
|
||||
["USE_REDIS"] = "",
|
||||
["REDIS_HOST"] = "",
|
||||
["REDIS_PORT"] = "",
|
||||
["REDIS_DATABASE"] = "",
|
||||
["REDIS_SSL"] = "",
|
||||
["REDIS_TIMEOUT"] = "",
|
||||
["REDIS_KEEPALIVE_IDLE"] = "",
|
||||
["REDIS_KEEPALIVE_POOL"] = ""
|
||||
}
|
||||
for k, v in pairs(redis_vars) do
|
||||
local value, err = utils.get_variable(k, false)
|
||||
if value == nil then
|
||||
return self:ret(false, "can't get " .. k .. " variable : " .. err)
|
||||
end
|
||||
redis_vars[k] = value
|
||||
end
|
||||
-- Init configuration
|
||||
local config = {
|
||||
secret = self.variables["SESSIONS_SECRET"],
|
||||
cookie_name = self.variables["SESSIONS_NAME"],
|
||||
idling_timeout = tonumber(self.variables["SESSIONS_IDLING_TIMEOUT"]),
|
||||
rolling_timeout = tonumber(self.variables["SESSIONS_ROLLING_TIMEOUT"]),
|
||||
absolute_timeout = tonumber(self.variables["SESSIONS_ABSOLUTE_TIMEOUT"])
|
||||
}
|
||||
if self.variables["SESSIONS_SECRET"] == "random" then
|
||||
if self.randoms["SESSIONS_SECRET"] then
|
||||
config.secret = self.randoms["SESSIONS_SECRET"]
|
||||
else
|
||||
config.secret = utils.rand(16)
|
||||
local ok, err = self.datastore:set("storage_sessions_SESSIONS_SECRET", config.secret)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.variables["SESSIONS_NAME"] == "random" then
|
||||
if self.randoms["SESSIONS_NAME"] then
|
||||
config.cookie_name = self.randoms["SESSIONS_NAME"]
|
||||
else
|
||||
config.cookie_name = utils.rand(16)
|
||||
local ok, err = self.datastore:set("storage_sessions_SESSIONS_NAME", config.cookie_name)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
|
||||
end
|
||||
end
|
||||
end
|
||||
if redis_vars["USE_REDIS"] ~= "yes" then
|
||||
config.storage = "cookie"
|
||||
else
|
||||
config.storage = "redis"
|
||||
config.redis = {
|
||||
prefix = "sessions_",
|
||||
connect_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
|
||||
pool = "bw-redis",
|
||||
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
|
||||
ssl = redis_vars["REDIS_SSL"] == "yes",
|
||||
host = redis_vars["REDIS_HOST"],
|
||||
port = tonumber(redis_vars["REDIS_PORT"]),
|
||||
database = tonumber(redis_vars["REDIS_DATABASE"])
|
||||
}
|
||||
end
|
||||
session.init(config)
|
||||
return self:ret(true, "sessions init successful")
|
||||
if self.is_loading or self.kind ~= "http" then
|
||||
return self:ret(true, "init not needed")
|
||||
end
|
||||
-- Get redis vars
|
||||
local redis_vars = {
|
||||
["USE_REDIS"] = "",
|
||||
["REDIS_HOST"] = "",
|
||||
["REDIS_PORT"] = "",
|
||||
["REDIS_DATABASE"] = "",
|
||||
["REDIS_SSL"] = "",
|
||||
["REDIS_TIMEOUT"] = "",
|
||||
["REDIS_KEEPALIVE_IDLE"] = "",
|
||||
["REDIS_KEEPALIVE_POOL"] = "",
|
||||
}
|
||||
for k, _ in pairs(redis_vars) do
|
||||
local value, err = utils.get_variable(k, false)
|
||||
if value == nil then
|
||||
return self:ret(false, "can't get " .. k .. " variable : " .. err)
|
||||
end
|
||||
redis_vars[k] = value
|
||||
end
|
||||
-- Init configuration
|
||||
local config = {
|
||||
secret = self.variables["SESSIONS_SECRET"],
|
||||
cookie_name = self.variables["SESSIONS_NAME"],
|
||||
idling_timeout = tonumber(self.variables["SESSIONS_IDLING_TIMEOUT"]),
|
||||
rolling_timeout = tonumber(self.variables["SESSIONS_ROLLING_TIMEOUT"]),
|
||||
absolute_timeout = tonumber(self.variables["SESSIONS_ABSOLUTE_TIMEOUT"]),
|
||||
}
|
||||
if self.variables["SESSIONS_SECRET"] == "random" then
|
||||
if self.randoms["SESSIONS_SECRET"] then
|
||||
config.secret = self.randoms["SESSIONS_SECRET"]
|
||||
else
|
||||
config.secret = utils.rand(16)
|
||||
local ok, err = self.datastore:set("storage_sessions_SESSIONS_SECRET", config.secret)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.variables["SESSIONS_NAME"] == "random" then
|
||||
if self.randoms["SESSIONS_NAME"] then
|
||||
config.cookie_name = self.randoms["SESSIONS_NAME"]
|
||||
else
|
||||
config.cookie_name = utils.rand(16)
|
||||
local ok, err = self.datastore:set("storage_sessions_SESSIONS_NAME", config.cookie_name)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
|
||||
end
|
||||
end
|
||||
end
|
||||
if redis_vars["USE_REDIS"] ~= "yes" then
|
||||
config.storage = "cookie"
|
||||
else
|
||||
config.storage = "redis"
|
||||
config.redis = {
|
||||
prefix = "sessions_",
|
||||
connect_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
|
||||
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
|
||||
pool = "bw-redis",
|
||||
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
|
||||
ssl = redis_vars["REDIS_SSL"] == "yes",
|
||||
host = redis_vars["REDIS_HOST"],
|
||||
port = tonumber(redis_vars["REDIS_PORT"]),
|
||||
database = tonumber(redis_vars["REDIS_DATABASE"]),
|
||||
}
|
||||
end
|
||||
session.init(config)
|
||||
return self:ret(true, "sessions init successful")
|
||||
end
|
||||
|
||||
return sessions
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
local class = require "middleclass"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
local class = require "middleclass"
|
||||
local env = require "resty.env"
|
||||
local ipmatcher = require "resty.ipmatcher"
|
||||
local env = require "resty.env"
|
||||
local plugin = require "bunkerweb.plugin"
|
||||
local utils = require "bunkerweb.utils"
|
||||
|
||||
local whitelist = class("whitelist", plugin)
|
||||
|
||||
|
@ -23,7 +23,7 @@ function whitelist:initialize(ctx)
|
|||
["RDNS"] = {},
|
||||
["ASN"] = {},
|
||||
["USER_AGENT"] = {},
|
||||
["URI"] = {}
|
||||
["URI"] = {},
|
||||
}
|
||||
for kind, _ in pairs(kinds) do
|
||||
for data in self.variables["WHITELIST_" .. kind]:gmatch("%S+") do
|
||||
|
@ -64,11 +64,11 @@ function whitelist:init()
|
|||
["RDNS"] = {},
|
||||
["ASN"] = {},
|
||||
["USER_AGENT"] = {},
|
||||
["URI"] = {}
|
||||
["URI"] = {},
|
||||
}
|
||||
local i = 0
|
||||
for kind, _ in pairs(whitelists) do
|
||||
local f, err = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
|
||||
local f, _ = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
table.insert(whitelists[kind], line)
|
||||
|
@ -123,13 +123,14 @@ function whitelist:access()
|
|||
return self:ret(true, err, ngx.OK)
|
||||
end
|
||||
-- Perform checks
|
||||
for k, v in pairs(already_cached) do
|
||||
local ok
|
||||
for k, _ in pairs(already_cached) do
|
||||
if not already_cached[k] then
|
||||
local ok, whitelisted = self:is_whitelisted(k)
|
||||
ok, whitelisted = self:is_whitelisted(k)
|
||||
if ok == nil then
|
||||
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is whitelisted : " .. whitelisted)
|
||||
else
|
||||
local ok, err = self:add_to_cache(self:kind_to_ele(k), whitelisted)
|
||||
ok, err = self:add_to_cache(self:kind_to_ele(k), whitelisted)
|
||||
if not ok then
|
||||
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
|
||||
end
|
||||
|
@ -163,7 +164,7 @@ end
|
|||
function whitelist:check_cache()
|
||||
-- Check the caches
|
||||
local checks = {
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr
|
||||
["IP"] = "ip" .. self.ctx.bw.remote_addr,
|
||||
}
|
||||
if self.ctx.bw.http_user_agent then
|
||||
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
|
||||
|
@ -172,7 +173,7 @@ function whitelist:check_cache()
|
|||
checks["URI"] = "uri" .. self.ctx.bw.uri
|
||||
end
|
||||
local already_cached = {}
|
||||
for k, v in pairs(checks) do
|
||||
for k, _ in pairs(checks) do
|
||||
already_cached[k] = false
|
||||
end
|
||||
for k, v in pairs(checks) do
|
||||
|
@ -242,14 +243,15 @@ function whitelist:is_whitelisted_ip()
|
|||
end
|
||||
if check_rdns then
|
||||
-- Get rDNS
|
||||
-- luacheck: ignore 421
|
||||
local rdns_list, err = utils.get_rdns(self.ctx.bw.remote_addr)
|
||||
-- Check if rDNS is in whitelist
|
||||
if rdns_list then
|
||||
local forward_check = nil
|
||||
local rdns_suffix = nil
|
||||
for i, rdns in ipairs(rdns_list) do
|
||||
for j, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(- #suffix) == suffix then
|
||||
for _, rdns in ipairs(rdns_list) do
|
||||
for _, suffix in ipairs(self.lists["RDNS"]) do
|
||||
if rdns:sub(-#suffix) == suffix then
|
||||
forward_check = rdns
|
||||
rdns_suffix = suffix
|
||||
break
|
||||
|
@ -262,12 +264,15 @@ function whitelist:is_whitelisted_ip()
|
|||
if forward_check then
|
||||
local ip_list, err = utils.get_ips(forward_check)
|
||||
if ip_list then
|
||||
for i, ip in ipairs(ip_list) do
|
||||
for _, ip in ipairs(ip_list) do
|
||||
if ip == self.ctx.bw.remote_addr then
|
||||
return true, "rDNS " .. rdns_suffix
|
||||
end
|
||||
end
|
||||
self.logger:log(ngx.WARN, "IP " .. self.ctx.bw.remote_addr .. " may spoof reverse DNS " .. forward_check)
|
||||
self.logger:log(
|
||||
ngx.WARN,
|
||||
"IP " .. self.ctx.bw.remote_addr .. " may spoof reverse DNS " .. forward_check
|
||||
)
|
||||
else
|
||||
self.logger:log(ngx.ERR, "error while getting rdns (forward check) : " .. err)
|
||||
end
|
||||
|
@ -283,7 +288,7 @@ function whitelist:is_whitelisted_ip()
|
|||
if not asn then
|
||||
self.logger:log(ngx.ERR, "can't get ASN of IP " .. self.ctx.bw.remote_addr .. " : " .. err)
|
||||
else
|
||||
for i, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
for _, bl_asn in ipairs(self.lists["ASN"]) do
|
||||
if bl_asn == tostring(asn) then
|
||||
return true, "ASN " .. bl_asn
|
||||
end
|
||||
|
@ -297,7 +302,7 @@ end
|
|||
|
||||
function whitelist:is_whitelisted_uri()
|
||||
-- Check if URI is in whitelist
|
||||
for i, uri in ipairs(self.lists["URI"]) do
|
||||
for _, uri in ipairs(self.lists["URI"]) do
|
||||
if utils.regex_match(self.ctx.bw.uri, uri) then
|
||||
return true, "URI " .. uri
|
||||
end
|
||||
|
@ -308,7 +313,7 @@ end
|
|||
|
||||
function whitelist:is_whitelisted_ua()
|
||||
-- Check if UA is in whitelist
|
||||
for i, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
for _, ua in ipairs(self.lists["USER_AGENT"]) do
|
||||
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
|
||||
return true, "UA " .. ua
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
call_parentheses = "Input"
|
||||
|
||||
[sort_requires]
|
||||
enabled = true
|
Loading…
Reference in New Issue