improved core plugin execution order

This commit is contained in:
florian 2023-05-19 12:39:40 +02:00
parent b32f318919
commit 179beea4d7
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
52 changed files with 435 additions and 326 deletions

View File

@ -7,7 +7,7 @@ helpers.load_plugin = function(json)
-- Open file
local file, err, nb = io.open(json, "r")
if not file then
return false, "can't load JSON at " .. json .. " : " .. err .. "(nb = " .. tostring(nb) .. ")"
return false, "can't load JSON at " .. json .. " : " .. err .. " (nb = " .. tostring(nb) .. ")"
end
-- Decode JSON
local ok, plugin = pcall(cjson.decode, file:read("*a"))
@ -17,20 +17,80 @@ helpers.load_plugin = function(json)
end
-- Check fields
local missing_fields = {}
local required_fields = { "id", "order", "name", "description", "version", "settings" }
local required_fields = { "id", "name", "description", "version", "settings", "stream" }
for i, field in ipairs(required_fields) do
if plugin[field] == nil then
valid_json = false
table.insert(missing_fields, field)
end
end
if #missing_fields > 0 then
return false, "missing field(s) " .. cjson.encode(missing_fields) .. " for JSON at " .. json
end
-- Try require
local plugin_lua, err = helpers.require_plugin(plugin.id)
if plugin_lua == false then
return false, err
end
-- Fill phases
local phases = utils.get_phases()
plugin.phases = {}
if plugin_lua then
for i, phase in ipairs(phases) do
if plugin_lua[phase] ~= nil then
table.insert(plugin.phases, phase)
end
end
end
-- Return plugin
return true, plugin
end
helpers.order_plugins = function(plugins)
-- Extract orders
local file, err, nb = io.open("/usr/share/bunkerweb/core/order.json", "r")
if not file then
return false, err .. " (nb = " .. tostring(nb) .. ")"
end
local ok, orders = pcall(cjson.decode, file:read("*a"))
file:close()
if not ok then
return false, "invalid order.json : " .. err
end
-- Compute plugins/id/phases table
local plugins_phases = {}
for i, plugin in ipairs(plugins) do
plugins_phases[plugin.id] = {}
for j, phase in ipairs(plugin.phases) do
plugins_phases[plugin.id][phase] = true
end
end
-- Order result
local result_orders = {}
for i, phase in ipairs(utils.get_phases()) do
result_orders[phase] = {}
end
-- Fill order first
for phase, order in pairs(orders) do
for i, id in ipairs(order) do
local plugin = plugins_phases[id]
if plugin and plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
-- Then append missing plugins to the end
for i, phase in ipairs(utils.get_phases()) do
for id, plugin in pairs(plugins_phases) do
if plugin[phase] then
table.insert(result_orders[phase], id)
plugin[phase] = nil
end
end
end
return true, result_orders
end
helpers.require_plugin = function(id)
-- Require call
local ok, plugin_lua = pcall(require, id .. "/" .. id)
@ -45,7 +105,7 @@ helpers.require_plugin = function(id)
return false, "missing new() method for plugin " .. id
end
-- Return plugin
return plugin_lua, "new() call successful for plugin " .. id
return plugin_lua, "require() call successful for plugin " .. id
end
helpers.new_plugin = function(plugin_lua)

View File

@ -639,4 +639,18 @@ utils.regex_match = function(str, regex, options)
return match
end
utils.get_phases = function()
return {
"init",
"init_worker",
"set",
"access",
"header",
"log",
"preread",
"log_stream",
"log_default"
}
end
return utils

View File

@ -62,19 +62,23 @@ server {
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call log_default() methods
logger:log(ngx.INFO, "calling log_default() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.log_default) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -91,11 +95,11 @@ server {
if not ok then
logger:log(ngx.ERR, ret)
else
logger:log(ngx.INFO, plugin.id .. ":log_default() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":log_default() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method log_default() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method log_default() is not defined")
end
end
end

View File

@ -92,9 +92,6 @@ for i, plugin_path in ipairs(plugin_paths) do
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
table.insert(plugins, plugin)
table.sort(plugins, function (a, b)
return a.order < b.order
end)
logger:log(ngx.NOTICE, "loaded plugin " .. plugin.id .. " v" .. plugin.version)
end
end
@ -105,13 +102,28 @@ if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins into datastore")
-- Call init() methodatastore
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
logger:log(ngx.ERR, "can't compute plugins order : " .. err)
return false
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))
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins order into datastore")
-- Call init() method
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order["init"]) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -128,9 +140,9 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":init() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":init() call failed : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin.id .. ":init() call successful : " .. ret.msg)
logger:log(ngx.NOTICE, plugin_id .. ":init() call successful : " .. ret.msg)
end
end
else

