From 77bfe2697f96da82279d63e553c8c1af98e077f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Diot?= Date: Thu, 12 Oct 2023 17:08:13 +0200 Subject: [PATCH] Add StyLua and luacheck to precommit config file and apply it --- .luacheckrc | 2 + .pre-commit-config.yaml | 13 + src/bw/lua/bunkerweb/api.lua | 54 ++- src/bw/lua/bunkerweb/cachestore.lua | 86 ++-- src/bw/lua/bunkerweb/clusterstore.lua | 235 +++++----- src/bw/lua/bunkerweb/datastore.lua | 25 +- src/bw/lua/bunkerweb/helpers.lua | 488 ++++++++++---------- src/bw/lua/bunkerweb/logger.lua | 2 +- src/bw/lua/bunkerweb/mmdb.lua | 4 +- src/bw/lua/bunkerweb/plugin.lua | 151 +++--- src/bw/lua/bunkerweb/utils.lua | 163 ++++--- src/common/core/antibot/antibot.lua | 121 ++--- src/common/core/badbehavior/badbehavior.lua | 56 ++- src/common/core/blacklist/blacklist.lua | 56 ++- src/common/core/bunkernet/bunkernet.lua | 43 +- src/common/core/cors/cors.lua | 29 +- src/common/core/country/country.lua | 88 ++-- src/common/core/dnsbl/dnsbl.lua | 57 +-- src/common/core/errors/errors.lua | 32 +- src/common/core/greylist/greylist.lua | 32 +- src/common/core/headers/headers.lua | 183 ++++---- src/common/core/letsencrypt/letsencrypt.lua | 21 +- src/common/core/limit/limit.lua | 144 +++--- src/common/core/misc/misc.lua | 34 +- src/common/core/redis/redis.lua | 6 +- src/common/core/reversescan/reversescan.lua | 264 +++++------ src/common/core/sessions/sessions.lua | 210 ++++----- src/common/core/whitelist/whitelist.lua | 45 +- stylua.toml | 4 + 29 files changed, 1428 insertions(+), 1220 deletions(-) create mode 100644 .luacheckrc create mode 100644 stylua.toml diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 000000000..ef653efbb --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,2 @@ +globals = {"ngx", "delay", "unpack"} +ignore = {"411"} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb1f3a43e..3741dfac8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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: diff --git a/src/bw/lua/bunkerweb/api.lua b/src/bw/lua/bunkerweb/api.lua index f70e4c4f0..6e969343d 100644 --- a/src/bw/lua/bunkerweb/api.lua +++ b/src/bw/lua/bunkerweb/api.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/cachestore.lua b/src/bw/lua/bunkerweb/cachestore.lua index 4b1bd5aa7..b26b6c474 100644 --- a/src/bw/lua/bunkerweb/cachestore.lua +++ b/src/bw/lua/bunkerweb/cachestore.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/clusterstore.lua b/src/bw/lua/bunkerweb/clusterstore.lua index d6528c3a0..d9359b477 100644 --- a/src/bw/lua/bunkerweb/clusterstore.lua +++ b/src/bw/lua/bunkerweb/clusterstore.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/datastore.lua b/src/bw/lua/bunkerweb/datastore.lua index 6be6b4c4a..1eae070c0 100644 --- a/src/bw/lua/bunkerweb/datastore.lua +++ b/src/bw/lua/bunkerweb/datastore.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/helpers.lua b/src/bw/lua/bunkerweb/helpers.lua index 7b20ea835..3fbaae06d 100644 --- a/src/bw/lua/bunkerweb/helpers.lua +++ b/src/bw/lua/bunkerweb/helpers.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/logger.lua b/src/bw/lua/bunkerweb/logger.lua index 39713280e..c71c64f27 100644 --- a/src/bw/lua/bunkerweb/logger.lua +++ b/src/bw/lua/bunkerweb/logger.lua @@ -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) diff --git a/src/bw/lua/bunkerweb/mmdb.lua b/src/bw/lua/bunkerweb/mmdb.lua index 45a20e697..23105eccd 100644 --- a/src/bw/lua/bunkerweb/mmdb.lua +++ b/src/bw/lua/bunkerweb/mmdb.lua @@ -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", } diff --git a/src/bw/lua/bunkerweb/plugin.lua b/src/bw/lua/bunkerweb/plugin.lua index 846d07f81..473714a7c 100644 --- a/src/bw/lua/bunkerweb/plugin.lua +++ b/src/bw/lua/bunkerweb/plugin.lua @@ -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 diff --git a/src/bw/lua/bunkerweb/utils.lua b/src/bw/lua/bunkerweb/utils.lua index dcf267f59..364b52b85 100644 --- a/src/bw/lua/bunkerweb/utils.lua +++ b/src/bw/lua/bunkerweb/utils.lua @@ -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 diff --git a/src/common/core/antibot/antibot.lua b/src/common/core/antibot/antibot.lua index f104e73ff..fe5b1880f 100644 --- a/src/common/core/antibot/antibot.lua +++ b/src/common/core/antibot/antibot.lua @@ -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 diff --git a/src/common/core/badbehavior/badbehavior.lua b/src/common/core/badbehavior/badbehavior.lua index f6d5da538..8673aa349 100644 --- a/src/common/core/badbehavior/badbehavior.lua +++ b/src/common/core/badbehavior/badbehavior.lua @@ -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 diff --git a/src/common/core/blacklist/blacklist.lua b/src/common/core/blacklist/blacklist.lua index 8eb493116..2e082d0ad 100644 --- a/src/common/core/blacklist/blacklist.lua +++ b/src/common/core/blacklist/blacklist.lua @@ -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 diff --git a/src/common/core/bunkernet/bunkernet.lua b/src/common/core/bunkernet/bunkernet.lua index a31b6472f..6b603492e 100644 --- a/src/common/core/bunkernet/bunkernet.lua +++ b/src/common/core/bunkernet/bunkernet.lua @@ -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 diff --git a/src/common/core/cors/cors.lua b/src/common/core/cors/cors.lua index e032273c6..fc2e1a00f 100644 --- a/src/common/core/cors/cors.lua +++ b/src/common/core/cors/cors.lua @@ -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 diff --git a/src/common/core/country/country.lua b/src/common/core/country/country.lua index 1b983ed07..6f697b154 100644 --- a/src/common/core/country/country.lua +++ b/src/common/core/country/country.lua @@ -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 diff --git a/src/common/core/dnsbl/dnsbl.lua b/src/common/core/dnsbl/dnsbl.lua index 88617ac50..90bca0e60 100644 --- a/src/common/core/dnsbl/dnsbl.lua +++ b/src/common/core/dnsbl/dnsbl.lua @@ -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 diff --git a/src/common/core/errors/errors.lua b/src/common/core/errors/errors.lua index cabf4dca4..05aea4bc5 100644 --- a/src/common/core/errors/errors.lua +++ b/src/common/core/errors/errors.lua @@ -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 diff --git a/src/common/core/greylist/greylist.lua b/src/common/core/greylist/greylist.lua index 6c66854f3..da68426f5 100644 --- a/src/common/core/greylist/greylist.lua +++ b/src/common/core/greylist/greylist.lua @@ -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 diff --git a/src/common/core/headers/headers.lua b/src/common/core/headers/headers.lua index c8b6d46d2..b006c0679 100644 --- a/src/common/core/headers/headers.lua +++ b/src/common/core/headers/headers.lua @@ -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 diff --git a/src/common/core/letsencrypt/letsencrypt.lua b/src/common/core/letsencrypt/letsencrypt.lua index 8cf70af5d..dbf958791 100644 --- a/src/common/core/letsencrypt/letsencrypt.lua +++ b/src/common/core/letsencrypt/letsencrypt.lua @@ -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 diff --git a/src/common/core/limit/limit.lua b/src/common/core/limit/limit.lua index c9ccd5a1f..20f70f642 100644 --- a/src/common/core/limit/limit.lua +++ b/src/common/core/limit/limit.lua @@ -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 diff --git a/src/common/core/misc/misc.lua b/src/common/core/misc/misc.lua index 9ad501185..1067faf92 100644 --- a/src/common/core/misc/misc.lua +++ b/src/common/core/misc/misc.lua @@ -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 diff --git a/src/common/core/redis/redis.lua b/src/common/core/redis/redis.lua index 36e11d961..e011f58a6 100644 --- a/src/common/core/redis/redis.lua +++ b/src/common/core/redis/redis.lua @@ -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 diff --git a/src/common/core/reversescan/reversescan.lua b/src/common/core/reversescan/reversescan.lua index 00b399d4d..10107e2ca 100644 --- a/src/common/core/reversescan/reversescan.lua +++ b/src/common/core/reversescan/reversescan.lua @@ -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 diff --git a/src/common/core/sessions/sessions.lua b/src/common/core/sessions/sessions.lua index 117763a86..a5ad86280 100644 --- a/src/common/core/sessions/sessions.lua +++ b/src/common/core/sessions/sessions.lua @@ -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 diff --git a/src/common/core/whitelist/whitelist.lua b/src/common/core/whitelist/whitelist.lua index 8b105e996..b74f1ec4c 100644 --- a/src/common/core/whitelist/whitelist.lua +++ b/src/common/core/whitelist/whitelist.lua @@ -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 diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 000000000..df35b312a --- /dev/null +++ b/stylua.toml @@ -0,0 +1,4 @@ +call_parentheses = "Input" + +[sort_requires] +enabled = true