init work on refactoring
This commit is contained in:
parent
92e6984581
commit
afc0ac1988
|
@ -1,3 +1,53 @@
|
|||
local mlcache = require "resty.mlcache"
|
||||
local clogger = require "bunkerweb.logger"
|
||||
local class = require "middleclass"
|
||||
local datastore = class("datastore")
|
||||
|
||||
-- Instantiate mlcache objects at module level (which will be cached when running init phase)
|
||||
-- TODO : shm_miss, shm_locks
|
||||
local shm = "datastore"
|
||||
local ipc_shm = "datastore_ipc"
|
||||
if not ngx.shared.datastore then
|
||||
shm = "datastore_stream"
|
||||
ipc_shm = "datastore_ipc_stream"
|
||||
end
|
||||
local store, err = mlcache.new(
|
||||
"datastore",
|
||||
shm,
|
||||
{
|
||||
lru_size = 100,
|
||||
ttl = 0,
|
||||
neg_ttl = 0,
|
||||
shm_set_tries = 1,
|
||||
ipc_shm = ipc_shm
|
||||
}
|
||||
)
|
||||
local logger = clogger:new("DATASTORE")
|
||||
if not store then
|
||||
logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
|
||||
end
|
||||
|
||||
function datastore:new()
|
||||
self.store = store
|
||||
self.logger = logger
|
||||
end
|
||||
|
||||
function datastore:get(key)
|
||||
local value, err, hit_level = self.store:get(key)
|
||||
if err then
|
||||
return false, err
|
||||
end
|
||||
self.logger:log(ngx.INFO, "hit level for " .. key .. " = " .. tostring(hit_level))
|
||||
return true, value
|
||||
end
|
||||
|
||||
function datastore:set(key, value)
|
||||
local ok, err = self.store:set(key, nil, value)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local datastore = { dict = ngx.shared.datastore }
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
local cjson = require "cjson"
|
||||
|
||||
local helpers = {}
|
||||
|
||||
helpers.load_plugin = function(json)
|
||||
-- Open file
|
||||
local file, err, nb = io.open(json, "r")
|
||||
if not file then
|
||||
return false, "can't load JSON at " .. json .. " : " .. err .. "(nb = " .. tostring(nb) .. ")"
|
||||
end
|
||||
-- Decode JSON
|
||||
local ok, plugin = pcall(cjson.decode, file:read("*a"))
|
||||
file:close()
|
||||
if not ok then
|
||||
return false, "invalid JSON at " .. json .. " : " .. err
|
||||
end
|
||||
-- Check fields
|
||||
local missing_fields = {}
|
||||
local required_fields = {"id", "order", "name", "description", "version", "settings"}
|
||||
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
|
||||
-- Return plugin
|
||||
return plugin
|
||||
end
|
||||
|
||||
helpers.new_plugin = function(id)
|
||||
-- Require call
|
||||
local ok, plugin_lua = pcall(require, id .. "/" .. id)
|
||||
if not ok then
|
||||
if plugin_lua:match("not found") then
|
||||
return nil, "plugin " .. id .. " doesn't have LUA code"
|
||||
end
|
||||
return false, "require error for plugin " .. id .. " : " .. plugin_lua
|
||||
end
|
||||
-- New call
|
||||
if plugin_lua.new == nil then
|
||||
return false, "missing new() method for plugin " .. id
|
||||
end
|
||||
local ok, err = pcall(plugin_lua.new, plugin_lua)
|
||||
if not ok then
|
||||
return false, "new() call failed for plugin " .. id .. " : " .. err
|
||||
end
|
||||
-- Return plugin
|
||||
return plugin_lua, "new() call successful for plugin " .. id
|
||||
end
|
||||
|
||||
helpers.call_plugin = function(plugin, method)
|
||||
-- Check if method is present
|
||||
if plugin[method] == nil then
|
||||
return nil, "missing " .. method .. "() method for plugin " .. plugin:get_id()
|
||||
end
|
||||
-- Call method
|
||||
local ok, ret = pcall(plugin[method], plugin)
|
||||
if not ok then
|
||||
return false, plugin.id .. ":" .. method .. "() failed : " .. ret
|
||||
end
|
||||
-- Check values
|
||||
local missing_values = {}
|
||||
local required_values = {"ret", "msg"}
|
||||
for i, value in ipairs(required_values) do
|
||||
if ret[value] == nil then
|
||||
table.insert(missing_values, value)
|
||||
end
|
||||
end
|
||||
if #missing_values > 0 then
|
||||
return false, "missing required return value(s) : " .. cjson.encode(missing_values)
|
||||
end
|
||||
-- Return
|
||||
return true, ret
|
||||
end
|
||||
|
||||
return helpers
|
|
@ -4,6 +4,7 @@ local cjson = require "cjson"
|
|||
local resolver = require "resty.dns.resolver"
|
||||
local mmdb = require "mmdb"
|
||||
local logger = require "logger"
|
||||
local session = require "resty.session"
|
||||
|
||||
local utils = {}
|
||||
|
||||
|
@ -365,4 +366,54 @@ utils.get_deny_status = function()
|
|||
return tonumber(status)
|
||||
end
|
||||
|
||||
utils.get_session = function()
|
||||
if ngx.ctx.session then
|
||||
return ngx.ctx.session, ngx.ctx.session_err, ngx.ctx.session_exists
|
||||
end
|
||||
local _session, err, exists = session.start()
|
||||
if err then
|
||||
logger.log(ngx.ERR, "UTILS", "can't start session : " .. err)
|
||||
end
|
||||
ngx.ctx.session = _session
|
||||
ngx.ctx.session_err = err
|
||||
ngx.ctx.session_exists = exists
|
||||
ngx.ctx.session_saved = false
|
||||
ngx.ctx.session_data = _session.get_data()
|
||||
if not ngx.ctx.session_data then
|
||||
ngx.ctx.session_data = {}
|
||||
end
|
||||
return _session, err, exists
|
||||
end
|
||||
|
||||
utils.save_session = function()
|
||||
if ngx.ctx.session and not ngx.ctx.session_err and not ngx.ctx.session_saved then
|
||||
ngx.ctx.session:set_data(ngx.ctx.session_data)
|
||||
local ok, err = ngx.ctx.session:save()
|
||||
if err then
|
||||
logger.log(ngx.ERR, "UTILS", "can't save session : " .. err)
|
||||
return false, "can't save session : " .. err
|
||||
end
|
||||
ngx.ctx.session_saved = true
|
||||
return true, "session saved"
|
||||
elseif ngx.ctx.session_saved then
|
||||
return true, "session already saved"
|
||||
end
|
||||
return true, "no session"
|
||||
end
|
||||
|
||||
utils.set_session = function(key, value)
|
||||
if ngx.ctx.session and not ngx.ctx.session_err then
|
||||
ngx.ctx.session_data[key] = value
|
||||
return true, "value set"
|
||||
end
|
||||
return true, "no session"
|
||||
end
|
||||
|
||||
utils.get_session = function(key)
|
||||
if ngx.ctx.session and not ngx.ctx.session_err then
|
||||
return true, "value set", ngx.ctx.session_data[key]
|
||||
end
|
||||
return false, "no session"
|
||||
end
|
||||
|
||||
return utils
|
||||
|
|
|
@ -45,6 +45,7 @@ lua_package_cpath "/usr/share/bunkerweb/deps/lib/?.so;/usr/share/bunkerweb/deps/
|
|||
lua_ssl_trusted_certificate "/usr/share/bunkerweb/misc/root-ca.pem";
|
||||
lua_ssl_verify_depth 2;
|
||||
lua_shared_dict datastore {{ DATASTORE_MEMORY_SIZE }};
|
||||
lua_shared_dict datastore_ipc {{ DATASTORE_IPC_MEMORY_SIZE }};
|
||||
|
||||
# LUA init block
|
||||
include /etc/nginx/init-lua.conf;
|
||||
|
|
|
@ -1,118 +1,166 @@
|
|||
init_by_lua_block {
|
||||
|
||||
local logger = require "logger"
|
||||
local datastore = require "datastore"
|
||||
local plugins = require "plugins"
|
||||
local utils = require "utils"
|
||||
local cjson = require "cjson"
|
||||
local class = require "middleclass"
|
||||
local clogger = require "bunkerweb.logger"
|
||||
local helpers = require "bunkerweb.helpers"
|
||||
local datastore = require "bunkerweb.datastore"
|
||||
|
||||
logger.log(ngx.NOTICE, "INIT", "Init phase started")
|
||||
-- Start init phase
|
||||
logger:new("INIT")
|
||||
datastore:new()
|
||||
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_", "^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, "INIT", "Can't delete " .. key .. " from datastore : " .. err)
|
||||
logger:log(ngx.ERR, "can't delete " .. key .. " from datastore : " .. err)
|
||||
return false
|
||||
end
|
||||
logger.log(ngx.INFO, "INIT", "Deleted " .. key .. " from datastore")
|
||||
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, "INIT", "Can't open /etc/nginx/variables.env file")
|
||||
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("(.+)=(.*)")
|
||||
ok, err = datastore:set("variable_" .. variable, value)
|
||||
local ok, err = datastore:set("variable_" .. variable, value)
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "INIT", "Can't save variable " .. variable .. " into datastore")
|
||||
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 default values into the datastore
|
||||
ok, err = datastore:set("plugins", cjson.encode({}))
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "INIT", "Can't set default value for plugins into the datastore : " .. err)
|
||||
-- Set misc values into the datastore
|
||||
logger:log(ngx.NOTICE, "saving misc values into datastore ...")
|
||||
local miscs = {
|
||||
reserved_ips = {
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"224.0.0.0/4",
|
||||
"233.252.0.0/24",
|
||||
"240.0.0.0/4",
|
||||
"255.255.255.255/32"
|
||||
},
|
||||
resolvers = {}
|
||||
}
|
||||
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 false
|
||||
end
|
||||
ok, err = utils.set_values()
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "INIT", "Error while setting default values : " .. err)
|
||||
return false
|
||||
for str_resolver in var_resolvers:gmatch("%S+") do
|
||||
table.insert(miscs.resolvers, str_resolver)
|
||||
end
|
||||
for k, v in pairs(miscs) do
|
||||
local ok, err = datastore:set("misc_" .. k, cjson.encode(v))
|
||||
if not ok then
|
||||
logger:log(ngx.ERR, "can't save misc " .. k .. " into datastore : " .. err)
|
||||
return false
|
||||
end
|
||||
logger:log(ngx.INFO, "saved misc " .. k .. " into datastore")
|
||||
end
|
||||
logger:log(ngx.NOTICE, "saved misc values into datastore")
|
||||
|
||||
-- API setup
|
||||
-- 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, "INIT", "Can't get variable USE_API from the datastore")
|
||||
logger.log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
|
||||
return false
|
||||
end
|
||||
if value == "yes" then
|
||||
value, err = datastore:get("variable_API_WHITELIST_IP")
|
||||
local value, err = datastore:get("variable_API_WHITELIST_IP")
|
||||
if not value then
|
||||
logger.log(ngx.ERR, "INIT", "Can't get variable API_WHITELIST_IP from the datastore")
|
||||
logger.log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
|
||||
return false
|
||||
end
|
||||
local whitelists = { data = {}}
|
||||
local whitelists = {}
|
||||
for whitelist in value:gmatch("%S+") do
|
||||
table.insert(whitelists.data, whitelist)
|
||||
table.insert(whitelists, whitelist)
|
||||
end
|
||||
ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
|
||||
local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "INIT", "Can't save api_whitelist_ip to datastore : " .. err)
|
||||
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
|
||||
plugin, err = plugins:load(path)
|
||||
if not plugin then
|
||||
logger.log(ngx.ERR, "INIT", "Error while loading plugin from " .. path .. " : " .. err)
|
||||
return false
|
||||
local ok, plugin = helpers.load_plugin(path .. "/plugin.json")
|
||||
if ok then
|
||||
logger.log(ngx.ERR, 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.NOTICE, "INIT", "Loaded plugin " .. plugin.id .. " v" .. plugin.version)
|
||||
end
|
||||
end
|
||||
|
||||
-- Call init method of plugins
|
||||
local list, err = plugins:list()
|
||||
if not list then
|
||||
logger.log(ngx.ERR, "INIT", "Can't list loaded plugins : " .. err)
|
||||
list = {}
|
||||
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
|
||||
for i, plugin in ipairs(list) do
|
||||
local ret, plugin_lua = pcall(require, plugin.id .. "/" .. plugin.id)
|
||||
if ret then
|
||||
local plugin_obj = plugin_lua.new()
|
||||
if plugin_obj.init ~= nil then
|
||||
ok, err = plugin_obj:init()
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "INIT", "Plugin " .. plugin.id .. " failed on init() : " .. err)
|
||||
else
|
||||
logger.log(ngx.INFO, "INIT", "Successfull init() call for plugin " .. plugin.id .. " : " .. err)
|
||||
end
|
||||
else
|
||||
logger.log(ngx.INFO, "INIT", "init() method not found in " .. plugin.id .. ", skipped execution")
|
||||
end
|
||||
logger:log(ngx.NOTICE, "saved plugins into datastore")
|
||||
|
||||
-- Call init() methods
|
||||
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
|
||||
for i, plugin in ipairs(plugins) do
|
||||
local plugin_lua, err = helpers.new_plugin(plugin.id)
|
||||
if plugin_lua == false then
|
||||
logger.log(ngx.ERR, err)
|
||||
else
|
||||
if plugin_lua:match("not found") then
|
||||
logger.log(ngx.INFO, "INIT", "can't require " .. plugin.id .. " : not found")
|
||||
logger.log(ngx.NOTICE, err)
|
||||
end
|
||||
if plugin_lua ~= nil then
|
||||
local ok, ret = helpers.call_plugin(plugin_lua)
|
||||
if ok == false then
|
||||
logger.log(ngx.ERR, ret)
|
||||
elseif ok == nil then
|
||||
logger.log(ngx.NOTICE, ret)
|
||||
else
|
||||
logger.log(ngx.ERR, "INIT", "can't require " .. plugin.id .. " : " .. plugin_lua)
|
||||
if ret.ret then
|
||||
logger.log(ngx.NOTICE, plugin.id .. ":init() call successful : " .. ret.msg)
|
||||
else
|
||||
logger.log(ngx.ERR, plugin.id .. ":init() call failed : " .. ret.msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
logger:log(ngx.NOTICE, "called init() methods of plugins")
|
||||
|
||||
logger.log(ngx.NOTICE, "INIT", "Init phase ended")
|
||||
logger:log(ngx.NOTICE, "init phase ended")
|
||||
|
||||
}
|
||||
|
|
|
@ -82,6 +82,14 @@ for i, plugin in ipairs(list) do
|
|||
end
|
||||
end
|
||||
|
||||
-- Save session
|
||||
local ok, err = utils.save_session()
|
||||
if not ok then
|
||||
logger.log(ngx.ERR, "ACCESS", "Can't save session : " .. err)
|
||||
else
|
||||
logger.log(ngx.INFO, "ACCESS", "Session save status : " .. err)
|
||||
end
|
||||
|
||||
logger.log(ngx.INFO, "ACCESS", "Access phase ended")
|
||||
|
||||
}
|
|
@ -5,7 +5,6 @@ local utils = require "utils"
|
|||
local datastore = require "datastore"
|
||||
local logger = require "logger"
|
||||
local cjson = require "cjson"
|
||||
local session = require "resty.session"
|
||||
local captcha = require "antibot.captcha"
|
||||
local base64 = require "base64"
|
||||
local sha256 = require "resty.sha256"
|
||||
|
@ -59,6 +58,12 @@ function _M:access()
|
|||
return false, "can't get Antibot URI from datastore : " .. err, nil, nil
|
||||
end
|
||||
|
||||
-- Prepare challenge
|
||||
local ok, err = self:prepare_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
return false, "can't prepare challenge : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
|
||||
-- Don't go further if client resolved the challenge
|
||||
local resolved, err, original_uri = self:challenge_resolved(antibot)
|
||||
if resolved == nil then
|
||||
|
@ -73,10 +78,6 @@ function _M:access()
|
|||
|
||||
-- Redirect to challenge page
|
||||
if ngx.var.uri ~= challenge_uri then
|
||||
local ok, err = self:prepare_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
return false, "can't prepare challenge : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "redirecting client to the challenge uri", true, ngx.redirect(challenge_uri)
|
||||
end
|
||||
|
||||
|
@ -84,13 +85,6 @@ function _M:access()
|
|||
if ngx.var.request_method == "GET" then
|
||||
local ok, err = self:display_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
if err == "can't open session" then
|
||||
local ok, err = self:prepare_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
return false, "can't prepare challenge : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "redirecting client to the challenge uri", true, ngx.redirect(challenge_uri)
|
||||
end
|
||||
return false, "display challenge error : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "displaying challenge to client", true, ngx.HTTP_OK
|
||||
|
@ -100,13 +94,6 @@ function _M:access()
|
|||
if ngx.var.request_method == "POST" then
|
||||
local ok, err, redirect = self:check_challenge(antibot)
|
||||
if ok == nil then
|
||||
if err == "can't open session" then
|
||||
local ok, err = self:prepare_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
return false, "can't prepare challenge : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "redirecting client to the challenge uri", true, ngx.redirect(challenge_uri)
|
||||
end
|
||||
return false, "check challenge error : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
if redirect then
|
||||
|
@ -114,13 +101,6 @@ function _M:access()
|
|||
end
|
||||
local ok, err = self:display_challenge(antibot)
|
||||
if not ok then
|
||||
if err == "can't open session" then
|
||||
local ok, err = self:prepare_challenge(antibot, challenge_uri)
|
||||
if not ok then
|
||||
return false, "can't prepare challenge : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "redirecting client to the challenge uri", true, ngx.redirect(challenge_uri)
|
||||
end
|
||||
return false, "display challenge error : " .. err, true, ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
end
|
||||
return true, "displaying challenge to client", true, ngx.HTTP_OK
|
||||
|
@ -131,63 +111,77 @@ function _M:access()
|
|||
end
|
||||
|
||||
function _M:challenge_resolved(antibot)
|
||||
local chall_session, present, reason = session.open()
|
||||
if present and chall_session.data.resolved and chall_session.data.type == antibot then
|
||||
return true, "challenge " .. antibot .. " resolved", chall_session.data.original_uri
|
||||
local session, err, exists = utils.get_session()
|
||||
if err then
|
||||
return false, "session error : " .. err
|
||||
end
|
||||
return false, "challenge " .. antibot .. " not resolved", nil
|
||||
local raw_data = get_session("antibot")
|
||||
if not raw_data then
|
||||
return false, "session is set but no antibot data", nil
|
||||
end
|
||||
local data = cjson.decode(raw_data)
|
||||
if data.resolved and antibot == data.antibot then
|
||||
return true, "challenge resolved", data.original_uri
|
||||
end
|
||||
return false, "challenge not resolved", data.original_uri
|
||||
return false, "no session", nil
|
||||
end
|
||||
|
||||
function _M:prepare_challenge(antibot, challenge_uri)
|
||||
local chall_session, present, reason = session.open()
|
||||
if not present then
|
||||
local chall_session, present, reason = chall_session:start()
|
||||
if not chall_session then
|
||||
return false, "can't start session", nil
|
||||
end
|
||||
chall_session.data.type = antibot
|
||||
chall_session.data.resolved = false
|
||||
if ngx.var.request_uri == challenge_uri then
|
||||
chall_session.data.original_uri = "/"
|
||||
else
|
||||
chall_session.data.original_uri = ngx.var.request_uri
|
||||
end
|
||||
if antibot == "cookie" then
|
||||
chall_session.data.resolved = true
|
||||
end
|
||||
local saved, err = chall_session:save()
|
||||
if not saved then
|
||||
return false, "error while saving session : " .. err
|
||||
local session, err, exists = utils.get_session()
|
||||
if err then
|
||||
return false, "session error : " .. err
|
||||
end
|
||||
local current_data = nil
|
||||
if exists then
|
||||
local raw_data = get_session("antibot")
|
||||
if raw_data then
|
||||
current_data = cjson.decode(raw_data)
|
||||
end
|
||||
end
|
||||
return true, antibot .. " challenge prepared"
|
||||
if not current_data or current_data.antibot ~= antibot then
|
||||
local data = {
|
||||
type = antibot,
|
||||
resolved = antibot == "cookie",
|
||||
original_uri = ngx.var.request_uri
|
||||
}
|
||||
if ngx.var.original_uri == challenge_uri then
|
||||
data.original_uri = "/"
|
||||
end
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
return true, "prepared"
|
||||
end
|
||||
return true, "already prepared"
|
||||
end
|
||||
|
||||
function _M:display_challenge(antibot, challenge_uri)
|
||||
-- Open session
|
||||
local chall_session, present, reason = session.open()
|
||||
if not present then
|
||||
return false, "can't open session"
|
||||
local session, err, exists = utils.get_session()
|
||||
if err then
|
||||
return false, "can't open session : " .. err
|
||||
end
|
||||
|
||||
-- Get data
|
||||
local raw_data = get_session("antibot")
|
||||
if not raw_data then
|
||||
return false, "session is set but no data"
|
||||
end
|
||||
local data = cjson.decode(raw_data)
|
||||
|
||||
-- Check if session type is equal to antibot type
|
||||
if antibot ~= chall_session.data.type then
|
||||
if antibot ~= data.type then
|
||||
return false, "session type is different from antibot type"
|
||||
end
|
||||
|
||||
-- Compute challenges
|
||||
if antibot == "javascript" then
|
||||
chall_session:start()
|
||||
chall_session.data.random = utils.rand(20)
|
||||
chall_session:save()
|
||||
data.random = utils.rand(20)
|
||||
elseif antibot == "captcha" then
|
||||
chall_session:start()
|
||||
local chall_captcha = captcha.new()
|
||||
chall_captcha:font("/usr/share/bunkerweb/core/antibot/files/font.ttf")
|
||||
chall_captcha:generate()
|
||||
chall_session.data.image = base64.encode(chall_captcha:jpegStr(70))
|
||||
chall_session.data.text = chall_captcha:getStr()
|
||||
chall_session:save()
|
||||
data.image = base64.encode(chall_captcha:jpegStr(70))
|
||||
data.text = chall_captcha:getStr()
|
||||
end
|
||||
|
||||
-- Load HTML templates
|
||||
|
@ -201,12 +195,12 @@ function _M:display_challenge(antibot, challenge_uri)
|
|||
|
||||
-- Javascript case
|
||||
if antibot == "javascript" then
|
||||
html = templates.javascript:format(challenge_uri, chall_session.data.random)
|
||||
html = templates.javascript:format(challenge_uri, data.random)
|
||||
end
|
||||
|
||||
-- Captcha case
|
||||
if antibot == "captcha" then
|
||||
html = templates.captcha:format(challenge_uri, chall_session.data.image)
|
||||
html = templates.captcha:format(challenge_uri, data.image)
|
||||
end
|
||||
|
||||
-- reCAPTCHA case
|
||||
|
@ -227,6 +221,12 @@ function _M:display_challenge(antibot, challenge_uri)
|
|||
html = templates.hcaptcha:format(challenge_uri, hcaptcha_sitekey)
|
||||
end
|
||||
|
||||
-- Set new data
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
local ok, err = utils.save_session()
|
||||
if not ok then
|
||||
return false, "can't save session : " .. err
|
||||
end
|
||||
ngx.header["Content-Type"] = "text/html"
|
||||
ngx.say(html)
|
||||
|
||||
|
@ -235,13 +235,20 @@ end
|
|||
|
||||
function _M:check_challenge(antibot)
|
||||
-- Open session
|
||||
local chall_session, present, reason = session.open()
|
||||
if not present then
|
||||
return nil, "can't open session", nil
|
||||
local session, err, exists = utils.get_session()
|
||||
if err then
|
||||
return nil, "can't open session : " .. err, nil
|
||||
end
|
||||
|
||||
-- Get data
|
||||
local raw_data = get_session("antibot")
|
||||
if not raw_data then
|
||||
return false, "session is set but no data", nil
|
||||
end
|
||||
local data = cjson.decode(raw_data)
|
||||
|
||||
-- Check if session type is equal to antibot type
|
||||
if antibot ~= chall_session.data.type then
|
||||
if antibot ~= data.type then
|
||||
return nil, "session type is different from antibot type", nil
|
||||
end
|
||||
|
||||
|
@ -257,16 +264,15 @@ function _M:check_challenge(antibot)
|
|||
return false, "missing challenge arg", nil
|
||||
end
|
||||
local hash = sha256:new()
|
||||
hash:update(chall_session.data.random .. args["challenge"])
|
||||
hash:update(data.random .. args["challenge"])
|
||||
local digest = hash:final()
|
||||
resolved = str.to_hex(digest):find("^0000") ~= nil
|
||||
if not resolved then
|
||||
return false, "wrong value", nil
|
||||
end
|
||||
chall_session:start()
|
||||
chall_session.data.resolved = true
|
||||
chall_session:save()
|
||||
return true, "resolved", chall_session.data.original_uri
|
||||
data.resolved = true
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
return true, "resolved", data.original_uri
|
||||
end
|
||||
|
||||
-- Captcha case
|
||||
|
@ -276,13 +282,12 @@ function _M:check_challenge(antibot)
|
|||
if err == "truncated" or not args or not args["captcha"] then
|
||||
return false, "missing challenge arg", nil
|
||||
end
|
||||
if chall_session.data.text ~= args["captcha"] then
|
||||
if data.text ~= args["captcha"] then
|
||||
return false, "wrong value", nil
|
||||
end
|
||||
chall_session:start()
|
||||
chall_session.data.resolved = true
|
||||
chall_session:save()
|
||||
return true, "resolved", chall_session.data.original_uri
|
||||
data.resolved = true
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
return true, "resolved", data.original_uri
|
||||
end
|
||||
|
||||
-- reCAPTCHA case
|
||||
|
@ -311,21 +316,20 @@ function _M:check_challenge(antibot)
|
|||
if not res then
|
||||
return nil, "can't send request to reCAPTCHA API : " .. err, nil
|
||||
end
|
||||
local ok, data = pcall(cjson.decode, res.body)
|
||||
local ok, rdata = pcall(cjson.decode, res.body)
|
||||
if not ok then
|
||||
return nil, "error while decoding JSON from reCAPTCHA API : " .. data, nil
|
||||
return nil, "error while decoding JSON from reCAPTCHA API : " .. rdata, nil
|
||||
end
|
||||
local recaptcha_score, err = utils.get_variable("ANTIBOT_RECAPTCHA_SCORE")
|
||||
if not recaptcha_score then
|
||||
return nil, "can't get reCAPTCHA score variable : " .. err, nil
|
||||
end
|
||||
if not data.success or data.score < tonumber(recaptcha_score) then
|
||||
return false, "client failed challenge with score " .. tostring(data.score), nil
|
||||
if not rdata.success or rdata.score < tonumber(recaptcha_score) then
|
||||
return false, "client failed challenge with score " .. tostring(rdata.score), nil
|
||||
end
|
||||
chall_session:start()
|
||||
chall_session.data.resolved = true
|
||||
chall_session:save()
|
||||
return true, "resolved", chall_session.data.original_uri
|
||||
data.resolved = true
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
return true, "resolved", data.original_uri
|
||||
end
|
||||
|
||||
-- hCaptcha case
|
||||
|
@ -354,17 +358,16 @@ function _M:check_challenge(antibot)
|
|||
if not res then
|
||||
return nil, "can't send request to hCaptcha API : " .. err, nil
|
||||
end
|
||||
local ok, data = pcall(cjson.decode, res.body)
|
||||
local ok, hdata = pcall(cjson.decode, res.body)
|
||||
if not ok then
|
||||
return nil, "error while decoding JSON from hCaptcha API : " .. data, nil
|
||||
end
|
||||
if not data.success then
|
||||
if not hdata.success then
|
||||
return false, "client failed challenge", nil
|
||||
end
|
||||
chall_session:start()
|
||||
chall_session.data.resolved = true
|
||||
chall_session:save()
|
||||
return true, "resolved", chall_session.data.original_uri
|
||||
data.resolved = true
|
||||
utils.set_session("antibot", cjson.encode(data))
|
||||
return true, "resolved", data.original_uri
|
||||
end
|
||||
|
||||
return nil, "unknown", nil
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
local _M = {}
|
||||
|
||||
local mlcache = require "resty.mlcache"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
local class = require "bunkerweb.middleclass"
|
||||
local logger = require "bunkerweb.logger"
|
||||
local settings = require "bunkerweb.settings"
|
||||
local plugin = class("plugin")
|
||||
|
||||
function plugin:initialize(name, fill_settings)
|
||||
self.name = name
|
||||
self.logger = logger:new(name)
|
||||
self.settings = {}
|
||||
for i, setting in ipairs(fill_settings) do
|
||||
local value, err = settings:get(setting)
|
||||
if not value then
|
||||
local err = string.format("can't get setting %s : %s", setting, err)
|
||||
self.logger:log(ngx.ERR, err)
|
||||
return false, err
|
||||
end
|
||||
self.settings[setting] = value
|
||||
end
|
||||
return true, nil
|
||||
end
|
||||
|
||||
return plugin
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"id": "session",
|
||||
"id": "sessions",
|
||||
"order": 999,
|
||||
"name": "Session",
|
||||
"name": "Sessions",
|
||||
"description": "Management of session used by other plugins.",
|
||||
"version": "0.1",
|
||||
"settings": {
|
||||
"SESSION_SECRET": {
|
||||
"SESSIONS_SECRET": {
|
||||
"context": "global",
|
||||
"default": "random",
|
||||
"help": "Secret used to encrypt sessions variables for storing data related to challenges.",
|
||||
"id": "session-secret",
|
||||
"label": "Session secret",
|
||||
"label": "Sessions secret",
|
||||
"regex": "^\\w+$",
|
||||
"type": "password"
|
||||
},
|
||||
"SESSION_NAME": {
|
||||
"SESSIONS_NAME": {
|
||||
"context": "global",
|
||||
"default": "random",
|
||||
"help": "Name of the cookie given to clients.",
|
||||
"id": "session-name",
|
||||
"label": "Session name",
|
||||
"id": "sessions-name",
|
||||
"label": "Sessions name",
|
||||
"regex": "^\\w+$",
|
||||
"type": "text"
|
||||
},
|
||||
"SESSION_IDLING_TIMEOUT": {
|
||||
"SESSIONS_IDLING_TIMEOUT": {
|
||||
"context": "global",
|
||||
"default": "1800",
|
||||
"help": "Maximum time (in seconds) of inactivity before the session is invalidated.",
|
||||
"id": "session-idling-timeout",
|
||||
"label": "Session idling timeout",
|
||||
"id": "sessions-idling-timeout",
|
||||
"label": "Sessions idling timeout",
|
||||
"regex": "^\\d+$",
|
||||
"type": "text"
|
||||
},
|
||||
"SESSION_ROLLING_TIMEOUT": {
|
||||
"SESSIONS_ROLLING_TIMEOUT": {
|
||||
"context": "global",
|
||||
"default": "3600",
|
||||
"help": "Maximum time (in seconds) before a session must be renewed.",
|
||||
"id": "session-rolling-timeout",
|
||||
"label": "Session rolling timeout",
|
||||
"id": "sessions-rolling-timeout",
|
||||
"label": "Sessions rolling timeout",
|
||||
"regex": "^\\d+$",
|
||||
"type": "text"
|
||||
},
|
||||
"SESSION_ABSOLUTE_TIMEOUT": {
|
||||
"SESSIONS_ABSOLUTE_TIMEOUT": {
|
||||
"context": "global",
|
||||
"default": "86400",
|
||||
"help": "Maximum time (in seconds) before a session is destroyed.",
|
||||
"id": "session-absolute-timeout",
|
||||
"label": "Session absolute timeout",
|
||||
"id": "sessions-absolute-timeout",
|
||||
"label": "SessionS absolute timeout",
|
||||
"regex": "^\\d+$",
|
||||
"type": "text"
|
||||
}
|
|
@ -12,11 +12,11 @@ end
|
|||
function _M:init()
|
||||
-- Get vars
|
||||
local vars = {
|
||||
["SESSION_SECRET"] = "",
|
||||
["SESSION_NAME"] = "",
|
||||
["SESSION_IDLING_TIMEOUT"] = "",
|
||||
["SESSION_ROLLING_TIMEOUT"] = "",
|
||||
["SESSION_ABSOLUTE_TIMEOUT"] = "",
|
||||
["SESSIONS_SECRET"] = "",
|
||||
["SESSIONS_NAME"] = "",
|
||||
["SESSIONS_IDLING_TIMEOUT"] = "",
|
||||
["SESSIONS_ROLLING_TIMEOUT"] = "",
|
||||
["SESSIONS_ABSOLUTE_TIMEOUT"] = "",
|
||||
["USE_REDIS"] = "",
|
||||
["REDIS_HOST"] = "",
|
||||
["REDIS_PORT"] = "",
|
||||
|
@ -33,16 +33,16 @@ function _M:init()
|
|||
end
|
||||
-- Init configuration
|
||||
local config = {
|
||||
secret = vars["SESSION_SECRET"],
|
||||
cookie_name = vars["SESSION_NAME"],
|
||||
idling_timeout = tonumber(vars["SESSION_IDLING_TIMEOUT"]),
|
||||
rolling_timeout = tonumber(vars["SESSION_ROLLING_TIMEOUT"]),
|
||||
absolute_timeout = tonumber(vars["SESSION_ABSOLUTE_TIMEOUT"])
|
||||
secret = vars["SESSIONS_SECRET"],
|
||||
cookie_name = vars["SESSIONS_NAME"],
|
||||
idling_timeout = tonumber(vars["SESSIONS_IDLING_TIMEOUT"]),
|
||||
rolling_timeout = tonumber(vars["SESSIONS_ROLLING_TIMEOUT"]),
|
||||
absolute_timeout = tonumber(vars["SESSIONS_ABSOLUTE_TIMEOUT"])
|
||||
}
|
||||
if vars["SESSION_SECRET"] == "random" then
|
||||
if vars["SESSIONS_SECRET"] == "random" then
|
||||
config.secret = utils.rand(16)
|
||||
end
|
||||
if vars["SESSION_NAME"] == "random" then
|
||||
if vars["SESSIONS_NAME"] == "random" then
|
||||
config.cookie_name = utils.rand(16)
|
||||
end
|
||||
if vars["USE_REDIS"] == "no" then
|
||||
|
@ -64,24 +64,7 @@ function _M:init()
|
|||
}
|
||||
end
|
||||
session.init(config)
|
||||
return true, "session init successful"
|
||||
end
|
||||
|
||||
function _M:access()
|
||||
-- Start session and refresh it if needed
|
||||
local client_session, err, exists, refreshed = session.start()
|
||||
if err then
|
||||
return false, "can't open session : " .. err, nil, nil
|
||||
end
|
||||
-- Refresh it
|
||||
if exists then
|
||||
local ok, err = client_session:refresh()
|
||||
if err then
|
||||
return false, "can't refresh session : " .. err, nil, nil
|
||||
end
|
||||
return true, "session exists", nil, nil
|
||||
end
|
||||
return true, "session doesn't exist", nil, nil
|
||||
return true, "sessions init successful"
|
||||
end
|
||||
|
||||
return _M
|
Loading…
Reference in New Issue