View File

@ -1,145 +1,158 @@
init_by_lua_block {
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local cjson = require "cjson"
-- Start init phase
local logger = clogger:new("INIT-STREAM")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init-stream phase started")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new()
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin_", "^variable_", "^plugins$", "^api_", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local cjson = require "cjson"
-- Start init phase
local logger = clogger:new("INIT-STREAM")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init-stream phase started")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new()
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't delete " .. key .. " from datastore : " .. err)
return false
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
logger:log(ngx.INFO, "deleted " .. key .. " from datastore")
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")
-- 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 = {}
local plugin_paths = {"/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins"}
for i, plugin_path in ipairs(plugin_paths) do
local paths = io.popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do
local ok, plugin = helpers.load_plugin(path .. "/plugin.json")
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin_", "^variable_", "^plugins$", "^api_", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
logger:log(ngx.ERR, plugin)
else
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
if not ok then
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
table.insert(plugins, plugin)
table.sort(plugins, function (a, b)
return a.order < b.order
end)
logger:log(ngx.NOTICE, "loaded plugin " .. plugin.id .. " v" .. plugin.version)
end
logger:log(ngx.ERR, "can't delete " .. key .. " from datastore : " .. err)
return false
end
logger:log(ngx.INFO, "deleted " .. key .. " from datastore")
end
end
local ok, err = datastore:set("plugins", cjson.encode(plugins))
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins into datastore")
-- Call init() methodatastore
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
for i, plugin in ipairs(plugins) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.NOTICE, err)
else
-- Check if plugin has init method
if plugin_lua.init ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
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")
-- 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 = {}
local plugin_paths = {"/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins"}
for i, plugin_path in ipairs(plugin_paths) do
local paths = io.popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do
local ok, plugin = helpers.load_plugin(path .. "/plugin.json")
if not ok then
logger:log(ngx.ERR, plugin_obj)
logger:log(ngx.ERR, plugin)
else
local ok, ret = helpers.call_plugin(plugin_obj, "init")
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":init() call failed : " .. ret.msg)
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
logger:log(ngx.NOTICE, plugin.id .. ":init() call successful : " .. ret.msg)
table.insert(plugins, plugin)
logger:log(ngx.NOTICE, "loaded plugin " .. plugin.id .. " v" .. plugin.version)
end
end
else
logger:log(ngx.NOTICE, "skipped execution of " .. plugin.id .. " because method init() is not defined")
end
end
end
logger:log(ngx.NOTICE, "called init() methods of plugins")
logger:log(ngx.NOTICE, "init-stream phase ended")
}
local ok, err = datastore:set("plugins", cjson.encode(plugins))
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
logger:log(ngx.ERR, "can't compute plugins order : " .. err)
return false
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))
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins order into datastore")
-- Call init() method
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
for i, plugin_id in ipairs(order["init"]) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.NOTICE, err)
else
-- Check if plugin has init method
if plugin_lua.init ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "init")
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":init() call failed : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. ":init() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.NOTICE, "skipped execution of " .. plugin.id .. " because method init() is not defined")
end
end
end
logger:log(ngx.NOTICE, "called init() methods of plugins")
logger:log(ngx.NOTICE, "init-stream phase ended")
}

View File

@ -56,23 +56,23 @@ local ready_work = function(premature)
logger:log(ngx.INFO, "init_worker phase started")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call init_worker() methods
logger:log(ngx.INFO, "calling init_worker() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.init_worker) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -89,13 +89,13 @@ local ready_work = function(premature)
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":init_worker() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":init_worker() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":init_worker() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":init_worker() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method init_worker() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method init_worker() is not defined")
end
end
end

View File

@ -42,21 +42,25 @@ else
logger:log(ngx.INFO, "IP " .. ngx.ctx.bw.remote_addr .. " is not banned")
end
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call access() methods
logger:log(ngx.INFO, "calling access() methods of plugins ...")
local status = nil
local redirect = nil
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.access) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -73,27 +77,27 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":access() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":access() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":access() call successful : " .. ret.msg)
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
logger:log(ngx.WARN, "denied access from " .. plugin.id .. " : " .. ret.msg)
ngx.ctx.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)
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
end
status = ret.status
break
elseif ret.redirect then
logger:log(ngx.NOTICE, plugin.id .. " redirect to " .. ret.redirect .. " : " .. ret.msg)
logger:log(ngx.NOTICE, plugin_id .. " redirect to " .. ret.redirect .. " : " .. ret.msg)
redirect = ret.redirect
break
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method access() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method access() is not defined")
end
end
end

