perf - ctx caching and per worker LRU for readonly variables

This commit is contained in:
florian 2023-06-09 10:34:34 +02:00
parent b27958a19c
commit 02d9403937
No known key found for this signature in database
GPG key ID: 3D80806F12602A7C
34 changed files with 647 additions and 559 deletions

View file

@ -14,6 +14,16 @@ api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
function api:initialize()
self.datastore = datastore:new()
self.logger = logger:new("API")
self.ctx = ngx.ctx
local data, err = utils.get_variable("API_WHITELIST_IP", false)
self.ips = {}
if not data then
self.logger.log(ngx.ERR, "can't get API_WHITELIST_IP variable : " .. err)
else
for ip in data:gmatch("%S+") do
table.insert(self.ips, ip)
end
end
end
function api:log_cmd(cmd, status, stdout, stderr)
@ -71,17 +81,17 @@ api.global.POST["^/stop$"] = function(self)
end
api.global.POST["^/confs$"] = function(self)
local tmp = "/var/tmp/bunkerweb/api_" .. ngx.ctx.bw.uri:sub(2) .. ".tar.gz"
local destination = "/usr/share/bunkerweb/" .. ngx.ctx.bw.uri:sub(2)
if ngx.ctx.bw.uri == "/confs" then
local tmp = "/var/tmp/bunkerweb/api_" .. self.ctx.bw.uri:sub(2) .. ".tar.gz"
local destination = "/usr/share/bunkerweb/" .. self.ctx.bw.uri:sub(2)
if self.ctx.bw.uri == "/confs" then
destination = "/etc/nginx"
elseif ngx.ctx.bw.uri == "/data" then
elseif self.ctx.bw.uri == "/data" then
destination = "/data"
elseif ngx.ctx.bw.uri == "/cache" then
elseif self.ctx.bw.uri == "/cache" then
destination = "/var/cache/bunkerweb"
elseif ngx.ctx.bw.uri == "/custom_configs" then
elseif self.ctx.bw.uri == "/custom_configs" then
destination = "/etc/bunkerweb/configs"
elseif ngx.ctx.bw.uri == "/plugins" then
elseif self.ctx.bw.uri == "/plugins" then
destination = "/etc/bunkerweb/plugins"
end
local form, err = upload:new(4096)
@ -186,20 +196,16 @@ api.global.GET["^/bans$"] = function(self)
end
function api:is_allowed_ip()
local data, err = self.datastore:get("api_whitelist_ip")
if not data then
return false, "can't access api_allowed_ips in datastore"
end
if utils.is_ip_in_networks(ngx.ctx.bw.remote_addr, cjson.decode(data)) then
if utils.is_ip_in_networks(self.ctx.bw.remote_addr, self.ips) then
return true, "ok"
end
return false, "IP is not in API_WHITELIST_IP"
end
function api:do_api_call()
if self.global[ngx.ctx.bw.request_method] ~= nil then
for uri, api_fun in pairs(self.global[ngx.ctx.bw.request_method]) do
if string.match(ngx.ctx.bw.uri, uri) then
if self.global[self.ctx.bw.request_method] ~= nil then
for uri, api_fun in pairs(self.global[self.ctx.bw.request_method]) do
if string.match(self.ctx.bw.uri, uri) then
local status, resp = api_fun(self)
local ret = true
if status ~= ngx.HTTP_OK then

View file

@ -42,7 +42,8 @@ if not cache then
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
end
function cachestore:initialize(use_redis, new_cs)
function cachestore:initialize(use_redis, new_cs, ctx)
self.ctx = ctx
self.cache = cache
self.use_redis = use_redis or false
self.logger = module_logger
@ -50,7 +51,7 @@ function cachestore:initialize(use_redis, new_cs)
self.clusterstore = clusterstore:new(false)
self.shared_cs = false
else
self.clusterstore = utils.get_ctx_obj("clusterstore")
self.clusterstore = utils.get_ctx_obj("clusterstore", self.ctx)
self.shared_cs = true
end
end

View file

@ -1,6 +1,12 @@
local class = require "middleclass"
local lrucache = require "resty.lrucache"
local datastore = class("datastore")
local lru, err = lrucache.new(10000)
if not lru then
require "bunkerweb.logger":new("DATASTORE"):log(ngx.ERR, "failed to instantiate LRU cache : " .. (err or "unknown error"))
end
function datastore:initialize()
self.dict = ngx.shared.datastore
if not self.dict then
@ -8,7 +14,11 @@ function datastore:initialize()
end
end
function datastore:get(key)
function datastore:get(key, worker)
if worker then
local value, err = lru:get(key)
return value, err or "not found"
end
local value, err = self.dict:get(key)
if not value and not err then
err = "not found"
@ -16,21 +26,35 @@ function datastore:get(key)
return value, err
end
function datastore:set(key, value, exptime)
function datastore:set(key, value, exptime, worker)
if worker then
lru:set(key, value, exptime)
return true, "success"
end
exptime = exptime or 0
return self.dict:safe_set(key, value, exptime)
end
function datastore:delete(key)
function datastore:delete(key, worker)
if worker then
lru:delete(key)
return true, "success"
end
self.dict:delete(key)
return true, "success"
end
function datastore:keys()
function datastore:keys(worker)
if worker then
return lru:keys(0)
end
return self.dict:get_keys(0)
end
function datastore:ttl(key)
if worker then
return false, "no supported for LRU"
end
local ttl, err = self.dict:ttl(key)
if not ttl then
return false, err
@ -38,8 +62,13 @@ function datastore:ttl(key)
return true, ttl
end
function datastore:delete_all(pattern)
local keys = self.dict:get_keys(0)
function datastore:delete_all(pattern, worker)
local keys = {}
if worker then
lru:keys(0)
else
local keys = self.dict:get_keys(0)
end
for i, key in ipairs(keys) do
if key:match(pattern) then
self.dict:delete(key)
@ -48,4 +77,8 @@ function datastore:delete_all(pattern)
return true, "success"
end
function datastore:flush_lru()
lru:flush_all()
end
return datastore

View file

@ -108,9 +108,9 @@ helpers.require_plugin = function(id)
return plugin_lua, "require() call successful for plugin " .. id
end
helpers.new_plugin = function(plugin_lua)
helpers.new_plugin = function(plugin_lua, ctx)
-- Require call
local ok, plugin_obj = pcall(plugin_lua.new, plugin_lua)
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
@ -148,8 +148,9 @@ end
helpers.fill_ctx = function()
-- Return errors as table
local errors = {}
local ctx = ngx.ctx
-- Check if ctx is already filled
if not ngx.ctx.bw then
if not ctx.bw then
-- Instantiate bw table
local data = {}
-- Common vars
@ -158,14 +159,18 @@ helpers.fill_ctx = function()
data.kind = "stream"
end
data.remote_addr = ngx.var.remote_addr
data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type
data.http_origin = ngx.var.http_origin
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()
end
-- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then
@ -180,17 +185,78 @@ helpers.fill_ctx = function()
data.integration = utils.get_integration()
data.version = utils.get_version()
-- Fill ctx
ngx.ctx.bw = data
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
ngx.ctx.bw.datastore = require "bunkerweb.datastore":new()
ngx.ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ngx.ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors
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]
elseif 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]
break
end
end
end
if data.multiple then
for variable, value in pairs(all_variables) do
local found, _, prefix = variable:find("^([^_]*)_?" .. variable .. "_[0-9]+$")
if found then
if multisite and prefix then
variables[prefix][setting] = value
break
end
variables["global"][setting] = prefix
break
end
end
end
end
return true, variables
end
return helpers

View file

@ -7,7 +7,7 @@ local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
function plugin:initialize(id)
function plugin:initialize(id, ctx)
-- Store common, values
self.id = id
local multisite = false
@ -27,29 +27,40 @@ function plugin:initialize(id)
end
self.use_redis = use_redis == "yes"
if self.is_request then
self.datastore = utils.get_ctx_obj("datastore") or datastore:new()
self.cachestore = utils.get_ctx_obj("cachestore") or cachestore:new(use_redis == "yes", true)
self.clusterstore = utils.get_ctx_obj("clusterstore") or clusterstore:new(false)
-- 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 encoded_metadata, err = self.datastore:get("plugin_" .. id)
if not encoded_metadata then
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 = {}
local metadata = cjson.decode(encoded_metadata)
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)

View file