View File

@ -23,19 +23,23 @@ elseif errors then
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call header() methods
logger:log(ngx.INFO, "calling header() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.header) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -52,13 +56,13 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":header() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":header() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":header() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":header() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method header() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method header() is not defined")
end
end
end

View File

@ -23,19 +23,23 @@ elseif errors then
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call log() methods
logger:log(ngx.INFO, "calling log() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.log) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -52,13 +56,13 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":log() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":log() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":log() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":log() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method log() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method log() is not defined")
end
end
end

View File

@ -38,19 +38,23 @@ elseif errors then
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call set() methods
logger:log(ngx.INFO, "calling set() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.set) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -67,13 +71,13 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":set() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":set() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":set() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":set() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method set() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method set() is not defined")
end
end
end

View File

@ -23,19 +23,23 @@ elseif errors then
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call log_stream() methods
logger:log(ngx.INFO, "calling log_stream() methods of plugins ...")
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.log_stream) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -52,13 +56,13 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":log_stream() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":log_stream() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":log_stream() call successful : " .. ret.msg)
logger:log(ngx.INFO, plugin_id .. ":log_stream() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method log_stream() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method log_stream() is not defined")
end
end
end

View File

@ -36,20 +36,24 @@ else
logger:log(ngx.INFO, "IP " .. ngx.ctx.bw.remote_addr .. " is not banned")
end
-- Get plugins
local plugins, err = datastore:get("plugins")
if not plugins then
logger:log(ngx.ERR, "can't get plugins from datastore : " .. err)
return false
-- Get plugins order
local order, err = datastore:get("plugins_order")
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
plugins = cjson.decode(plugins)
order = cjson.decode(order)
-- Call preread() methods
logger:log(ngx.INFO, "calling preread() methods of plugins ...")
local status = nil
for i, plugin in ipairs(plugins) do
for i, plugin_id in ipairs(order.preread) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin.id)
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
@ -66,23 +70,23 @@ for i, plugin in ipairs(plugins) do
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin.id .. ":preread() call failed : " .. ret.msg)
logger:log(ngx.ERR, plugin_id .. ":preread() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin.id .. ":preread() call successful : " .. ret.msg)
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
logger:log(ngx.WARN, "denied access from " .. plugin.id .. " : " .. ret.msg)
ngx.ctx.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)
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
end
status = ret.status
break
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin.id .. " because method preread() is not defined")
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method preread() is not defined")
end
end
end

View File

@ -1,6 +1,5 @@
{
"id": "antibot",
"order": 9,
"name": "Antibot",
"description": "Bot detection by using a challenge.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "authbasic",
"order": 999,
"name": "Auth basic",
"description": "Enforce login before accessing a resource or the whole site using HTTP basic auth method.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "badbehavior",
"order": 999,
"name": "Bad behavior",
"description": "Ban IP generating too much 'bad' HTTP status code in a period of time.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "blacklist",
"order": 2,
"name": "Blacklist",
"description": "Deny access based on internal and external IP/network/rDNS/ASN blacklists.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "brotli",
"order": 999,
"name": "Brotli",
"description": "Compress HTTP requests with the brotli algorithm.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "bunkernet",
"order": 7,
"name": "BunkerNet",
"description": "Share threat data with other BunkerWeb instances via BunkerNet.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "clientcache",
"order": 999,
"name": "Client cache",
"description": "Manage caching for clients.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "cors",
"order": 999,
"name": "CORS",
"description": "Cross-Origin Resource Sharing.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "country",
"order": 4,
"name": "Country",
"description": "Deny access based on the country of the client IP.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "customcert",
"order": 999,
"name": "Custom HTTPS certificate",
"description": "Choose custom certificate for HTTPS.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "db",
"order": 999,
"name": "DB",
"description": "Integrate easily the Database.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "dnsbl",
"order": 5,
"name": "DNSBL",
"description": "Deny access based on external DNSBL servers.",
"version": "1.0",

View File

@ -5,9 +5,9 @@
error_page {{ code }} {{ page }};
location = {{ page }} {
root {% if ROOT_FOLDER == "" %}/var/www/html/{% if MULTISITE == "yes" %}{{ SERVER_NAME.split(" ")[0] }}{% endif %}{% else %}{{ ROOT_FOLDER }}{% endif %};
modsecurity off;
internal;
root {% if ROOT_FOLDER == "" %}/var/www/html/{% if MULTISITE == "yes" %}{{ SERVER_NAME.split(" ")[0] }}{% endif %}{% else %}{{ ROOT_FOLDER }}{% endif %};
modsecurity off;
internal;
auth_basic off;
}
{% endfor %}

View File

@ -1,6 +1,5 @@
{
"id": "errors",
"order": 999,
"name": "Errors",
"description": "Manage default error pages",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "greylist",
"order": 3,
"name": "Greylist",
"description": "Allow access while keeping security features based on internal and external IP/network/rDNS/ASN greylists.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "gzip",
"order": 999,
"name": "Gzip",
"description": "Compress HTTP requests with the gzip algorithm.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "headers",
"order": 999,
"name": "Headers",
"description": "Manage HTTP headers sent to clients.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "inject",
"order": 999,
"name": "HTML injection",
"description": "Inject custom HTML code before the </body> tag.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "jobs",
"order": 999,
"name": "Jobs",
"description": "Fake core plugin for internal jobs.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "letsencrypt",
"order": 999,
"name": "Let's Encrypt",
"description": "Automatic creation, renewal and configuration of Let's Encrypt certificates.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "limit",
"order": 8,
"name": "Limit",
"description": "Limit maximum number of requests and connections.",
"version": "1.0",

View File

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

View File

@ -1,6 +1,5 @@
{
"id": "misc",
"order": 0,
"name": "Miscellaneous",
"description": "Miscellaneous settings.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "modsecurity",
"order": 999,
"name": "ModSecurity",
"description": "Management of the ModSecurity WAF.",
"version": "1.0",

View File

@ -0,0 +1,55 @@
{
"init": [
"sessions",
"whitelist",
"blacklist",
"greylist",
"bunkernet",
"limit"
],
"init_worker": [
"redis",
"bunkernet",
"dnsbl"
],
"set": [
"whitelist"
],
"access": [
"whitelist",
"letsencrypt",
"blacklist",
"greylist",
"country",
"dnsbl",
"bunkernet",
"reversescan",
"limit",
"misc",
"cors",
"antibot"
],
"headers": [
"cors"
],
"log": [
"badbheavior",
"bunkernet"
],
"preread": [
"whitelist",
"blacklist",
"greylist",
"country",
"dnsbl",
"reversescan"
],
"log_stream": [
"badbehavior",
"bunkernet"
],
"log_default": [
"badbheavior",
"bunkernet"
]
}

View File

@ -1,6 +1,5 @@
{
"id": "php",
"order": 999,
"name": "PHP",
"description": "Manage local or remote PHP-FPM.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "realip",
"order": 999,
"name": "Real IP",
"description": "Get real IP of clients when BunkerWeb is behind a reverse proxy / load balancer.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "redirect",
"order": 999,
"name": "Redirect",
"description": "Manage HTTP redirects.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "redis",
"order": 999,
"name": "Redis",
"description": "Redis server configuration when using BunkerWeb in cluster mode.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "reverseproxy",
"order": 999,
"name": "Reverse proxy",
"description": "Manage reverse proxy configurations.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "reversescan",
"order": 6,
"name": "Reverse scan",
"description": "Scan clients ports to detect proxies or servers.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "selfsigned",
"order": 999,
"name": "Self-signed certificate",
"description": "Generate self-signed certificate.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "sessions",
"order": 999,
"name": "Sessions",
"description": "Management of session used by other plugins.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "ui",
"order": 999,
"name": "UI",
"description": "Integrate easily the BunkerWeb UI.",
"version": "1.0",

View File

@ -1,6 +1,5 @@
{
"id": "whitelist",
"order": 1,
"name": "Whitelist",
"description": "Allow access based on internal and external IP/network/rDNS/ASN whitelists.",
"version": "1.0",

View File

@ -281,7 +281,6 @@ class Database:
settings = plugin
plugin = {
"id": "general",
"order": 999,
"name": "General",
"description": "The general settings for the server",
"version": "0.1",
@ -963,8 +962,8 @@ class Database:
db_plugin = (
session.query(Plugins)
.with_entities(
Plugins.order,
Plugins.name,
Plugins.stream,
Plugins.description,
Plugins.version,
Plugins.method,
@ -985,8 +984,8 @@ class Database:
updates = {}
if plugin["order"] != db_plugin.order:
updates[Plugins.order] = plugin["order"]
if plugin["stream"] != db_plugin.stream:
updates[Plugins.stream] = plugin["stream"]
if plugin["name"] != db_plugin.name:
updates[Plugins.name] = plugin["name"]
@ -1380,7 +1379,7 @@ class Database:
session.query(Plugins)
.with_entities(
Plugins.id,
Plugins.order,
Plugins.stream,
Plugins.name,
Plugins.description,
Plugins.version,
@ -1389,20 +1388,18 @@ class Database:
Plugins.data,
Plugins.checksum,
)
.order_by(Plugins.order)
.all()
if with_data
else session.query(Plugins)
.with_entities(
Plugins.id,
Plugins.order,
Plugins.stream,
Plugins.name,
Plugins.description,
Plugins.version,
Plugins.external,
Plugins.method,
)
.order_by(Plugins.order)
.all()
):
if external and not plugin.external:
@ -1416,7 +1413,7 @@ class Database:
)
data = {
"id": plugin.id,
"order": plugin.order,
"stream": plugin.stream,
"name": plugin.name,
"description": plugin.description,
"version": plugin.version,

View File

@ -102,7 +102,6 @@ class Configurator:
return loads(Path(path).read_text())
def __load_plugins(self, path: str, _type: str = "core") -> List[Dict[str, Any]]:
orders = {}
plugins = []
files = glob(f"{path}/*/plugin.json")
for file in files:
@ -116,16 +115,6 @@ class Configurator:
)
continue
if data["order"] not in orders:
orders[data["order"]] = [data["id"]]
else:
if len(orders[data["order"]]) > 1 and data["order"] != 999:
self.__logger.warning(
f"Plugin {data['id']} have the same order than {', '.join(orders[data['order']])}. Therefor, the execution order will be random."
)
orders[data["order"]].append(data["id"])
if _type == "external":
plugin_content = BytesIO()
with tar_open(fileobj=plugin_content, mode="w:gz") as tar:
@ -275,7 +264,6 @@ class Configurator:
key in plugin.keys()
for key in [
"id",
"order",
"name",
"description",
"version",
@ -285,7 +273,7 @@ class Configurator:
):
return (
False,
f"Missing mandatory keys for plugin {plugin.get('id', 'unknown')} (id, order, name, description, version, stream, settings)",
f"Missing mandatory keys for plugin {plugin.get('id', 'unknown')} (id, name, description, version, stream, settings)",
)
if not self.__plugin_id_rx.match(plugin["id"]):
@ -293,8 +281,6 @@ class Configurator:
False,
f"Invalid id for plugin {plugin['id']} (Can only contain numbers, letters, underscores and hyphens (min 1 characters and max 64))",
)
elif not isinstance(plugin["order"], int):
return False, f"Invalid order for plugin {plugin['id']}, must be a number"
elif len(plugin["name"]) > 128:
return (
False,

View File

@ -187,23 +187,6 @@ if __name__ == "__main__":
)
sys_exit(1)
# Check core plugins orders
logger.info("Checking core plugins orders ...")
core_plugins = {}
files = glob(f"{args.core}/*/plugin.json")
for file in files:
try:
core_plugin = loads(Path(file).read_text())
if core_plugin["order"] not in core_plugins:
core_plugins[core_plugin["order"]] = []
core_plugins[core_plugin["order"]].append(core_plugin)
except:
logger.error(
f"Exception while loading JSON from {file} : {format_exc()}",
)
if args.variables:
logger.info(f"Variables : {args.variables}")

View File

@ -159,7 +159,6 @@ login_manager.login_view = "login"
user = User(vars["ADMIN_USERNAME"], vars["ADMIN_PASSWORD"])
PLUGIN_KEYS = [
"id",
"order",
"name",
"description",
"version",

View File

@ -144,7 +144,6 @@ class Config:
0,
{
"id": "general",
"order": 999,
"name": "General",
"description": "The general settings for the server",
"version": "0.1",