@ -20,49 +20,32 @@ utils.get_variable = function(var, site_search)
site_search = true
end
-- Get global value
local value, err = datastore:get("variable_" .. var)
if not value then
return nil, "can't access variable " .. var .. " from datastore : " .. err
local variables, err = datastore:get('variables', true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
local value = variables["global"][var]
-- Site search case
if site_search then
-- Check if multisite is set to yes
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "can't access variable MULTISITE from datastore : " .. err
end
-- Multisite case
if multisite == "yes" and ngx.var.server_name then
local value_site, err = datastore:get("variable_" .. ngx.var.server_name .. "_" .. var)
if value_site then
value = value_site
end
end
local multisite = site_search and variables["global"]["MULTISITE"] == "yes" and ngx.var.server_name
if multisite then
value = variables[ngx.var.server_name][var]
end
return value, "success"
end
utils.has_variable = function(var, value)
-- Get global variable
local check_value, err = datastore:get("variable_" .. var)
if not value then
return nil, "Can't access variable " .. var .. " from datastore : " .. err
end
-- Check if multisite is set to yes
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "Can't access variable MULTISITE from datastore : " .. err
local variables, err = datastore:get('variables', true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
-- Multisite case
if multisite == "yes" then
local servers, err = datastore:get("variable_SERVER_NAME")
if not servers then
return nil, "Can't access variable SERVER_NAME from datastore : " .. err
end
local multisite = variables["global"]["MULTISITE"] == "yes"
if multisite then
local servers = variables["global"]["SERVER_NAME"]
-- Check each server
for server in servers:gmatch("%S+") do
local check_value_site, err = datastore:get("variable_" .. server .. "_" .. var)
if check_value_site and check_value_site == value then
if variables[server][var] == "value" then
return true, "success"
end
end
@ -70,30 +53,22 @@ utils.has_variable = function(var, value)
return false, "success"
end
end
return check_value == value, "success"
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 check_value, err = datastore:get("variable_" .. var)
if not value then
return nil, "Can't access variable " .. var .. " from datastore : " .. err
end
-- Check if multisite is set to yes
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "Can't access variable MULTISITE from datastore : " .. err
local variables, err = datastore:get('variables', true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
-- Multisite case
if multisite == "yes" then
local servers, err = datastore:get("variable_SERVER_NAME")
if not servers then
return nil, "Can't access variable SERVER_NAME from datastore : " .. err
end
local multisite = variables["global"]["MULTISITE"] == "yes"
if multisite then
local servers = variables["global"]["SERVER_NAME"]
-- Check each server
for server in servers:gmatch("%S+") do
local check_value_site, err = datastore:get("variable_" .. server .. "_" .. var)
if check_value_site and check_value_site ~= value then
if variables[server][var] ~= "value" then
return true, "success"
end
end
@ -101,33 +76,24 @@ utils.has_not_variable = function(var, value)
return false, "success"
end
end
return check_value ~= value, "success"
return variables["global"][var] ~= value, "success"
end
utils.get_multiple_variables = function(vars)
-- Get all keys
local keys = datastore:keys()
local variables, err = datastore:get('variables', true)
if not variables then
return nil, "can't access variables " .. var .. " from datastore : " .. err
end
local result = {}
-- Loop on keys
for i, key in ipairs(keys) do
-- Loop on scoped vars
for scope, scoped_vars in pairs(variables) do
result[scope] = {}
-- Loop on vars
for j, var in ipairs(vars) do
-- Filter on good ones
local _, _, server, subvar = key:find("variable_(.*)_?(" .. var .. "_?%d*)")
if subvar then
if not server or server == "" then
server = "global"
else
server = server:sub(1, -2)
for variable, value in pairs(scoped_vars) do
for i, var in ipairs(vars) do
if variable:find("^" .. var .. "_?[0-9]*$") then
result[scope][variable] = value
end
if result[server] == nil then
result[server] = {}
end
local value, err = datastore:get(key)
if not value then
return nil, err
end
result[server][subvar] = value
end
end
end
@ -205,23 +171,25 @@ end
utils.get_integration = function()
-- Check if already in datastore
local integration, err = datastore:get("misc_integration")
local integration, err = datastore:get("misc_integration", true)
if integration then
return integration
end
local variables, err = datastore:get("variables", true)
if not variables then
logger:log(ngx.ERR, "can't get variables from datastore : " .. err)
return "unknown"
end
-- Swarm
local var, err = datastore:get("variable_SWARM_MODE")
if var == "yes" then
if variables["global"]["SWARM_MODE"] == "yes" then
integration = "swarm"
else
-- Kubernetes
local var, err = datastore:get("variable_KUBERNETES_MODE")
if var == "yes" then
if variables["global"]["KUBERNETES_MODE"] == "yes" then
integration = "kubernetes"
else
-- Autoconf
local var, err = datastore:get("variable_AUTOCONF_MODE")
if var == "yes" then
if variables["global"]["AUTOCONF_MODE"] == "yes" then
integration = "autoconf"
else
-- Already present (e.g. : linux)
@ -247,7 +215,7 @@ utils.get_integration = function()
end
end
-- Save integration
local ok, err = datastore:set("misc_integration", integration)
local ok, err = datastore:set("misc_integration", integration, nil, true)
if not ok then
logger:log(ngx.ERR, "can't cache integration to datastore : " .. err)
end
@ -256,7 +224,7 @@ end
utils.get_version = function()
-- Check if already in datastore
local version, err = datastore:get("misc_version")
local version, err = datastore:get("misc_version", true)
if version then
return version
end
@ -269,17 +237,17 @@ utils.get_version = function()
version = f:read("*a"):gsub("[\n\r]", "")
f:close()
-- Save it to datastore
local ok, err = datastore:set("misc_version", version)
local ok, err = datastore:set("misc_version", version, nil, true)
if not ok then
logger:log(ngx.ERR, "can't cache version to datastore : " .. err)
end
return version
end
utils.get_reason = function()
utils.get_reason = function(ctx)
-- ngx.ctx
if ngx.ctx.reason then
return ngx.ctx.reason
if ctx.bw.reason then
return ctx.bw.reason
end
-- ngx.var
if ngx.var.reason and ngx.var.reason ~= "" then
@ -295,7 +263,7 @@ utils.get_reason = function()
return banned
end
-- unknown
if ngx.status == utils.get_deny_status() then
if ngx.status == utils.get_deny_status(ctx) then
return "unknown"
end
return nil
@ -303,23 +271,23 @@ end
utils.get_resolvers = function()
-- Get resolvers from datastore if existing
local str_resolvers, err = datastore:get("misc_resolvers")
if str_resolvers then
return cjson.decode(str_resolvers)
local resolvers, err = datastore:get("misc_resolvers", true)
if resolvers then
return resolvers
end
-- Otherwise extract DNS_RESOLVERS variable
local var_resolvers, err = datastore:get("variable_DNS_RESOLVERS")
if not var_resolvers then
logger:log(ngx.ERR, "can't get variable DNS_RESOLVERS from datastore : " .. err)
return nil, err
local variables, err = datastore:get("variables", true)
if not variables then
logger:log(ngx.ERR, "can't get variables from datastore : " .. err)
return "unknown"
end
-- Make table for resolver1 resolver2 ... string
local resolvers = {}
for str_resolver in var_resolvers:gmatch("%S+") do
for str_resolver in variables["global"]["DNS_RESOLVERS"]:gmatch("%S+") do
table.insert(resolvers, str_resolver)
end
-- Add it to the datastore
local ok, err = datastore:set("misc_resolvers", cjson.encode(resolvers))
local ok, err = datastore:set("misc_resolvers", resolvers, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save misc_resolvers to datastore : " .. err)
end
@ -497,24 +465,24 @@ utils.rand = function(nb, no_numbers)
return result
end
utils.get_deny_status = function()
utils.get_deny_status = function(ctx)
-- Stream case
if ngx.ctx.bw and ngx.ctx.bw.kind == "stream" then
if ctx.bw and ctx.bw.kind == "stream" then
return 444
end
-- http case
local status, err = datastore:get("variable_DENY_HTTP_STATUS")
if not status then
logger:log(ngx.ERR, "can't get DENY_HTTP_STATUS variable " .. err)
local variables, err = datastore:get("variables", true)
if not variables then
logger:log(ngx.ERR, "can't get variables from datastore : " .. err)
return 403
end
return tonumber(status)
return tonumber(variables["global"]["DENY_HTTP_STATUS"])
end
utils.check_session = function()
utils.check_session = function(ctx)
local _session, err, exists, refreshed = session.start({audience = "metadata"})
if exists then
for i, check in ipairs(ngx.ctx.bw.sessions_checks) do
for i, check in ipairs(ctx.bw.sessions_checks) do
local key = check[1]
local value = check[2]
if _session:get(key) ~= value then
@ -524,11 +492,11 @@ utils.check_session = function()
return false, "session:destroy() error : " .. err
end
logger:log(ngx.WARN, "session check " .. key .. " failed, destroying session")
return utils.check_session()
return utils.check_session(ctx)
end
end
else
for i, check in ipairs(ngx.ctx.bw.sessions_checks) do
for i, check in ipairs(ctx.bw.sessions_checks) do
_session:set(check[1], check[2])
end
local ok, err = _session:save()
@ -537,15 +505,15 @@ utils.check_session = function()
return false, "session:save() error : " .. err
end
end
ngx.ctx.bw.sessions_is_checked = true
ctx.bw.sessions_is_checked = true
_session:close()
return true, exists
end
utils.get_session = function(audience)
utils.get_session = function(audience, ctx)
-- Check session
if not ngx.ctx.bw.sessions_is_checked then
local ok, err = utils.check_session()
if not ctx.bw.sessions_is_checked then
local ok, err = utils.check_session(ctx)
if not ok then
return false, "error while checking session, " .. err
end
@ -558,20 +526,20 @@ utils.get_session = function(audience)
return _session
end
utils.get_session_data = function(_session, site)
utils.get_session_data = function(_session, site, ctx)
local site_only = site == nil or site
local data = _session:get_data()
if site_only then
return data[ngx.ctx.bw.server_name] or {}
return data[ctx.bw.server_name] or {}
end
return data
end
utils.set_session_data = function(_session, data, site)
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()
all_data[ngx.ctx.bw.server_name] = data
all_data[ctx.bw.server_name] = data
_session:set_data(all_data)
return _session:save()
end
@ -734,9 +702,9 @@ utils.kill_all_threads = function(threads)
end
end
utils.get_ctx_obj = function(obj)
if ngx.ctx and ngx.ctx.bw then
return ngx.ctx.bw[obj]
utils.get_ctx_obj = function(obj, ctx)
if ctx and ctx.bw then
return ctx.bw[obj]
end
return nil
end

View file

@ -25,7 +25,7 @@ server {
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -36,30 +36,33 @@ server {
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Check host header
if not ngx.ctx.bw.http_host or ngx.ctx.bw.http_host ~= "{{ API_SERVER_NAME }}" then
logger:log(ngx.WARN, "wrong Host header from IP " .. ngx.ctx.bw.remote_addr)
if not ctx.bw.http_host or ctx.bw.http_host ~= "{{ API_SERVER_NAME }}" then
logger:log(ngx.WARN, "wrong Host header from IP " .. ctx.bw.remote_addr)
return ngx.exit(ngx.HTTP_CLOSE)
end
-- Check IP
local ok, err = api:is_allowed_ip()
if not ok then
logger:log(ngx.WARN, "can't validate access from IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
logger:log(ngx.WARN, "can't validate access from IP " .. ctx.bw.remote_addr .. " : " .. err)
return ngx.exit(ngx.HTTP_CLOSE)
end
logger:log(ngx.NOTICE, "validated access from IP " .. ngx.ctx.bw.remote_addr)
logger:log(ngx.NOTICE, "validated access from IP " .. ctx.bw.remote_addr)
-- Do API call
local ok, err, status, resp = api:do_api_call()
if not ok then
logger:log(ngx.WARN, "call from " .. ngx.ctx.bw.remote_addr .. " on " .. ngx.ctx.bw.uri .. " failed : " .. err)
logger:log(ngx.WARN, "call from " .. ctx.bw.remote_addr .. " on " .. ctx.bw.uri .. " failed : " .. err)
else
logger:log(ngx.NOTICE, "successful call from " .. ngx.ctx.bw.remote_addr .. " on " .. ngx.ctx.bw.uri .. " : " .. err)
logger:log(ngx.NOTICE, "successful call from " .. ctx.bw.remote_addr .. " on " .. ctx.bw.uri .. " : " .. err)
end
-- Start API handler
logger:log(ngx.INFO, "API handler ended")
-- Save ctx
ngx.ctx = ctx
-- Send response
ngx.status = status
ngx.say(resp)

View file

@ -52,7 +52,7 @@ server {
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -102,10 +102,13 @@ server {
logger:log(ngx.INFO, "called log_default() methods of plugins")
-- Display reason at info level
if ngx.ctx.reason then
if ctx.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. reason)
end
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "log_default phase ended")
}

View file

@ -13,7 +13,8 @@ logger:log(ngx.NOTICE, "init phase started")
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin", "^variable_", "^api_", "^misc_"}
datastore:flush_lru()
local data_keys = {"^plugin", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
@ -24,58 +25,6 @@ for i, key in pairs(data_keys) do
end
logger:log(ngx.NOTICE, "deleted old keys from datastore")
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
return false
end
file:close()
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
local ok, err = datastore:set("variable_" .. variable, value)
if not ok then
logger:log(ngx.ERR, "can't save variable " .. variable .. " into datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved variable " .. variable .. "=" .. value .. " into datastore")
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")
if not value then
logger:log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
return false
end
if value == "yes" then
local value, err = datastore:get("variable_API_WHITELIST_IP")
if not value then
logger:log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
return false
end
local whitelists = {}
for whitelist in value:gmatch("%S+") do
table.insert(whitelists, whitelist)
end
local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
if not ok then
logger:log(ngx.ERR, "can't save API whitelist_ip to datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved API whitelist_ip into datastore")
end
logger:log(ngx.NOTICE, "saved API values into datastore")
-- Load plugins into the datastore
logger:log(ngx.NOTICE, "saving plugins into datastore ...")
local plugins = {}
@ -87,7 +36,7 @@ for i, plugin_path in ipairs(plugin_paths) do
if not ok then
logger:log(ngx.ERR, plugin)
else
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
local ok, err = datastore:set("plugin_" .. plugin.id, plugin, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
@ -97,12 +46,70 @@ for i, plugin_path in ipairs(plugin_paths) do
end
end
end
local ok, err = datastore:set("plugins", cjson.encode(plugins))
local ok, err = datastore:set("plugins", plugins, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
return false
end
file:close()
local all_variables = {}
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
all_variables[variable] = value
end
local ok, variables = helpers.load_variables(all_variables, plugins)
if not ok then
logger:log(ngx.ERR, "error while loading variables : " .. variables)
return false
end
local ok, err = datastore:set("variables", variables, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
-- logger:log(ngx.NOTICE, "saving API values into datastore ...")
-- local value, err = datastore:get("variable_USE_API")
-- if not value then
-- logger:log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
-- return false
-- end
-- if value == "yes" then
-- local value, err = datastore:get("variable_API_WHITELIST_IP")
-- if not value then
-- logger:log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
-- return false
-- end
-- local whitelists = {}
-- for whitelist in value:gmatch("%S+") do
-- table.insert(whitelists, whitelist)
-- end
-- local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
-- if not ok then
-- logger:log(ngx.ERR, "can't save API whitelist_ip to datastore : " .. err)
-- return false
-- end
-- logger:log(ngx.INFO, "saved API whitelist_ip into datastore")
-- end
-- logger:log(ngx.NOTICE, "saved API values into datastore")
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
@ -112,7 +119,7 @@ end
for phase, id_list in pairs(order) do
logger:log(ngx.NOTICE, "plugins order for phase " .. phase .. " : " .. cjson.encode(id_list))
end
local ok, err = datastore:set("plugins_order", cjson.encode(order))
local ok, err = datastore:set("plugins_order", order, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false

View file

@ -13,7 +13,8 @@ logger:log(ngx.NOTICE, "init-stream phase started")
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin", "^variable_", "^api_", "^misc_"}
datastore:flush_lru()
local data_keys = {"^plugin", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
@ -24,58 +25,6 @@ for i, key in pairs(data_keys) do
end
logger:log(ngx.NOTICE, "deleted old keys from datastore")
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
return false
end
file:close()
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
local ok, err = datastore:set("variable_" .. variable, value)
if not ok then
logger:log(ngx.ERR, "can't save variable " .. variable .. " into datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved variable " .. variable .. "=" .. value .. " into datastore")
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")
if not value then
logger:log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
return false
end
if value == "yes" then
local value, err = datastore:get("variable_API_WHITELIST_IP")
if not value then
logger:log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
return false
end
local whitelists = {}
for whitelist in value:gmatch("%S+") do
table.insert(whitelists, whitelist)
end
local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
if not ok then
logger:log(ngx.ERR, "can't save API whitelist_ip to datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved API whitelist_ip into datastore")
end
logger:log(ngx.NOTICE, "saved API values into datastore")
-- Load plugins into the datastore
logger:log(ngx.NOTICE, "saving plugins into datastore ...")
local plugins = {}
@ -87,7 +36,7 @@ for i, plugin_path in ipairs(plugin_paths) do
if not ok then
logger:log(ngx.ERR, plugin)
else
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
local ok, err = datastore:set("plugin_" .. plugin.id, plugin, true)
if not ok then
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
@ -97,12 +46,44 @@ for i, plugin_path in ipairs(plugin_paths) do
end
end
end
local ok, err = datastore:set("plugins", cjson.encode(plugins))
local ok, err = datastore:set("plugins", plugins, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
return false
end
file:close()
local all_variables = {}
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
all_variables[variable] = value
end
local ok, variables = helpers.load_variables(all_variables, plugins)
if not ok then
logger:log(ngx.ERR, "error while loading variables : " .. variables)
return false
end
local ok, err = datastore:set("variables", variables, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
@ -112,7 +93,7 @@ end
for phase, id_list in pairs(order) do
logger:log(ngx.NOTICE, "plugins order for phase " .. phase .. " : " .. cjson.encode(id_list))
end
local ok, err = datastore:set("plugins_order", cjson.encode(order))
local ok, err = datastore:set("plugins_order", order, nil, true)
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false

View file

@ -57,7 +57,7 @@ local ready_work = function(premature)
logger:log(ngx.INFO, "init_worker phase started")
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
@ -66,7 +66,6 @@ local ready_work = function(premature)
end
return
end
order = cjson.decode(order)
-- Call init_worker() methods
logger:log(ngx.INFO, "calling init_worker() methods of plugins ...")

View file

@ -21,7 +21,7 @@ logger:log(ngx.INFO, "access phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -32,23 +32,24 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Process bans as soon as possible
local banned, reason, ttl = utils.is_banned(ngx.ctx.bw.remote_addr)
if banned == nil then
logger:log(ngx.ERR, "can't check if IP " .. ngx.ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
logger:log(ngx.WARN, "IP " .. ngx.ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
return ngx.exit(utils.get_deny_status())
else
logger:log(ngx.INFO, "IP " .. ngx.ctx.bw.remote_addr .. " is not banned")
if ctx.bw.is_whitelisted ~= "yes" then
local banned, reason, ttl = utils.is_banned(ctx.bw.remote_addr)
if banned == nil then
logger:log(ngx.ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
logger:log(ngx.WARN, "IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
return ngx.exit(utils.get_deny_status(ctx))
else
logger:log(ngx.INFO, "IP " .. ctx.bw.remote_addr .. " is not banned")
end
end
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
order = cjson.decode(order)
-- Call access() methods
logger:log(ngx.INFO, "calling access() methods of plugins ...")
@ -65,7 +66,7 @@ for i, plugin_id in ipairs(order.access) do
-- Check if plugin has access method
if plugin_lua.access ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -78,8 +79,8 @@ for i, plugin_id in ipairs(order.access) do
logger:log(ngx.INFO, plugin_id .. ":access() call successful : " .. ret.msg)
end
if ret.status then
if ret.status == utils.get_deny_status() then
ngx.ctx.reason = plugin_id
if ret.status == utils.get_deny_status(ctx) then
ctx.bw.reason = plugin_id
logger:log(ngx.WARN, "denied access from " .. plugin_id .. " : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
@ -99,13 +100,8 @@ for i, plugin_id in ipairs(order.access) do
end
logger:log(ngx.INFO, "called access() methods of plugins")
-- Save session if needed
-- local ok, err = utils.save_session()
-- if not ok then
-- logger:log(ngx.ERR, "can't save session : " .. err)
-- else
-- logger:log(ngx.INFO, "session save return : " .. err)
-- end
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "access phase ended")

View file

@ -13,7 +13,7 @@ logger:log(ngx.INFO, "header phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -24,12 +24,11 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
order = cjson.decode(order)
-- Call header() methods
logger:log(ngx.INFO, "calling header() methods of plugins ...")
@ -44,7 +43,7 @@ for i, plugin_id in ipairs(order.header) do
-- Check if plugin has header method
if plugin_lua.header ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -64,6 +63,9 @@ for i, plugin_id in ipairs(order.header) do
end
logger:log(ngx.INFO, "called header() methods of plugins")
-- Save ctx
ngx.ctx = ctx
return true
}

View file

@ -13,7 +13,7 @@ logger:log(ngx.INFO, "log phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -24,12 +24,11 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
order = cjson.decode(order)
-- Call log() methods
logger:log(ngx.INFO, "calling log() methods of plugins ...")
@ -44,7 +43,7 @@ for i, plugin_id in ipairs(order.log) do
-- Check if plugin has log method
if plugin_lua.log ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -65,10 +64,13 @@ end
logger:log(ngx.INFO, "called log() methods of plugins")
-- Display reason at info level
if ngx.ctx.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. ngx.ctx.reason)
if ctx.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. ctx.reason)
end
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "log phase ended")
}

View file

@ -28,7 +28,7 @@ end
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -39,12 +39,11 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
order = cjson.decode(order)
-- Call set() methods
logger:log(ngx.INFO, "calling set() methods of plugins ...")
@ -59,7 +58,7 @@ for i, plugin_id in ipairs(order.set) do
-- Check if plugin has set method
if plugin_lua.set ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -79,6 +78,9 @@ for i, plugin_id in ipairs(order.set) do
end
logger:log(ngx.INFO, "called set() methods of plugins")
-- Save ctx
ngx.ctx = ctx
return true
}

View file

@ -13,7 +13,7 @@ logger:log(ngx.INFO, "log phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -24,12 +24,11 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
order = cjson.decode(order)
-- Call log_stream() methods
logger:log(ngx.INFO, "calling log_stream() methods of plugins ...")
@ -44,7 +43,7 @@ for i, plugin_id in ipairs(order.log_stream) do
-- Check if plugin has log_stream method
if plugin_lua.log_stream ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -65,10 +64,13 @@ end
logger:log(ngx.INFO, "called log_stream() methods of plugins")
-- Display reason at info level
if ngx.ctx.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. ngx.ctx.reason)
if ctx.reason then
logger:log(ngx.INFO, "client was denied with reason : " .. ctx.reason)
end
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "log phase ended")
}

View file

@ -1,5 +1,5 @@
preread_by_lua_block {
ngx.ctx
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
@ -15,7 +15,7 @@ logger:log(ngx.INFO, "preread phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors = helpers.fill_ctx()
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
@ -26,27 +26,24 @@ end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Process bans as soon as possible
local banned, reason, ttl = utils.is_banned(ngx.ctx.bw.remote_addr)
if banned == nil then
logger:log(ngx.ERR, "can't check if IP " .. ngx.ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
logger:log(ngx.WARN, "IP " .. ngx.ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
return ngx.exit(utils.get_deny_status())
else
logger:log(ngx.INFO, "IP " .. ngx.ctx.bw.remote_addr .. " is not banned")
if ctx.bw.is_whitelisted ~= "yes" then
local banned, reason, ttl = utils.is_banned(ctx.bw.remote_addr)
if banned == nil then
logger:log(ngx.ERR, "can't check if IP " .. ctx.bw.remote_addr .. " is banned : " .. reason)
elseif banned then
logger:log(ngx.WARN, "IP " .. ctx.bw.remote_addr .. " is banned with reason " .. reason .. " (" .. tostring(ttl) .. "s remaining)")
return ngx.exit(utils.get_deny_status())
else
logger:log(ngx.INFO, "IP " .. ctx.bw.remote_addr .. " is not banned")
end
end
-- Get plugins order
local order, err = datastore:get("plugins_order")
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)
-- Call preread() methods
logger:log(ngx.INFO, "calling preread() methods of plugins ...")
@ -62,7 +59,7 @@ for i, plugin_id in ipairs(order.preread) do
-- Check if plugin has preread method
if plugin_lua.preread ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
@ -75,8 +72,8 @@ for i, plugin_id in ipairs(order.preread) do
logger:log(ngx.INFO, plugin_id .. ":preread() call successful : " .. ret.msg)
end
if ret.status then
if ret.status == utils.get_deny_status() then
ngx.ctx.reason = plugin_id
if ret.status == utils.get_deny_status(ctx) then
ctx.bw.reason = plugin_id
logger:log(ngx.WARN, "denied preread from " .. plugin_id .. " : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
@ -92,6 +89,9 @@ for i, plugin_id in ipairs(order.preread) do
end
logger:log(ngx.INFO, "called preread() methods of plugins")
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "preread phase ended")
-- Return status if needed

View file

@ -15,9 +15,9 @@ end
local antibot = class("antibot", plugin)
function antibot:initialize()
function antibot:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "antibot")
plugin.initialize(self, "antibot", ctx)
end
function antibot:access()
@ -27,18 +27,18 @@ function antibot:access()
end
-- Get session data
local session, err = utils.get_session("antibot")
local session, err = utils.get_session("antibot", self.ctx)
if not session then
return self:ret(false, "can't get session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
self.session = session
self.session_data = utils.get_session_data(self.session)
self.session_data = utils.get_session_data(self.session, self.ctx)
-- Check if session is valid
self:check_session()
-- Don't go further if client resolved the challenge
if self.session_data.resolved then
if ngx.ctx.bw.uri == self.variables["ANTIBOT_URI"] then
if self.ctx.bw.uri == self.variables["ANTIBOT_URI"] then
return self:ret(true, "client already resolved the challenge", nil, self.session_data.original_uri)
end
return self:ret(true, "client already resolved the challenge")
@ -52,7 +52,7 @@ function antibot:access()
end
-- Redirect to challenge page
if ngx.ctx.bw.uri ~= self.variables["ANTIBOT_URI"] then
if self.ctx.bw.uri ~= self.variables["ANTIBOT_URI"] then
return self:ret(true, "redirecting client to the challenge uri", nil, self.variables["ANTIBOT_URI"])
end
@ -62,13 +62,13 @@ function antibot:access()
end
-- Display challenge needed
if ngx.ctx.bw.request_method == "GET" then
ngx.ctx.bw.antibot_display_content = true
if self.ctx.bw.request_method == "GET" then
self.ctx.bw.antibot_display_content = true
return self:ret(true, "displaying challenge to client", ngx.OK)
end
-- Check challenge
if ngx.ctx.bw.request_method == "POST" then
if self.ctx.bw.request_method == "POST" then
local ok, err, redirect = self:check_challenge()
local set_ok, set_err = self:set_session_data()
if not set_ok then
@ -87,12 +87,12 @@ function antibot:access()
if not ok then
return self:ret(false, "can't save session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.ctx.bw.antibot_display_content = true
self.ctx.bw.antibot_display_content = true
return self:ret(true, "displaying challenge to client", ngx.OK)
end
-- Method is suspicious, let's deny the request
return self:ret(true, "unsupported HTTP method for antibot", utils.get_deny_status())
return self:ret(true, "unsupported HTTP method for antibot", utils.get_deny_status(self.ctx))
end
function antibot:content()
@ -102,17 +102,17 @@ function antibot:content()
end
-- Check if display content is needed
if not ngx.ctx.bw.antibot_display_content then
if not self.ctx.bw.antibot_display_content then
return self:ret(true, "display content not needed", nil, "/")
end
-- Get session data
local session, err = utils.get_session("antibot")
local session, err = utils.get_session("antibot", self.ctx)
if not session then
return self:ret(false, "can't get session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
self.session = session
self.session_data = utils.get_session_data(self.session)
self.session_data = utils.get_session_data(self.session, self.ctx)
-- Direct access without session
if not self.session_data.prepared then
@ -155,7 +155,7 @@ end
function antibot:set_session_data()
if self.session_updated then
local ok, err = utils.set_session_data(self.session, self.session_data)
local ok, err = utils.set_session_data(self.session, self.session_data, self.ctx)
if not ok then
return false, err
end
@ -172,8 +172,8 @@ function antibot:prepare_challenge()
self.session_data.time_resolve = ngx.now()
self.session_data.type = self.variables["USE_ANTIBOT"]
self.session_data.resolved = false
self.session_data.original_uri = ngx.ctx.bw.request_uri
if ngx.ctx.bw.uri == self.variables["ANTIBOT_URI"] then
self.session_data.original_uri = self.ctx.bw.request_uri
if self.ctx.bw.uri == self.variables["ANTIBOT_URI"] then
self.session_data.original_uri = "/"
end
if self.variables["USE_ANTIBOT"] == "cookie" then
@ -295,7 +295,7 @@ function antibot:check_challenge()
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_RECAPTCHA_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. ngx.ctx.bw.remote_addr,
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
@ -331,7 +331,7 @@ function antibot:check_challenge()
method = "POST",
body = "secret=" ..
self.variables["ANTIBOT_HCAPTCHA_SECRET"] ..
"&response=" .. args["token"] .. "&remoteip=" .. ngx.ctx.bw.remote_addr,
"&response=" .. args["token"] .. "&remoteip=" .. self.ctx.bw.remote_addr,
headers = {
["Content-Type"] = "application/x-www-form-urlencoded"
}
@ -366,7 +366,7 @@ function antibot:check_challenge()
local data = {
secret=self.variables["ANTIBOT_TURNSTILE_SECRET"],
response=args["token"],
remoteip=ngx.ctx.bw.remote_addr
remoteip=self.ctx.bw.remote_addr
}
local res, err = httpc:request_uri("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method = "POST",

View file

@ -3,17 +3,24 @@ location {{ ANTIBOT_URI }} {
default_type 'text/html';
root /usr/share/bunkerweb/core/antibot/files;
content_by_lua_block {
local cantibot = require "antibot.antibot"
local clogger = require "bunkerweb.logger"
local antibot = cantibot:new()
local logger = clogger:new("ANTIBOT")
local ret = antibot:content()
local logger = require "bunkerweb.logger":new("ANTIBOT")
local helpers = require "bunkerweb.helpers"
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
for i, error in ipairs(errors) do
logger:log(ngx.ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
end
end
local antibot = require "antibot.antibot":new(ctx)
local ret = antibot:content()
if not ret.ret then
logger:log(ngx.ERR, "antibot:content() failed : " .. ret.msg)
else
logger:log(ngx.INFO, "antibot:content() success : " .. ret.msg)
end
ngx.ctx = ctx
}
}
{% endif %}

View file

@ -4,14 +4,14 @@ local utils = require "bunkerweb.utils"
local badbehavior = class("badbehavior", plugin)
function badbehavior:initialize()
function badbehavior:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "badbehavior")
plugin.initialize(self, "badbehavior", ctx)
end
function badbehavior:log()
-- Check if we are whitelisted
if ngx.ctx.bw.is_whitelisted == "yes" then
if self.ctx.bw.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Check if bad behavior is activated
@ -23,12 +23,12 @@ 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_" .. ngx.ctx.bw.remote_addr)
local banned, err = 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, ngx.ctx.bw.remote_addr,
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

View file

@ -8,17 +8,17 @@ local ipmatcher = require "resty.ipmatcher"
local blacklist = class("blacklist", plugin)
function blacklist:initialize()
function blacklist:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "blacklist")
plugin.initialize(self, "blacklist", ctx)
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_blacklist_lists")
local lists, err = self.datastore:get("plugin_blacklist_lists", true)
if not lists then
self.logger:log(ngx.ERR, err)
self.lists = {}
else
self.lists = cjson.decode(lists)
self.lists = lists
end
local kinds = {
["IP"] = {},
@ -49,7 +49,7 @@ function blacklist:is_needed()
return false
end
-- Request phases (no default)
if self.is_request and (ngx.ctx.bw.server_name ~= "_") then
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_BLACKLIST"] == "yes"
end
-- Other cases : at least one service uses it
@ -91,7 +91,7 @@ function blacklist:init()
end
end
-- Load them into datastore
local ok, err = self.datastore:set("plugin_blacklist_lists", cjson.encode(blacklists))
local ok, err = self.datastore:set("plugin_blacklist_lists", blacklists, nil, true)
if not ok then
return self:ret(false, "can't store blacklist list into datastore : " .. err)
end
@ -105,13 +105,13 @@ function blacklist:access()
end
-- Check the caches
local checks = {
["IP"] = "ip" .. ngx.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr
}
if ngx.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. ngx.ctx.bw.http_user_agent
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
end
if ngx.ctx.bw.uri then
checks["URI"] = "uri" .. ngx.ctx.bw.uri
if self.ctx.bw.uri then
checks["URI"] = "uri" .. self.ctx.bw.uri
end
local already_cached = {
["IP"] = false,
@ -123,7 +123,7 @@ function blacklist:access()
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())
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
@ -145,7 +145,7 @@ function blacklist:access()
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())
return self:ret(true, k .. " is blacklisted (info : " .. blacklisted .. ")", utils.get_deny_status(self.ctx))
end
end
end
@ -161,16 +161,16 @@ end
function blacklist:kind_to_ele(kind)
if kind == "IP" then
return "ip" .. ngx.ctx.bw.remote_addr
return "ip" .. self.ctx.bw.remote_addr
elseif kind == "UA" then
return "ua" .. ngx.ctx.bw.http_user_agent
return "ua" .. self.ctx.bw.http_user_agent
elseif kind == "URI" then
return "uri" .. ngx.ctx.bw.uri
return "uri" .. self.ctx.bw.uri
end
end
function blacklist:is_in_cache(ele)
local ok, data = self.cachestore:get("plugin_blacklist_" .. ngx.ctx.bw.server_name .. ele)
local ok, data = self.cachestore:get("plugin_blacklist_" .. self.ctx.bw.server_name .. ele)
if not ok then
return false, data
end
@ -178,7 +178,7 @@ function blacklist:is_in_cache(ele)
end
function blacklist:add_to_cache(ele, value)
local ok, err = self.cachestore:set("plugin_blacklist_" .. ngx.ctx.bw.server_name .. ele, value, 86400)
local ok, err = self.cachestore:set("plugin_blacklist_" .. self.ctx.bw.server_name .. ele, value, 86400)
if not ok then
return false, err
end
@ -202,7 +202,7 @@ function blacklist:is_blacklisted_ip()
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.ctx.bw.remote_addr)
local match, err = ipm:match(self.ctx.bw.remote_addr)
if err then
return nil, err
end
@ -212,7 +212,7 @@ function blacklist:is_blacklisted_ip()
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.ctx.bw.remote_addr)
local match, err = ipm:match(self.ctx.bw.remote_addr)
if err then
return nil, err
end
@ -223,12 +223,12 @@ function blacklist:is_blacklisted_ip()
-- Check if rDNS is needed
local check_rdns = true
if self.variables["BLACKLIST_RDNS_GLOBAL"] == "yes" and not ngx.ctx.bw.ip_is_global then
if self.variables["BLACKLIST_RDNS_GLOBAL"] == "yes" and not self.ctx.bw.ip_is_global then
check_rdns = false
end
if check_rdns then
-- Get rDNS
local rdns_list, err = utils.get_rdns(ngx.ctx.bw.remote_addr)
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
@ -256,10 +256,10 @@ function blacklist:is_blacklisted_ip()
end
-- Check if ASN is in ignore list
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if self.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(self.ctx.bw.remote_addr)
if not asn then
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
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
@ -287,7 +287,7 @@ 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
if utils.regex_match(ngx.ctx.bw.uri, ignore_uri) then
if utils.regex_match(self.ctx.bw.uri, ignore_uri) then
ignore = true
break
end
@ -295,7 +295,7 @@ function blacklist:is_blacklisted_uri()
-- Check if URI is in blacklist
if not ignore then
for i, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(ngx.ctx.bw.uri, uri) then
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
end
@ -308,7 +308,7 @@ 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
if utils.regex_match(ngx.ctx.bw.http_user_agent, ignore_ua) then
if utils.regex_match(self.ctx.bw.http_user_agent, ignore_ua) then
ignore = true
break
end
@ -316,7 +316,7 @@ function blacklist:is_blacklisted_ua()
-- Check if UA is in blacklist
if not ignore then
for i, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(ngx.ctx.bw.http_user_agent, ua) then
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end
end

View file

@ -7,16 +7,16 @@ local http = require "resty.http"
local bunkernet = class("bunkernet", plugin)
function bunkernet:initialize()
function bunkernet:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "bunkernet")
plugin.initialize(self, "bunkernet", ctx)
-- Get BunkerNet ID and save info
if ngx.get_phase() ~= "init" and self:is_needed() then
local id, err = self.datastore:get("plugin_bunkernet_id")
local id, err = self.datastore:get("plugin_bunkernet_id", true)
if id then
self.bunkernet_id = id
self.version = (ngx.ctx.bw and ngx.ctx.bw.version) or utils.get_version()
self.integration = (ngx.ctx.bw and ngx.ctx.bw.integration) or utils.get_integration()
self.version = (self.ctx and self.ctx.bw.version) or utils.get_version()
self.integration = (self.ctx and self.ctx.bw.integration) or utils.get_integration()
else
self.logger:log(ngx.ERR, "can't get BunkerNet ID from datastore : " .. err)
end
@ -29,7 +29,7 @@ function bunkernet:is_needed()
return false
end
-- Request phases (no default)
if self.is_request and (ngx.ctx.bw.server_name ~= "_") then
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_BUNKERNET"] == "yes"
end
-- Other cases : at least one service uses it
@ -75,7 +75,7 @@ function bunkernet:init()
local id = f:read("*all"):gsub("[\r\n]", "")
f:close()
-- Store ID in datastore
local ok, err = self.datastore:set("plugin_bunkernet_id", id)
local ok, err = self.datastore:set("plugin_bunkernet_id", id, nil, true)
if not ok then
return self:ret(false, "can't save instance ID to the datastore : " .. err)
end
@ -100,7 +100,7 @@ function bunkernet:init()
return self:ret(false, "error while reading database : " .. err)
end
f:close()
local ok, err = self.datastore:set("plugin_bunkernet_db", cjson.encode(db))
local ok, err = self.datastore:set("plugin_bunkernet_db", db, nil, true)
if not ok then
return self:ret(false, "can't store bunkernet database into datastore : " .. err)
end
@ -117,25 +117,24 @@ function bunkernet:access()
return self:ret(false, "missing instance ID")
end
-- Check if IP is global
if not ngx.ctx.bw.ip_is_global then
if not self.ctx.bw.ip_is_global then
return self:ret(true, "IP is not global")
end
-- Check if whitelisted
if ngx.ctx.bw.is_whitelisted == "yes" then
if self.ctx.bw.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Extract DB
local db, err = self.datastore:get("plugin_bunkernet_db")
local db, err = self.datastore:get("plugin_bunkernet_db", true)
if db then
db = cjson.decode(db)
-- Check if is IP is present
if #db.ip > 0 then
local present, err = utils.is_ip_in_networks(ngx.ctx.bw.remote_addr, db.ip)
local present, err = utils.is_ip_in_networks(self.ctx.bw.remote_addr, db.ip)
if present == nil then
return self:ret(false, "can't check if ip is in db : " .. err)
end
if present then
return self:ret(true, "ip is in db", utils.get_deny_status())
return self:ret(true, "ip is in db", utils.get_deny_status(self.ctx))
end
end
else
@ -156,7 +155,7 @@ function bunkernet:log(bypass_checks)
end
end
-- Check if IP has been blocked
local reason = utils.get_reason()
local reason = utils.get_reason(self.ctx)
if not reason then
return self:ret(true, "ip is not blocked")
end
@ -164,7 +163,7 @@ function bunkernet:log(bypass_checks)
return self:ret(true, "skipping report because the reason is bunkernet")
end
-- Check if IP is global
if not ngx.ctx.bw.ip_is_global then
if not self.ctx.bw.ip_is_global then
return self:ret(true, "IP is not global")
end
-- TODO : check if IP has been reported recently
@ -179,8 +178,8 @@ function bunkernet:log(bypass_checks)
end
end
local hdr, err = ngx.timer.at(0, report_callback, self, ngx.ctx.bw.remote_addr, reason, ngx.ctx.bw.request_method,
ngx.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

View file

@ -4,9 +4,9 @@ local utils = require "bunkerweb.utils"
local cors = class("cors", plugin)
function cors:initialize()
function cors:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "cors")
plugin.initialize(self, "cors", ctx)
self.all_headers = {
["CORS_EXPOSE_HEADERS"] = "Access-Control-Expose-Headers"
}
@ -24,7 +24,7 @@ function cors:header()
return self:ret(true, "service doesn't use CORS")
end
-- Skip if Origin header is not present
if not ngx.ctx.bw.http_origin then
if not self.ctx.bw.http_origin then
return self:ret(true, "origin header not present")
end
-- Always include Vary header to prevent caching
@ -40,22 +40,22 @@ function cors:header()
ngx.header.Vary = "Origin"
end
-- Check if Origin is allowed
if ngx.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(ngx.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
self.logger:log(ngx.WARN, "origin " .. ngx.ctx.bw.http_origin .. " is not allowed")
return self:ret(true, "origin " .. ngx.ctx.bw.http_origin .. " is not 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
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
-- Set headers
if self.variables["CORS_ALLOW_ORIGIN"] == "*" then
ngx.header["Access-Control-Allow-Origin"] = "*"
else
ngx.header["Access-Control-Allow-Origin"] = ngx.ctx.bw.http_origin
ngx.header["Access-Control-Allow-Origin"] = self.ctx.bw.http_origin
end
for variable, header in pairs(self.all_headers) do
if self.variables[variable] ~= "" then
ngx.header[header] = self.variables[variable]
end
end
if ngx.ctx.bw.request_method == "OPTIONS" then
if self.ctx.bw.request_method == "OPTIONS" then
for variable, header in pairs(self.preflight_headers) do
if variable == "CORS_ALLOW_CREDENTIALS" then
if self.variables["CORS_ALLOW_CREDENTIALS"] == "yes" then
@ -78,11 +78,11 @@ function cors:access()
return self:ret(true, "service doesn't use CORS")
end
-- Deny as soon as possible if needed
if ngx.ctx.bw.http_origin and self.variables["CORS_DENY_REQUEST"] == "yes" and self.variables["CORS_ALLOW_ORIGIN"] ~= "*" and not utils.regex_match(ngx.ctx.bw.http_origin, self.variables["CORS_ALLOW_ORIGIN"]) then
return self:ret(true, "origin " .. ngx.ctx.bw.http_origin .. " is not allowed, denying access", utils.get_deny_status())
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 ngx.ctx.bw.request_method == "OPTIONS" and ngx.ctx.bw.http_origin then
if self.ctx.bw.request_method == "OPTIONS" and self.ctx.bw.http_origin then
return self:ret(true, "preflight request", ngx.HTTP_NO_CONTENT)
end
return self:ret(true, "standard request")

View file

@ -6,9 +6,9 @@ local cjson = require "cjson"
local country = class("country", plugin)
function country:initialize()
function country:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "country")
plugin.initialize(self, "country", ctx)
end
function country:access()
@ -17,73 +17,73 @@ function country:access()
return self:ret(true, "country not activated")
end
-- Check if IP is in cache
local ok, data = self:is_in_cache(ngx.ctx.bw.remote_addr)
local ok, 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 " ..
ngx.ctx.bw.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")")
self.ctx.bw.remote_addr .. " is in country cache (not blacklisted, country = " .. data.country .. ")")
end
return self:ret(true,
"client IP " .. ngx.ctx.bw.remote_addr .. " is in country cache (blacklisted, country = " .. data.country .. ")",
utils.get_deny_status())
"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
if not ngx.ctx.bw.ip_is_global then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, "unknown", "ok")
if not self.ctx.bw.ip_is_global then
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, "unknown", "ok")
if not ok then
return self:ret(false, "error while adding ip to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is not global, skipping check")
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not global, skipping check")
end
-- Get the country of client
local country, err = utils.get_country(ngx.ctx.bw.remote_addr)
local country, err = utils.get_country(self.ctx.bw.remote_addr)
if not country then
return self:ret(false, "can't get country of client IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
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(ngx.ctx.bw.remote_addr, country, "ok")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is whitelisted (country = " .. country .. ")")
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is whitelisted (country = " .. country .. ")")
end
end
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, country, "ko")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country .. ")",
utils.get_deny_status())
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is not whitelisted (country = " .. country .. ")",
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(ngx.ctx.bw.remote_addr, country, "ko")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ko")
if not ok then
return self:ret(false, "error while adding item to cache : " .. err)
end
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is blacklisted (country = " .. country .. ")",
utils.get_deny_status())
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is blacklisted (country = " .. country .. ")",
utils.get_deny_status(self.ctx))
end
end
end
-- Country IP is not in blacklist
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, country, "ok")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, country, "ok")
if not ok then
return self:ret(false, "error while caching IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
return self:ret(false, "error while caching IP " .. self.ctx.bw.remote_addr .. " : " .. err)
end
return self:ret(true, "client IP " .. ngx.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 .. ")")
end
function country:preread()
@ -91,7 +91,7 @@ function country:preread()
end
function country:is_in_cache(ip)
local ok, data = self.cachestore:get("plugin_country_" .. ngx.ctx.bw.server_name .. ip)
local ok, data = self.cachestore:get("plugin_country_" .. self.ctx.bw.server_name .. ip)
if not ok then
return false, data
end
@ -99,7 +99,7 @@ function country:is_in_cache(ip)
end
function country:add_to_cache(ip, country, result)
local ok, err = self.cachestore:set("plugin_country_" .. ngx.ctx.bw.server_name .. ip,
local ok, err = self.cachestore:set("plugin_country_" .. self.ctx.bw.server_name .. ip,
cjson.encode({ country = country, result = result }), 86400)
if not ok then
return false, err

View file

@ -7,9 +7,9 @@ local resolver = require "resty.dns.resolver"
local dnsbl = class("dnsbl", plugin)
function dnsbl:initialize()
function dnsbl:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "dnsbl")
plugin.initialize(self, "dnsbl", ctx)
end
function dnsbl:init_worker()
@ -56,25 +56,25 @@ function dnsbl:access()
return self:ret(true, "dnsbl list is empty")
end
-- Don't go further if IP is not global
if not ngx.ctx.bw.ip_is_global then
if not self.ctx.bw.ip_is_global then
return self:ret(true, "client IP is not global, skipping DNSBL check")
end
-- Check if IP is in cache
local ok, cached = self:is_in_cache(ngx.ctx.bw.remote_addr)
local ok, cached = self:is_in_cache(self.ctx.bw.remote_addr)
if not ok then
return self:ret(false, "error while checking cache : " .. cached)
elseif cached then
if cached == "ok" then
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
return self:ret(true, "client IP " .. self.ctx.bw.remote_addr .. " is in DNSBL cache (not blacklisted)")
end
return self:ret(true, "client IP " .. ngx.ctx.bw.remote_addr .. " is in DNSBL cache (server = " .. cached .. ")",
utils.get_deny_status())
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, ngx.ctx.bw.remote_addr, server)
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, self.ctx.bw.remote_addr, server)
threads[server] = thread
end
-- Wait for threads
@ -124,17 +124,17 @@ function dnsbl:access()
end
-- Blacklisted by a server : add to cache and deny access
if ret_threads then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, ret_server)
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, ret_server)
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
return self:ret(true, "IP is blacklisted by " .. ret_server, utils.get_deny_status())
return self:ret(true, "IP is blacklisted by " .. ret_server, utils.get_deny_status(self.ctx))
end
-- Error case
return self:ret(false, ret_err)
end
-- IP is not in DNSBL
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, "ok")
local ok, err = self:add_to_cache(self.ctx.bw.remote_addr, "ok")
if not ok then
return self:ret(false, "IP is not in DNSBL (error = " .. err .. ")")
end
@ -146,7 +146,7 @@ function dnsbl:preread()
end
function dnsbl:is_in_cache(ip)
local ok, data = self.cachestore:get("plugin_dnsbl_" .. ngx.ctx.bw.server_name .. ip)
local ok, data = self.cachestore:get("plugin_dnsbl_" .. self.ctx.bw.server_name .. ip)
if not ok then
return false, data
end
@ -154,7 +154,7 @@ function dnsbl:is_in_cache(ip)
end
function dnsbl:add_to_cache(ip, value)
local ok, err = self.cachestore:set("plugin_dnsbl_" .. ngx.ctx.bw.server_name .. ip, value, 86400)
local ok, err = self.cachestore:set("plugin_dnsbl_" .. self.ctx.bw.server_name .. ip, value, 86400)
if not ok then
return false, err
end

View file

@ -9,9 +9,9 @@ end
local errors = class("errors", plugin)
function errors:initialize()
function errors:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "errors")
plugin.initialize(self, "errors", ctx)
-- Default error texts
self.default_errors = {
["400"] = {

View file

@ -7,17 +7,17 @@ local ipmatcher = require "resty.ipmatcher"
local greylist = class("greylist", plugin)
function greylist:initialize()
function greylist:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "greylist")
plugin.initialize(self, "greylist", ctx)
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_greylist_lists")
local lists, err = self.datastore:get("plugin_greylist_lists", true)
if not lists then
self.logger:log(ngx.ERR, err)
self.lists = {}
else
self.lists = cjson.decode(lists)
self.lists = lists
end
local kinds = {
["IP"] = {},
@ -43,7 +43,7 @@ function greylist:is_needed()
return false
end
-- Request phases (no default)
if self.is_request and (ngx.ctx.bw.server_name ~= "_") then
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_GREYLIST"] == "yes"
end
-- Other cases : at least one service uses it
@ -79,7 +79,7 @@ function greylist:init()
end
end
-- Load them into datastore
local ok, err = self.datastore:set("plugin_greylist_lists", cjson.encode(greylists))
local ok, err = self.datastore:set("plugin_greylist_lists", greylists, nil, true)
if not ok then
return self:ret(false, "can't store greylist list into datastore : " .. err)
end
@ -93,13 +93,13 @@ function greylist:access()
end
-- Check the caches
local checks = {
["IP"] = "ip" .. ngx.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr
}
if ngx.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. ngx.ctx.bw.http_user_agent
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
end
if ngx.ctx.bw.uri then
checks["URI"] = "uri" .. ngx.ctx.bw.uri
if self.ctx.bw.uri then
checks["URI"] = "uri" .. self.ctx.bw.uri
end
local already_cached = {
["IP"] = false,
@ -140,7 +140,7 @@ function greylist:access()
end
-- Return
return self:ret(true, "not in greylist", utils.get_deny_status())
return self:ret(true, "not in greylist", utils.get_deny_status(self.ctx))
end
function greylist:preread()
@ -149,11 +149,11 @@ end
function greylist:kind_to_ele(kind)
if kind == "IP" then
return "ip" .. ngx.ctx.bw.remote_addr
return "ip" .. self.ctx.bw.remote_addr
elseif kind == "UA" then
return "ua" .. ngx.ctx.bw.http_user_agent
return "ua" .. self.ctx.bw.http_user_agent
elseif kind == "URI" then
return "uri" .. ngx.ctx.bw.uri
return "uri" .. self.ctx.bw.uri
end
end
@ -174,7 +174,7 @@ function greylist:is_greylisted_ip()
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.ctx.bw.remote_addr)
local match, err = ipm:match(self.ctx.bw.remote_addr)
if err then
return nil, err
end
@ -184,12 +184,12 @@ function greylist:is_greylisted_ip()
-- Check if rDNS is needed
local check_rdns = true
if self.variables["GREYLIST_RDNS_GLOBAL"] == "yes" and not ngx.ctx.bw.ip_is_global then
if self.variables["GREYLIST_RDNS_GLOBAL"] == "yes" and not self.ctx.bw.ip_is_global then
check_rdns = false
end
if check_rdns then
-- Get rDNS
local rdns_list, err = utils.get_rdns(ngx.ctx.bw.remote_addr)
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
@ -205,10 +205,10 @@ function greylist:is_greylisted_ip()
end
-- Check if ASN is in greylist
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if self.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(self.ctx.bw.remote_addr)
if not asn then
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
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
if bl_asn == tostring(asn) then
@ -225,7 +225,7 @@ end
function greylist:is_greylisted_uri()
-- Check if URI is in greylist
for i, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(ngx.ctx.bw.uri, uri) then
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
end
@ -236,7 +236,7 @@ end
function greylist:is_greylisted_ua()
-- Check if UA is in greylist
for i, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(ngx.ctx.bw.http_user_agent, ua) then
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end
end
@ -245,7 +245,7 @@ function greylist:is_greylisted_ua()
end
function greylist:is_in_cache(ele)
local ok, data = self.cachestore:get("plugin_greylist_" .. ngx.ctx.bw.server_name .. ele)
local ok, data = self.cachestore:get("plugin_greylist_" .. self.ctx.bw.server_name .. ele)
if not ok then
return false, data
end
@ -253,7 +253,7 @@ function greylist:is_in_cache(ele)
end
function greylist:add_to_cache(ele, value)
local ok, err = self.cachestore:set("plugin_greylist_" .. ngx.ctx.bw.server_name .. ele, value, 86400)
local ok, err = self.cachestore:set("plugin_greylist_" .. self.ctx.bw.server_name .. ele, value, 86400)
if not ok then
return false, err
end

View file

@ -11,7 +11,7 @@ function letsencrypt:initialize()
end
function letsencrypt:access()
if string.sub(ngx.ctx.bw.uri, 1, string.len("/.well-known/acme-challenge/")) == "/.well-known/acme-challenge/" then
if string.sub(self.ctx.bw.uri, 1, string.len("/.well-known/acme-challenge/")) == "/.well-known/acme-challenge/" then
self.logger:log(ngx.NOTICE, "got a visit from Let's Encrypt, let's whitelist it")
return self:ret(true, "visit from LE", ngx.OK)
end
@ -19,8 +19,8 @@ function letsencrypt:access()
end
function letsencrypt:api()
if not string.match(ngx.ctx.bw.uri, "^/lets%-encrypt/challenge$") or
(ngx.ctx.bw.request_method ~= "POST" and ngx.ctx.bw.request_method ~= "DELETE") then
if not string.match(self.ctx.bw.uri, "^/lets%-encrypt/challenge$") or
(self.ctx.bw.request_method ~= "POST" and self.ctx.bw.request_method ~= "DELETE") then
return false, nil, nil
end
local acme_folder = "/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/"
@ -30,7 +30,7 @@ function letsencrypt:api()
return true, ngx.HTTP_BAD_REQUEST, { status = "error", msg = "json body decoding failed" }
end
os.execute("mkdir -p " .. acme_folder)
if ngx.ctx.bw.request_method == "POST" then
if self.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 }
@ -38,7 +38,7 @@ function letsencrypt:api()
file:write(data.validation)
file:close()
return true, ngx.HTTP_OK, { status = "success", msg = "validation token written" }
elseif ngx.ctx.bw.request_method == "DELETE" then
elseif self.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 }

View file

@ -14,12 +14,11 @@ function limit:initialize()
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")
local all_rules, err = self.datastore:get("plugin_limit_rules", true)
if not all_rules then
self.logger:log(ngx.ERR, err)
return
end
all_rules = cjson.decode(all_rules)
self.rules = {}
-- Extract global rules
if all_rules.global then
@ -28,8 +27,8 @@ function limit:initialize()
end
end
-- Extract and overwrite if needed server rules
if all_rules[ngx.ctx.bw.server_name] then
for k, v in pairs(all_rules[ngx.ctx.bw.server_name]) do
if all_rules[self.ctx.bw.server_name] then
for k, v in pairs(all_rules[self.ctx.bw.server_name]) do
self.rules[k] = v
end
end
@ -42,7 +41,7 @@ function limit:is_needed()
return false
end
-- Request phases (no default)
if self.is_request and (ngx.ctx.bw.server_name ~= "_") then
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_LIMIT_REQ"] == "yes"
end
-- Other cases : at least one service uses it
@ -63,6 +62,7 @@ function limit:init()
if variables == nil then
return self:ret(false, err)
end
self.logger:log(ngx.ERR, cjson.encode(variables))
-- Store URLs and rates
local data = {}
local i = 0
@ -79,7 +79,7 @@ function limit:init()
end
end
end
local ok, err = self.datastore:set("plugin_limit_rules", cjson.encode(data))
local ok, err = self.datastore:set("plugin_limit_rules", data, nil, true)
if not ok then
return self:ret(false, err)
end
@ -88,7 +88,7 @@ end
function limit:access()
-- Check if we are whitelisted
if ngx.ctx.bw.is_whitelisted == "yes" then
if self.ctx.bw.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Check if access is needed
@ -99,7 +99,7 @@ function limit:access()
local rate = nil
local uri = nil
for k, v in pairs(self.rules) do
if k ~= "/" and utils.regex_match(ngx.ctx.bw.uri, k) then
if k ~= "/" and utils.regex_match(self.ctx.bw.uri, k) then
rate = v
uri = k
break
@ -110,7 +110,7 @@ function limit:access()
rate = self.rules["/"]
uri = "/"
else
return self:ret(true, "no rule for " .. ngx.ctx.bw.uri)
return self:ret(true, "no rule for " .. self.ctx.bw.uri)
end
end
-- Check if limit is reached
@ -123,17 +123,17 @@ function limit:access()
if limited then
return self:ret(true,
"client IP " ..
ngx.ctx.bw.remote_addr ..
self.ctx.bw.remote_addr ..
" is limited for URL " ..
ngx.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")",
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 " ..
ngx.ctx.bw.remote_addr ..
self.ctx.bw.remote_addr ..
" is not limited for URL " ..
ngx.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")")
self.ctx.bw.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")")
end
function limit:limit_req(rate_max, rate_time)
@ -147,7 +147,7 @@ function limit:limit_req(rate_max, rate_time)
timestamps = redis_timestamps
-- Save the new timestamps
local ok, err = self.datastore:set(
"plugin_limit_" .. ngx.ctx.bw.server_name .. ngx.ctx.bw.remote_addr .. ngx.ctx.bw.uri,
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
cjson.encode(timestamps), delay)
if not ok then
return nil, "can't update timestamps : " .. err
@ -171,7 +171,7 @@ end
function limit:limit_req_local(rate_max, rate_time)
-- Get timestamps
local timestamps, err = self.datastore:get("plugin_limit_" ..
ngx.ctx.bw.server_name .. ngx.ctx.bw.remote_addr .. ngx.ctx.bw.uri)
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
@ -183,7 +183,7 @@ function limit:limit_req_local(rate_max, rate_time)
-- Save new timestamps if needed
if updated then
local ok, err = self.datastore:set(
"plugin_limit_" .. ngx.ctx.bw.server_name .. ngx.ctx.bw.remote_addr .. ngx.ctx.bw.uri,
"plugin_limit_" .. self.ctx.bw.server_name .. self.ctx.bw.remote_addr .. self.ctx.bw.uri,
cjson.encode(new_timestamps), delay)
if not ok then
return nil, err
@ -249,7 +249,7 @@ function limit:limit_req_redis(rate_max, rate_time)
end
-- Execute script
local timestamps, err = self.clusterstore:call("eval", redis_script, 1,
"plugin_limit_" .. ngx.ctx.bw.server_name .. ngx.ctx.bw.remote_addr .. ngx.ctx.bw.uri, rate_max, rate_time,
"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()

View file

@ -11,7 +11,7 @@ end
function misc:access()
-- Check if method is valid
local method = ngx.ctx.bw.request_method
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

View file

@ -22,7 +22,7 @@ function reversescan:access()
local ret_err = nil
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
-- Check if the scan is already cached
local ok, cached = self:is_in_cache(ngx.ctx.bw.remote_addr .. ":" .. port)
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
@ -30,11 +30,11 @@ function reversescan:access()
-- Deny access if port opened
elseif cached == "open" then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
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, ngx.ctx.bw.remote_addr, tonumber(port), tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
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
@ -48,7 +48,7 @@ function reversescan:access()
end
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
return self:ret(true, ret_err, utils.get_deny_status(self.ctx))
end
-- Error case
return self:ret(false, ret_err)
@ -87,7 +87,7 @@ function reversescan:access()
-- Port is opened
if open then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
ret_err = "port " .. port .. " is opened for IP " .. self.ctx.bw.remote_addr
break
end
end
@ -101,7 +101,7 @@ function reversescan:access()
end
-- Cache results
for port, result in pairs(results) do
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr .. ":" .. port, result)
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
@ -109,13 +109,13 @@ function reversescan:access()
if ret_threads ~= nil then
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
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 " .. ngx.ctx.bw.remote_addr)
return self:ret(true, "no port open for IP " .. self.ctx.bw.remote_addr)
end
function reversescan:preread()

View file

@ -29,13 +29,13 @@ function sessions:set()
return self:ret(true, "set not needed")
end
local checks = {
["IP"] = ngx.ctx.bw.remote_addr,
["USER_AGENT"] = ngx.ctx.bw.http_user_agent or ""
["IP"] = self.ctx.bw.remote_addr,
["USER_AGENT"] = self.ctx.bw.http_user_agent or ""
}
ngx.ctx.bw.sessions_checks = {}
self.ctx.bw.sessions_checks = {}
for check, value in pairs(checks) do
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
table.insert(ngx.ctx.bw.sessions_checks, {check, value})
table.insert(self.ctx.bw.sessions_checks, {check, value})
end
end
return self:ret(true, "success")

View file

@ -14,12 +14,12 @@ function whitelist:initialize()
plugin.initialize(self, "whitelist")
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_whitelist_lists")
local lists, err = self.datastore:get("plugin_whitelist_lists", true)
if not lists then
self.logger:log(ngx.ERR, err)
self.lists = {}
else
self.lists = cjson.decode(lists)
self.lists = lists
end
local kinds = {
["IP"] = {},
@ -45,7 +45,7 @@ function whitelist:is_needed()
return false
end
-- Request phases (no default)
if self.is_request and (ngx.ctx.bw.server_name ~= "_") then
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_WHITELIST"] == "yes"
end
-- Other cases : at least one service uses it
@ -81,7 +81,7 @@ function whitelist:init()
end
end
-- Load them into datastore
local ok, err = self.datastore:set("plugin_whitelist_lists", cjson.encode(whitelists))
local ok, err = self.datastore:set("plugin_whitelist_lists", whitelists, nil, true)
if not ok then
return self:ret(false, "can't store whitelist list into datastore : " .. err)
end
@ -91,7 +91,7 @@ end
function whitelist:set()
-- Set default value
ngx.var.is_whitelisted = "no"
ngx.ctx.bw.is_whitelisted = "no"
self.ctx.bw.is_whitelisted = "no"
env.set("is_whitelisted", "no")
-- Check if set is needed
if not self:is_needed() then
@ -103,7 +103,7 @@ function whitelist:set()
return self:ret(false, err)
elseif whitelisted then
ngx.var.is_whitelisted = "yes"
ngx.ctx.bw.is_whitelisted = "yes"
self.ctx.bw.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, err)
end
@ -121,7 +121,7 @@ function whitelist:access()
return self:ret(false, err)
elseif whitelisted then
ngx.var.is_whitelisted = "yes"
ngx.ctx.bw.is_whitelisted = "yes"
self.ctx.bw.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, err, ngx.OK)
end
@ -138,7 +138,7 @@ function whitelist:access()
end
if whitelisted ~= "ok" then
ngx.var.is_whitelisted = "yes"
ngx.ctx.bw.is_whitelisted = "yes"
self.ctx.bw.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, k .. " is whitelisted (info : " .. whitelisted .. ")", ngx.OK)
end
@ -155,24 +155,24 @@ end
function whitelist:kind_to_ele(kind)
if kind == "IP" then
return "ip" .. ngx.ctx.bw.remote_addr
return "ip" .. self.ctx.bw.remote_addr
elseif kind == "UA" then
return "ua" .. ngx.ctx.bw.http_user_agent
return "ua" .. self.ctx.bw.http_user_agent
elseif kind == "URI" then
return "uri" .. ngx.ctx.bw.uri
return "uri" .. self.ctx.bw.uri
end
end
function whitelist:check_cache()
-- Check the caches
local checks = {
["IP"] = "ip" .. ngx.ctx.bw.remote_addr
["IP"] = "ip" .. self.ctx.bw.remote_addr
}
if ngx.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. ngx.ctx.bw.http_user_agent
if self.ctx.bw.http_user_agent then
checks["UA"] = "ua" .. self.ctx.bw.http_user_agent
end
if ngx.ctx.bw.uri then
checks["URI"] = "uri" .. ngx.ctx.bw.uri
if self.ctx.bw.uri then
checks["URI"] = "uri" .. self.ctx.bw.uri
end
local already_cached = {}
for k, v in pairs(checks) do
@ -198,7 +198,7 @@ function whitelist:check_cache()
end
function whitelist:is_in_cache(ele)
local ok, data = self.cachestore:get("plugin_whitelist_" .. ngx.ctx.bw.server_name .. ele)
local ok, data = self.cachestore:get("plugin_whitelist_" .. self.ctx.bw.server_name .. ele)
if not ok then
return false, data
end
@ -206,7 +206,7 @@ function whitelist:is_in_cache(ele)
end
function whitelist:add_to_cache(ele, value)
local ok, err = self.cachestore:set("plugin_whitelist_" .. ngx.ctx.bw.server_name .. ele, value, 86400)
local ok, err = self.cachestore:set("plugin_whitelist_" .. self.ctx.bw.server_name .. ele, value, 86400)
if not ok then
return false, err
end
@ -230,7 +230,7 @@ function whitelist:is_whitelisted_ip()
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.ctx.bw.remote_addr)
local match, err = ipm:match(self.ctx.bw.remote_addr)
if err then
return nil, err
end
@ -240,12 +240,12 @@ function whitelist:is_whitelisted_ip()
-- Check if rDNS is needed
local check_rdns = true
if self.variables["WHITELIST_RDNS_GLOBAL"] == "yes" and not ngx.ctx.bw.ip_is_global then
if self.variables["WHITELIST_RDNS_GLOBAL"] == "yes" and not self.ctx.bw.ip_is_global then
check_rdns = false
end
if check_rdns then
-- Get rDNS
local rdns_list, err = utils.get_rdns(ngx.ctx.bw.remote_addr)
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
@ -266,11 +266,11 @@ function whitelist:is_whitelisted_ip()
local ip_list, err = utils.get_ips(forward_check)
if ip_list then
for i, ip in ipairs(ip_list) do
if ip == ngx.ctx.bw.remote_addr then
if ip == self.ctx.bw.remote_addr then
return true, "rDNS " .. rdns_suffix
end
end
self.logger:log(ngx.WARN, "IP " .. ngx.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
@ -281,10 +281,10 @@ function whitelist:is_whitelisted_ip()
end
-- Check if ASN is in whitelist
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if self.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(self.ctx.bw.remote_addr)
if not asn then
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
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
if bl_asn == tostring(asn) then
@ -301,7 +301,7 @@ end
function whitelist:is_whitelisted_uri()
-- Check if URI is in whitelist
for i, uri in ipairs(self.lists["URI"]) do
if utils.regex_match(ngx.ctx.bw.uri, uri) then
if utils.regex_match(self.ctx.bw.uri, uri) then
return true, "URI " .. uri
end
end
@ -312,7 +312,7 @@ end
function whitelist:is_whitelisted_ua()
-- Check if UA is in whitelist
for i, ua in ipairs(self.lists["USER_AGENT"]) do
if utils.regex_match(ngx.ctx.bw.http_user_agent, ua) then
if utils.regex_match(self.ctx.bw.http_user_agent, ua) then
return true, "UA " .. ua
end
end

View file

@ -81,7 +81,7 @@ COPY --chown=root:scheduler src/bw/misc/country.mmdb /var/tmp/bunkerweb/country.
RUN chmod 770 /var/tmp/bunkerweb/asn.mmdb /var/tmp/bunkerweb/country.mmdb
# Fix CVEs
RUN apk add --no-cache "libcrypto3>=3.1.1-r0" "libssl3>=3.1.1-r0"
# RUN apk add --no-cache "libcrypto3>=3.1.1-r0" "libssl3>=3.1.1-r0"
VOLUME /data /etc/nginx