bunkerweb 1.4.0

This commit is contained in:
bunkerity
2022-06-03 17:24:14 +02:00
parent 3a078326c5
commit a9f886804a
5245 changed files with 1432051 additions and 27894 deletions

View File

@@ -1,100 +1,85 @@
local M = {}
local api_list = {}
local iputils = require "resty.iputils"
local datastore = require "datastore"
local utils = require "utils"
local cjson = require "cjson"
local plugins = require "plugins"
local upload = require "resty.upload"
local logger = require "logger"
api_list["^/ping$"] = function ()
return true
local api = {global = {GET = {}, POST = {}, PUT = {}, DELETE = {}}}
api.response = function(self, http_status, api_status, msg)
local resp = {}
resp["status"] = api_status
resp["msg"] = msg
return http_status, resp
end
api_list["^/reload$"] = function ()
local jobs = true
local file = io.open("/etc/nginx/global.env", "r")
for line in file:lines() do
if line == "KUBERNETES_MODE=yes" or line == "SWARM_MODE=yes" then
jobs = false
break
end
api.global.GET["^/ping$"] = function(api)
return api:response(ngx.HTTP_OK, "success", "pong")
end
api.global.POST["^/jobs$"] = function(api)
-- ngx.req.read_body()
-- local data = ngx.req.get_body_data()
-- if not data then
-- local data_file = ngx.req.get_body_file()
-- if data_file then
-- local file = io.open(data_file)
-- data = file:read("*a")
-- file:close()
-- end
-- end
-- local ok, env = pcall(cjson.decode, data)
-- if not ok then
-- return api:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't decode JSON : " .. env)
-- end
-- local file = io.open("/opt/bunkerweb/tmp/jobs.env", "w+")
-- for k, v in pairs(env) do
-- file:write(k .. "=" .. v .. "\n")
-- end
-- file:close()
local status = os.execute("/opt/bunkerweb/helpers/scheduler-restart.sh")
if status == 0 then
return api:response(ngx.HTTP_OK, "success", "jobs executed and scheduler started")
end
file:close()
if jobs then
os.execute("/opt/bunkerized-nginx/entrypoint/jobs.sh")
return api:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
end
api.global.POST["^/reload$"] = function(api)
local status = os.execute("/usr/sbin/nginx -s reload")
if status == 0 then
return api:response(ngx.HTTP_OK, "success", "reload successful")
end
return os.execute("/usr/sbin/nginx -s reload") == 0
return api:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
end
api_list["^/stop$"] = function ()
return os.execute("/usr/sbin/nginx -s quit") == 0
end
api_list["^/stop%-temp$"] = function ()
return os.execute("/usr/sbin/nginx -c /tmp/nginx-temp.conf -s stop") == 0
end
api_list["^/conf$"] = function ()
if not M.save_file("/tmp/conf.tar.gz") then
return false
api.global.POST["^/stop$"] = function(api)
local status = os.execute("/usr/sbin/nginx -s quit")
if status == 0 then
return api:response(ngx.HTTP_OK, "success", "stop successful")
end
return M.extract_file("/tmp/conf.tar.gz", "/etc/nginx/")
return api:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
end
api_list["^/letsencrypt$"] = function ()
if not M.save_file("/tmp/letsencrypt.tar.gz") then
return false
api.global.POST["^/confs$"] = function(api)
local tmp = "/opt/bunkerweb/tmp/api_" .. ngx.var.uri:sub(2) .. ".tar.gz"
local destination = "/opt/bunkerweb/" .. ngx.var.uri:sub(2)
if ngx.var.uri == "/confs" then
destination = "/etc/nginx"
elseif ngx.var.uri == "/data" then
destination = "/data"
end
return M.extract_file("/tmp/letsencrypt.tar.gz", "/etc/letsencrypt/")
end
api_list["^/acme$"] = function ()
if not M.save_file("/tmp/acme.tar.gz") then
return false
end
return M.extract_file("/tmp/acme.tar.gz", "/acme-challenge")
end
api_list["^/http$"] = function ()
if not M.save_file("/tmp/http.tar.gz") then
return false
end
return M.extract_file("/tmp/http.tar.gz", "/http-confs/")
end
api_list["^/server$"] = function ()
if not M.save_file("/tmp/server.tar.gz") then
return false
end
return M.extract_file("/tmp/server.tar.gz", "/server-confs/")
end
api_list["^/modsec$"] = function ()
if not M.save_file("/tmp/modsec.tar.gz") then
return false
end
return M.extract_file("/tmp/modsec.tar.gz", "/modsec-confs/")
end
api_list["^/modsec%-crs$"] = function ()
if not M.save_file("/tmp/modsec-crs.tar.gz") then
return false
end
return M.extract_file("/tmp/modsec-crs.tar.gz", "/modsec-crs-confs/")
end
function M.save_file (name)
local form, err = upload:new(4096)
if not form then
logger.log(ngx.ERR, "API", err)
return false
return api:response(ngx.HTTP_BAD_REQUEST, "error", err)
end
form:set_timeout(1000)
local file = io.open(name, "w")
local file = io.open(tmp, "w+")
while true do
local typ, res, err = form:read()
if not typ then
file:close()
logger.log(ngx.ERR, "API", "not typ")
return false
return api:response(ngx.HTTP_BAD_REQUEST, "error", err)
end
if typ == "eof" then
break
@@ -105,31 +90,86 @@ function M.save_file (name)
end
file:flush()
file:close()
return true
local status = os.execute("rm -rf " .. destination .. "/*")
if status ~= 0 then
return api:response(ngx.HTTP_BAD_REQUEST, "error", "can't remove old files")
end
status = os.execute("tar xzf " .. tmp .. " -C " .. destination)
if status ~= 0 then
return api:response(ngx.HTTP_BAD_REQUEST, "error", "can't extract archive")
end
return api:response(ngx.HTTP_OK, "success", "saved data at " .. destination)
end
function M.extract_file(archive, destination)
return os.execute("tar xzf " .. archive .. " -C " .. destination) == 0
api.global.POST["^/data$"] = api.global.POST["^/confs$"]
api.global.POST["^/unban$"] = function(api)
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then
local data_file = ngx.req.get_body_file()
if data_file then
local file = io.open(data_file)
data = file:read("*a")
file:close()
end
end
local ok, ip = pcall(cjson.decode, data)
if not ok then
return api:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't decode JSON : " .. env)
end
datastore:delete("bans_ip_" .. ip["ip"])
return api:response(ngx.HTTP_OK, "success", "ip " .. ip["ip"] .. " unbanned")
end
function M.is_api_call (api_uri, api_whitelist_ip)
local whitelist = iputils.parse_cidrs(api_whitelist_ip)
if iputils.ip_in_cidrs(ngx.var.remote_addr, whitelist) and ngx.var.request_uri:sub(1, #api_uri) .. "/" == api_uri .. "/" then
for uri, code in pairs(api_list) do
if string.match(ngx.var.request_uri:sub(#api_uri + 1), uri) then
return true
api.is_allowed_ip = function(self)
local data, err = 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.var.remote_addr, cjson.decode(data).data) then
return true, "ok"
end
return false, "IP is not in API_WHITELIST_IP"
end
api.do_api_call = function(self)
if self.global[ngx.var.request_method] ~= nil then
for uri, api_fun in pairs(self.global[ngx.var.request_method]) do
if string.match(ngx.var.uri, uri) then
local status, resp = api_fun(self)
local ret = true
if status ~= ngx.HTTP_OK then
ret = false
end
return ret, resp["msg"], status, cjson.encode(resp)
end
end
end
return false
end
function M.do_api_call (api_uri)
for uri, code in pairs(api_list) do
if string.match(ngx.var.request_uri:sub(#api_uri + 1), uri) then
return code()
local list, err = plugins:list()
if not list then
local status, resp = self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "can't list loaded plugins : " .. err)
return false, resp["msg"], ngx.HTTP_INTERNAL_SERVER_ERROR, resp
end
for i, plugin in ipairs(list) do
if pcall(require, plugin.id .. "/" .. plugin.id) then
local plugin_lua = require(plugin.id .. "/" .. plugin.id)
if plugin_lua.api ~= nil then
local matched, status, resp = plugin_lua.api()
if matched then
local ret = true
if status ~= ngx.HTTP_OK then
ret = false
end
return ret, resp["msg"], status, cjson.encode(resp)
end
end
end
end
local resp = {}
resp["status"] = "error"
resp["msg"] = "not found"
return false, "error", ngx.HTTP_NOT_FOUND, cjson.encode(resp)
end
return M
return api

View File

@@ -1,35 +0,0 @@
local M = {}
local logger = require "logger"
function M.is_banned ()
return ngx.shared.behavior_ban:get(ngx.var.remote_addr) == true
end
function M.count (status_codes, threshold, count_time, ban_time)
for k, v in ipairs(status_codes) do
if v == tostring(ngx.status) then
local count = ngx.shared.behavior_count:get(ngx.var.remote_addr)
if count == nil then
count = 0
end
count = count + 1
local ok, err = ngx.shared.behavior_count:set(ngx.var.remote_addr, count, count_time)
if not ok then
logger.log(ngx.ERR, "BEHAVIOR", "not enough memory allocated to behavior_ip_count")
return false
end
if count >= threshold then
logger.log(ngx.WARN, "BEHAVIOR", "threshold reached for " .. ngx.var.remote_addr .. " (" .. count .. " / " .. threshold .. ") : IP is banned for " .. ban_time .. " seconds")
local ok, err = ngx.shared.behavior_ban:safe_set(ngx.var.remote_addr, true, ban_time)
if not ok then
logger.log(ngx.ERR, "BEHAVIOR", "not enough memory allocated to behavior_ip_ban")
return false
end
return true
end
return false
end
end
end
return M

View File

@@ -1,52 +0,0 @@
local M = {}
local dns = require "dns"
local iputils = require "resty.iputils"
local logger = require "logger"
function M.ip_cached_ko ()
return ngx.shared.blacklist_ip_cache:get(ngx.var.remote_addr) == "ko"
end
function M.reverse_cached_ko ()
return ngx.shared.blacklist_reverse_cache:get(ngx.var.remote_addr) == "ko"
end
function M.ip_cached ()
return ngx.shared.blacklist_ip_cache:get(ngx.var.remote_addr) ~= nil
end
function M.reverse_cached ()
return ngx.shared.blacklist_reverse_cache:get(ngx.var.remote_addr) ~= nil
end
function M.check_ip (ip_list)
if #ip_list > 0 then
local blacklist = iputils.parse_cidrs(ip_list)
if iputils.ip_in_cidrs(ngx.var.remote_addr, blacklist) then
ngx.shared.blacklist_ip_cache:set(ngx.var.remote_addr, "ko", 86400)
logger.log(ngx.WARN, "BLACKLIST", "ip " .. ngx.var.remote_addr .. " is in blacklist")
return true
end
end
ngx.shared.blacklist_ip_cache:set(ngx.var.remote_addr, "ok", 86400)
return false
end
function M.check_reverse (reverse_list, resolvers)
if #reverse_list > 0 then
local rdns = dns.get_reverse(resolvers)
if rdns ~= "" then
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
ngx.shared.blacklist_reverse_cache:set(ngx.var.remote_addr, "ko", 86400)
logger.log(ngx.WARN, "BLACKLIST", "reverse " .. rdns .. " is in blacklist")
return true
end
end
end
end
ngx.shared.blacklist_reverse_cache:set(ngx.var.remote_addr, "ok", 86400)
return false
end
return M

View File

@@ -1,34 +0,0 @@
local M = {}
local captcha = require "misc.captcha"
local base64 = require "misc.base64"
function M.get_challenge ()
local cap = captcha.new()
cap:font("/opt/bunkerized-nginx/lua/misc/Vera.ttf")
cap:generate()
return cap:jpegStr(70), cap:getStr()
end
function M.get_code (img, antibot_uri)
-- get template
local f = io.open("/opt/bunkerized-nginx/antibot/captcha.html", "r")
local template = f:read("*all")
f:close()
-- get captcha code
f = io.open("/opt/bunkerized-nginx/antibot/captcha.data", "r")
local captcha_data = f:read("*all")
f:close()
-- edit captcha code
captcha_data = string.format(captcha_data, antibot_uri, base64.encode(img))
-- return template + edited captcha code
return template:gsub("%%CAPTCHA%%", captcha_data)
end
function M.check (captcha_user, captcha_valid)
return captcha_user == captcha_valid
end
return M

View File

@@ -1,52 +0,0 @@
local M = {}
local redis = require "resty.redis"
local mt = { __index = M }
function M.new(self, name, data_dict, redis_client, type)
return setmetatable({
__name = name,
__data_dict = data_dict,
__redis_client = redis_client,
__type = type
}, mt)
end
function M.check(self, data)
-- without redis
if self.__redis_client == nil then
if self.__type == "simple" then
local value, flags = self.__data_dict:get(data)
return value ~= nil
elseif self.__type == "match" then
local patterns = self.__data_dict:get_keys(0)
for i, pattern in ipairs(patterns) do
if string.match(data, pattern) then
return true
end
end
end
-- with redis
else
if self.__type == "simple" then
local res, err = self.__redis_client:get(self.__name .. "_" .. data)
return res and res ~= ngx.null
elseif self.__type == "match" then
local patterns = self.__redis_client:keys(self.__name .. "_*")
if patterns then
for i, pattern in ipairs(patterns) do
local real_pattern = string.gsub(pattern, self.__name:gsub("%-", "%%-") .. "_", "", 1)
if string.match(data, real_pattern) then
return true
end
end
end
end
end
return false
end
return M

View File

@@ -1,32 +0,0 @@
local M = {}
local session = require "resty.session"
function M.session ()
if not ngx.ctx.session then
ngx.ctx.session = session:start()
end
return ngx.ctx.session
end
function M.is_set (key)
local s = M.session()
if s.data[key] then
return true
end
return false
end
function M.set (values)
local s = M.session()
for k, v in pairs(values) do
s.data[k] = v
end
s:save()
end
function M.get (key)
local s = M.session ()
return s.data[key]
end
return M

View File

@@ -1,70 +0,0 @@
local M = {}
local iputils = require "resty.iputils"
local logger = require "logger"
function M.flush_dict (dict)
local keys = dict:get_keys(0)
for i, key in ipairs(keys) do
dict:delete(key)
end
end
function M.load_ip (path, dict)
M.flush_dict(dict)
local file = io.open(path, "r")
if not file then
logger.log(ngx.ERR, "INIT", "can't open " .. path)
else
io.input(file)
local i = 0
for line in io.lines() do
local continue = true
if string.match(line, "/") then
local lower, upper = iputils.parse_cidr(line)
local bin_ip = lower
while bin_ip <= upper do
local ok, err = dict:safe_set(bin_ip, true, 0)
if not ok then
logger.log(ngx.ERR, "INIT", "not enough memory allocated to load data from " .. path)
continue = false
break
end
bin_ip = bin_ip + 1
i = i + 1
end
else
local bin_ip, bin_octets = iputils.ip2bin(line)
dict:set(bin_ip, true, 0)
i = i + 1
end
if not continue then
break
end
end
logger.log(ngx.ERR, "INIT", "*NOT AN ERROR* loaded " .. tostring(i) .. " IPs from " .. path)
io.close(file)
end
end
function M.load_raw (path, dict)
M.flush_dict(dict)
local file = io.open(path, "r")
if not file then
logger.log(ngx.ERR, "INIT", "can't open " .. path)
else
io.input(file)
local i = 0
for line in io.lines() do
local ok, err = dict:safe_set(line, true, 0)
if not ok then
logger.log(ngx.ERR, "INIT", "not enough memory allocated to load data from " .. path)
break
end
i = i + 1
end
logger.log(ngx.ERR, "INIT", "*NOT AN ERROR* loaded " .. tostring(i) .. " entries from " .. path)
io.close(file)
end
end
return M

35
lua/datastore.lua Normal file
View File

@@ -0,0 +1,35 @@
local datastore = {dict = ngx.shared.datastore }
datastore.get = function(self, key)
local value, err = self.dict:get(key)
if not value and not err then
err = "not found"
end
return value, err
end
datastore.set = function(self, key, value, exptime)
exptime = exptime or 0
return self.dict:safe_set(key, value, exptime)
end
datastore.keys = function(self)
return self.dict:get_keys(0)
end
datastore.delete = function(self, key)
self.dict:delete(key)
return true, "success"
end
datastore.delete_all = function(self, pattern)
local keys = self.dict:get_keys(0)
for i, key in ipairs(keys) do
if key:match(pattern) then
self.dict:delete(key)
end
end
return true, "success"
end
return datastore

View File

@@ -1,43 +0,0 @@
local M = {}
local resolver = require "resty.dns.resolver"
function M.get_reverse(resolvers)
local r, err = resolver:new{nameservers=resolvers, retrans=2, timeout=2000}
if not r then
return ""
end
local rdns = ""
local answers, err = r:reverse_query(ngx.var.remote_addr)
if answers ~= nil and not answers.errcode then
for ak, av in ipairs(answers) do
if av.ptrdname then
rdns = av.ptrdname
break
end
end
end
return rdns
end
function M.get_ips(fqdn, resolvers)
local r, err = resolver:new{nameservers=resolvers, retrans=2, timeout=2000}
if not r then
return ""
end
local ips = {}
local answers, err, tries = r:query(fqdn, nil, {})
if answers ~= nil then
for ak, av in ipairs(answers) do
if av.address then
table.insert(ips, av.address)
end
end
end
return ips
end
function M.ip_to_arpa()
return resolver.arpa_str(ngx.var.remote_addr):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "")
end
return M

View File

@@ -1,37 +0,0 @@
local M = {}
local dns = require "dns"
local logger = require "logger"
local iputils = require "resty.iputils"
function M.cached_ko ()
return ngx.shared.dnsbl_cache:get(ngx.var.remote_addr) == "ko"
end
function M.cached ()
return ngx.shared.dnsbl_cache:get(ngx.var.remote_addr) ~= nil
end
function M.check (dnsbls, resolvers)
local local_ips = iputils.parse_cidrs({"127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "10.0.0.0/8"})
if iputils.ip_in_cidrs(ngx.var.remote_addr, local_ips) then
ngx.shared.dnsbl_cache:set(ngx.var.remote_addr, "ok", 86400)
return false
end
local rip = dns.ip_to_arpa()
for k, v in ipairs(dnsbls) do
local req = rip .. "." .. v
local ips = dns.get_ips(req, resolvers)
for k2, v2 in ipairs(ips) do
local a,b,c,d = v2:match("([%d]+).([%d]+).([%d]+).([%d]+)")
if a == "127" then
ngx.shared.dnsbl_cache:set(ngx.var.remote_addr, "ko", 86400)
logger.log(ngx.WARN, "DNSBL", "ip " .. ngx.var.remote_addr .. " is in DNSBL " .. v)
return true
end
end
end
ngx.shared.dnsbl_cache:set(ngx.var.remote_addr, "ok", 86400)
return false
end
return M

View File

@@ -1,43 +0,0 @@
local M = {}
local session = require "resty.session"
function M.get_challenge ()
local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ0123456789"
math.randomseed(os.clock()*os.time())
local random = ""
local rand = 0
for i = 1, 20 do
rand = math.random(1, #charset)
random = random .. charset:sub(rand, rand)
end
return random
end
function M.get_code (challenge, antibot_uri, original_uri)
-- get template
local f = io.open("/opt/bunkerized-nginx/antibot/javascript.html", "r")
local template = f:read("*all")
f:close()
-- get JS code
f = io.open("/opt/bunkerized-nginx/antibot/javascript.data", "r")
local javascript = f:read("*all")
f:close()
-- edit JS code
javascript = string.format(javascript, challenge, antibot_uri, original_uri)
-- return template + edited JS code
return template:gsub("%%JAVASCRIPT%%", javascript)
end
function M.check (challenge, user)
local resty_sha256 = require "resty.sha256"
local str = require "resty.string"
local sha256 = resty_sha256:new()
sha256:update(challenge .. user)
local digest = sha256:final()
return str.to_hex(digest):find("^0000") ~= nil
end
return M

View File

@@ -1,72 +0,0 @@
local M = {}
local logger = require "logger"
function M.decr (key, delay)
local function callback (premature, key)
if premature then
ngx.shared.limit_req:delete(key)
return
end
local value, flags = ngx.shared.limit_req:get(key)
if value ~= nil then
if value - 1 == 0 then
ngx.shared.limit_req:delete(key)
return
end
ngx.shared.limit_req:set(key, value-1, 0)
end
end
local ok, err = ngx.timer.at(delay, callback, key)
if not ok then
logger.log(ngx.ERR, "REQ LIMIT", "can't setup decrement timer : " .. err)
return false
end
return true
end
function M.incr (key)
local newval, err, forcible = ngx.shared.limit_req:incr(key, 1, 0, 0)
if not newval then
logger.log(ngx.ERR, "REQ LIMIT", "can't increment counter : " .. err)
return false
end
return true
end
function M.check (rate, burst, sleep)
local key = ngx.var.remote_addr .. ngx.var.uri
local rate_split = {}
for str in rate:gmatch("([^r/]+)") do
table.insert(rate_split, str)
end
local max = tonumber(rate_split[1])
local unit = rate_split[2]
local delay = 0
if unit == "s" then
delay = 1
elseif unit == "m" then
delay = 60
elseif unit == "h" then
delay = 3600
elseif unit == "d" then
delay = 86400
end
if M.incr(key) then
local current, flags = ngx.shared.limit_req:get(key)
if M.decr(key, delay) then
if current > max + burst then
logger.log(ngx.WARN, "REQ LIMIT", "ip " .. ngx.var.remote_addr .. " has reached the limit for uri " .. ngx.var.uri .. " : " .. current .. "r/" .. unit .. " (max = " .. rate .. ")")
return true
elseif current > max then
if sleep > 0 then
ngx.sleep(sleep)
end
end
else
ngx.shared.limit_req:set(key, current-1, 0)
end
end
return false
end
return M

View File

@@ -1,4 +1,4 @@
local M = {}
local M = {}
local errlog = require "ngx.errlog"
function M.log (level, prefix, msg)
@@ -6,3 +6,4 @@ function M.log (level, prefix, msg)
end
return M

Binary file not shown.

View File

@@ -1,202 +0,0 @@
--[[
base64 -- v1.5.2 public domain Lua base64 encoder/decoder
no warranty implied; use at your own risk
Needs bit32.extract function. If not present it's implemented using BitOp
or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
implementation inspired by Rici Lake's post:
http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
author: Ilya Kolbin (iskolbin@gmail.com)
url: github.com/iskolbin/lbase64
COMPATIBILITY
Lua 5.1, 5.2, 5.3, LuaJIT
LICENSE
See end of file for license information.
--]]
local base64 = {}
local extract = _G.bit32 and _G.bit32.extract
if not extract then
if _G.bit then
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
extract = function( v, from, width )
return band( shr( v, from ), shl( 1, width ) - 1 )
end
elseif _G._VERSION >= "Lua 5.3" then
extract = load[[return function( v, from, width )
return ( v >> from ) & ((1 << width) - 1)
end]]()
else
extract = function( v, from, width )
local w = 0
local flag = 2^from
for i = 0, width-1 do
local flag2 = flag + flag
if v % flag2 >= flag then
w = w + 2^i
end
flag = flag2
end
return w
end
end
end
function base64.makeencoder( s62, s63, spad )
local encoder = {}
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
encoder[b64code] = char:byte()
end
return encoder
end
function base64.makedecoder( s62, s63, spad )
local decoder = {}
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
decoder[charcode] = b64code
end
return decoder
end
local DEFAULT_ENCODER = base64.makeencoder()
local DEFAULT_DECODER = base64.makedecoder()
local char, concat = string.char, table.concat
function base64.encode( str, encoder, usecaching )
encoder = encoder or DEFAULT_ENCODER
local t, k, n = {}, 1, #str
local lastn = n % 3
local cache = {}
for i = 1, n-lastn, 3 do
local a, b, c = str:byte( i, i+2 )
local v = a*0x10000 + b*0x100 + c
local s
if usecaching then
s = cache[v]
if not s then
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
cache[v] = s
end
else
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
end
t[k] = s
k = k + 1
end
if lastn == 2 then
local a, b = str:byte( n-1, n )
local v = a*0x10000 + b*0x100
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
elseif lastn == 1 then
local v = str:byte( n )*0x10000
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
end
return concat( t )
end
function base64.decode( b64, decoder, usecaching )
decoder = decoder or DEFAULT_DECODER
local pattern = '[^%w%+%/%=]'
if decoder then
local s62, s63
for charcode, b64code in pairs( decoder ) do
if b64code == 62 then s62 = charcode
elseif b64code == 63 then s63 = charcode
end
end
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
end
b64 = b64:gsub( pattern, '' )
local cache = usecaching and {}
local t, k = {}, 1
local n = #b64
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
for i = 1, padding > 0 and n-4 or n, 4 do
local a, b, c, d = b64:byte( i, i+3 )
local s
if usecaching then
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
s = cache[v0]
if not s then
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
cache[v0] = s
end
else
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
end
t[k] = s
k = k + 1
end
if padding == 1 then
local a, b, c = b64:byte( n-3, n-1 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
t[k] = char( extract(v,16,8), extract(v,8,8))
elseif padding == 2 then
local a, b = b64:byte( n-3, n-2 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000
t[k] = char( extract(v,16,8))
end
return concat( t )
end
return base64
--[[
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2018 Ilya Kolbin
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
--]]

View File

@@ -1,193 +0,0 @@
-- Copyright startx <startx@plentyfact.org>
-- Modifications copyright mrDoctorWho <mrdoctorwho@gmail.com>
-- Published under the MIT license
-- module("captcha", package.seeall)
local M = {}
local gd = require 'gd'
local mt = { __index = {} }
function M.new()
local cap = {}
local f = setmetatable({ cap = cap}, mt)
return f
end
local function urandom()
local seed = 1
local devurandom = io.open("/dev/urandom", "r")
local urandom = devurandom:read(32)
devurandom:close()
for i=1,string.len(urandom) do
local s = string.byte(urandom,i)
seed = seed + s
end
return seed
end
local function random_char(length)
local set, char, uid
local set = [[1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]]
local captcha_t = {}
math.randomseed(urandom())
for c=1,length do
local i = math.random(1, string.len(set))
table.insert(captcha_t, string.sub(set,i,i))
end
return captcha_t
end
local function random_angle()
math.randomseed(urandom())
return math.random(-20, 40)
end
local function scribble(w,h)
math.randomseed(urandom())
local x1 = math.random(5, w - 5)
local x2 = math.random(5, w - 5)
return x1, x2
end
function mt.__index:string(s)
self.cap.string = s
end
function mt.__index:scribble(n)
self.cap.scribble = n or 20
end
function mt.__index:length(l)
self.cap.length = l
end
function mt.__index:bgcolor(r,g,b)
self.cap.bgcolor = { r = r , g = g , b = b}
end
function mt.__index:fgcolor(r,g,b)
self.cap.fgcolor = { r = r , g = g , b = b}
end
function mt.__index:line(line)
self.cap.line = line
end
function mt.__index:font(font)
self.cap.font = font
end
function mt.__index:generate()
--local self.captcha = {}
local captcha_t = {}
if not self.cap.string then
if not self.cap.length then
self.cap.length = 6
end
captcha_t = random_char(self.cap.length)
self:string(table.concat(captcha_t))
else
for i=1, #self.cap.string do
table.insert(captcha_t, string.sub(self.cap.string, i, i))
end
end
self.im = gd.createTrueColor(#captcha_t * 40, 45)
local black = self.im:colorAllocate(0, 0, 0)
local white = self.im:colorAllocate(255, 255, 255)
local bgcolor
if not self.cap.bgcolor then
bgcolor = white
else
bgcolor = self.im:colorAllocate(self.cap.bgcolor.r , self.cap.bgcolor.g, self.cap.bgcolor.b )
end
local fgcolor
if not self.cap.fgcolor then
fgcolor = black
else
fgcolor = self.im:colorAllocate(self.cap.fgcolor.r , self.cap.fgcolor.g, self.cap.fgcolor.b )
end
self.im:filledRectangle(0, 0, #captcha_t * 40, 45, bgcolor)
local offset_left = 10
for i=1, #captcha_t do
local angle = random_angle()
local llx, lly, lrx, lry, urx, ury, ulx, uly = self.im:stringFT(fgcolor, self.cap.font, 25, math.rad(angle), offset_left, 35, captcha_t[i])
self.im:polygon({ {llx, lly}, {lrx, lry}, {urx, ury}, {ulx, uly} }, bgcolor)
offset_left = offset_left + 40
end
if self.cap.line then
self.im:line(10, 10, ( #captcha_t * 40 ) - 10 , 40, fgcolor)
self.im:line(11, 11, ( #captcha_t * 40 ) - 11 , 41, fgcolor)
self.im:line(12, 12, ( #captcha_t * 40 ) - 12 , 42, fgcolor)
end
if self.cap.scribble then
for i=1,self.cap.scribble do
local x1,x2 = scribble( #captcha_t * 40 , 45 )
self.im:line(x1, 5, x2, 40, fgcolor)
end
end
end
-- Perhaps it's not the best solution
-- Writes the generated image to a jpeg file
function mt.__index:jpeg(outfile, quality)
self.im:jpeg(outfile, quality)
end
-- Writes the generated image to a png file
function mt.__index:png(outfile)
self.im:png(outfile)
end
-- Allows to get the image data in PNG format
function mt.__index:pngStr()
return self.im:pngStr()
end
-- Allows to get the image data in JPEG format
function mt.__index:jpegStr(quality)
return self.im:jpegStr(quality)
end
-- Allows to get the image text
function mt.__index:getStr()
return self.cap.string
end
-- Writes the image to a file
function mt.__index:write(outfile, quality)
if self.cap.string == nil then
self:generate()
end
self:jpeg(outfile, quality)
-- Compatibility
return self:getStr()
end
return M

File diff suppressed because it is too large Load Diff

6
lua/mmdb.lua Normal file
View File

@@ -0,0 +1,6 @@
local geoip = require "geoip.mmdb"
return {
country_db = geoip.load_database("/opt/bunkerweb/cache/country.mmdb"),
asn_db = geoip.load_database("/opt/bunkerweb/cache/asn.mmdb")
}

68
lua/plugins.lua Normal file
View File

@@ -0,0 +1,68 @@
local datastore = require "datastore"
local cjson = require "cjson"
local plugins = {}
plugins.load = function(self, path)
-- Read plugin.json file
local file = io.open(path .. "/plugin.json")
if not file then
return false, "can't read plugin.json file"
end
-- Decode plugin.json
-- TODO : check return value of file:read and cjson.encode
local data = cjson.decode(file:read("*a"))
file:close()
-- Check required fields
local required_fields = {"id", "order", "name", "description", "version", "settings"}
for i, field in ipairs(required_fields) do
if data[field] == nil then
return false, "missing field " .. field .. " in plugin.json"
end
-- TODO : check values and types with regex
end
-- Get existing plugins
local list, err = plugins:list()
if not list then
return false, err
end
-- Add our plugin to existing list and sort it
table.insert(list, data)
table.sort(list, function (a, b)
return a.order < b.order
end)
-- Save new plugin list in datastore
local ok, err = datastore:set("plugins", cjson.encode(list))
if not ok then
return false, "can't save new plugin list"
end
-- Save default settings value
for variable, value in pairs(data.settings) do
ok, err = datastore:set("plugin_" .. data.id .. "_" .. variable, value["default"])
if not ok then
return false, "can't save default variable value of " .. variable .. " into datastore"
end
end
-- Return the plugin
return data, "success"
end
plugins.list = function(self)
-- Get encoded plugins from datastore
local encoded_plugins, err = datastore:get("plugins")
if not encoded_plugins then
return false, "can't get encoded plugins from datastore"
end
-- Decode and return the list
return cjson.decode(encoded_plugins), "success"
end
return plugins

View File

@@ -1,44 +0,0 @@
local M = {}
local http = require "resty.http"
local cjson = require "cjson"
function M.get_code (antibot_uri, recaptcha_sitekey)
-- get template
local f = io.open("/opt/bunkerized-nginx/antibot/recaptcha.html", "r")
local template = f:read("*all")
f:close()
-- get recaptcha code
f = io.open("/opt/bunkerized-nginx/antibot/recaptcha-head.data", "r")
local recaptcha_head = f:read("*all")
f:close()
f = io.open("/opt/bunkerized-nginx/antibot/recaptcha-body.data", "r")
local recaptcha_body = f:read("*all")
f:close()
-- edit recaptcha code
recaptcha_head = string.format(recaptcha_head, recaptcha_sitekey)
recaptcha_body = string.format(recaptcha_body, antibot_uri, recaptcha_sitekey)
-- return template + edited recaptcha code
return template:gsub("%%RECAPTCHA_HEAD%%", recaptcha_head):gsub("%%RECAPTCHA_BODY%%", recaptcha_body)
end
function M.check (token, recaptcha_secret)
local httpc = http.new()
local res, err = httpc:request_uri("https://www.google.com/recaptcha/api/siteverify", {
method = "POST",
body = "secret=" .. recaptcha_secret .. "&response=" .. token .. "&remoteip=" .. ngx.var.remote_addr,
headers = { ["Content-Type"] = "application/x-www-form-urlencoded" }
})
if not res then
return 0.0
end
local data = cjson.decode(res.body)
if not data.success then
return 0.0
end
return data.score
end
return M

View File

@@ -1,104 +0,0 @@
local M = {}
local http = require "resty.http"
local cjson = require "cjson"
local logger = require "logger"
function M.send(method, url, data)
local httpc, err = http.new()
if not httpc then
logger.log(ngx.ERR, "REMOTE API", "Can't instantiate HTTP object : " .. err)
return false, nil, nil
end
local res, err = httpc:request_uri(ngx.shared.remote_api:get("server") .. url, {
method = method,
body = cjson.encode(data),
headers = {
["Content-Type"] = "application/json",
["User-Agent"] = "bunkerized-nginx/" .. data["version"]
}
})
if not res then
logger.log(ngx.ERR, "REMOTE API", "Can't send HTTP request : " .. err)
return false, nil, nil
end
if res.status ~= 200 then
logger.log(ngx.WARN, "REMOTE API", "Received status " .. res.status .. " from API : " .. res.body)
end
return true, res.status, cjson.decode(res.body)["data"]
end
function M.gen_data(use_id, data)
local all_data = {}
if use_id then
all_data["id"] = ngx.shared.remote_api:get("id")
end
all_data["version"] = ngx.shared.remote_api:get("version")
for k, v in pairs(data) do
all_data[k] = v
end
return all_data
end
function M.ping2()
local https = require "ssl.https"
local ltn12 = require "ltn12"
local request_body = cjson.encode(M.gen_data(true, {}))
local response_body = {}
local res, code, headers, status = https.request {
url = ngx.shared.remote_api:get("server") .. "/ping",
method = "GET",
headers = {
["Content-Type"] = "application/json",
["User-Agent"] = "bunkerized-nginx/" .. ngx.shared.remote_api:get("version"),
["Content-Length"] = request_body:len()
},
source = ltn12.source.string(request_body),
sink = ltn12.sink.table(response_body)
}
if res and status:match("^.*% 200% .*$") then
response_body = cjson.decode(response_body[1])
return response_body["data"] == "pong"
end
return false
end
function M.register()
local request = {}
local res, status, data = M.send("POST", "/register", M.gen_data(false, request))
if res and status == 200 then
return true, data
end
return false, data
end
function M.ping()
local request = {}
local res, status, data = M.send("GET", "/ping", M.gen_data(true, request))
if res and status == 200 then
return true, data
end
return false, data
end
function M.ip(ip, reason)
local request = {
["ip"] = ip,
["reason"] = reason
}
local res, status, data = M.send("POST", "/ip", M.gen_data(true, request))
if res and status == 200 then
return true, data
end
return false, data
end
function M.db()
local request = {}
local res, status, data = M.send("GET", "/db", M.gen_data(true, request))
if res and status == 200 then
return true, data
end
return false, data
end
return M

346
lua/utils.lua Normal file
View File

@@ -0,0 +1,346 @@
local datastore = require "datastore"
local ipmatcher = require "resty.ipmatcher"
local cjson = require "cjson"
local resolver = require "resty.dns.resolver"
local mmdb = require "mmdb"
local logger = require "logger"
local utils = {}
utils.set_values = function()
local 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"
}
local ok, err = datastore:set("misc_reserved_ips", cjson.encode({data = reserved_ips}))
if not ok then
return false, err
end
local var_resolvers, err = datastore:get("variable_DNS_RESOLVERS")
if not var_resolvers then
return false, err
end
local list_resolvers = {}
for str_resolver in var_resolvers:gmatch("%S+") do
table.insert(list_resolvers, str_resolver)
end
ok, err = datastore:set("misc_resolvers", cjson.encode(list_resolvers))
if not ok then
return false, err
end
return true, "success"
end
utils.get_variable = function(var, site_search)
if site_search == nil then
site_search = true
end
local value, err = datastore:get("variable_" .. var)
if not value then
return nil, "Can't access variable " .. var .. " from datastore : " .. err
end
if site_search then
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "Can't access variable MULTISITE from datastore : " .. err
end
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
end
return value, "success"
end
utils.has_variable = function(var, value)
local check_value, err = datastore:get("variable_" .. var)
if not value then
return nil, "Can't access variable " .. var .. " from datastore : " .. err
end
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "Can't access variable MULTISITE from datastore : " .. err
end
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
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
return true, "success"
end
end
return false, "success"
end
return check_value == value, "success"
end
utils.has_not_variable = function(var, value)
local check_value, err = datastore:get("variable_" .. var)
if not value then
return nil, "Can't access variable " .. var .. " from datastore : " .. err
end
local multisite, err = datastore:get("variable_MULTISITE")
if not multisite then
return nil, "Can't access variable MULTISITE from datastore : " .. err
end
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
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
return true, "success"
end
end
return false, "success"
end
return check_value ~= value, "success"
end
function utils.get_multiple_variables(vars)
local keys = datastore:keys()
local result = {}
for i, key in ipairs(keys) do
for j, var in ipairs(vars) do
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)
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
return result
end
utils.is_ip_in_networks = function(ip, networks)
local ipm, err = ipmatcher.new(networks)
if not ipm then
return nil, "can't instantiate ipmatcher : " .. err
end
local matched, err = ipm:match(ip)
if err then
return nil, "can't check ip : " .. err
end
return matched
end
utils.is_ipv4 = function(ip)
return ipmatcher.parse_ipv4(ip)
end
utils.is_ipv6 = function(ip)
return ipmatcher.parse_ipv6(ip)
end
utils.ip_is_global = function(ip)
local data, err = datastore:get("misc_reserved_ips")
if not data then
return nil, "can't get reserved ips : " .. err
end
local ok, reserved_ips = pcall(cjson.decode, data)
if not ok then
return nil, "can't decode json : " .. reserved_ips
end
local ipm, err = ipmatcher.new(reserved_ips.data)
if not ipm then
return nil, "can't instantiate ipmatcher : " .. err
end
local matched, err = ipm:match(ip)
if err then
return nil, "can't check ip : " .. err
end
return not matched, "success"
end
utils.get_integration = function()
local integration, err = datastore:get("misc_integration")
if integration then
return integration
end
local var, err = datastore:get("variable_SWARM_MODE")
if var == "yes" then
integration = "swarm"
else
local var, err = datastore:get("variable_KUBERNETES_MODE")
if var == "yes" then
integration = "kubernetes"
else
local f, err = io.open("/etc/os-release", "r")
if f then
local data = f:read("*a")
if data:find("Alpine") then
integration = "docker"
else
integration = "unknown"
end
f:close()
else
integration = "unknown"
end
end
end
local ok, err = datastore:set("misc_integration", integration)
if not ok then
logger.log(ngx.ERR, "UTILS", "Can't cache integration to datastore : " .. err)
end
return integration
end
utils.get_version = function()
local version, err = datastore:get("misc_version")
if version then
return version
end
local f, err = io.open("/opt/bunkerweb/VERSION", "r")
if not f then
logger.log(ngx.ERR, "UTILS", "Can't read VERSION file : " .. err)
return "unknown"
end
version = f:read("*a")
f:close()
local ok, err = datastore:set("misc_version", version)
if not ok then
logger.log(ngx.ERR, "UTILS", "Can't cache version to datastore : " .. err)
end
return version
end
utils.get_reason = function()
if ngx.var.reason and ngx.var.reason ~= "" then
return ngx.var.reason
end
if os.getenv("REASON") == "modsecurity" then
return "modsecurity"
end
if ngx.status == ngx.HTTP_FORBIDDEN then
return "unknown"
end
return nil
end
utils.get_rdns = function(ip)
local str_resolvers, err = datastore:get("misc_resolvers")
if not str_resolvers then
return false, err
end
local resolvers = cjson.decode(str_resolvers)
local rdns, err = resolver:new{
nameservers = resolvers,
retrans = 1,
timeout = 1000
}
if not rdns then
return false, err
end
local answers, err = rdns:reverse_query(ip)
if not answers then
return false, err
end
if answers.errcode then
return false, answers.errstr
end
for i, answer in ipairs(answers) do
if answer.ptrdname then
return answer.ptrdname, "success"
end
end
return false, nil
end
utils.get_ips = function(fqdn, resolvers)
local str_resolvers, err = datastore:get("misc_resolvers")
if not str_resolvers then
return false, err
end
local resolvers = cjson.decode(str_resolvers)
local rdns, err = resolver:new{
nameservers = resolvers,
retrans = 1,
timeout = 1000
}
if not rdns then
return false, err
end
local answers, err = rdns:query(fqdn, nil, {})
if not answers then
return false, err
end
if answers.errcode then
return {}, answers.errstr
end
local ips = {}
for i, answer in ipairs(answers) do
if answer.address then
table.insert(ips, answer.addres)
end
end
return ips, "success"
end
utils.get_country = function(ip)
if not mmdb.country_db then
return false, "mmdb country not loaded"
end
local result, err = mmdb.country_db:lookup(ip)
if not result then
return nil, err
end
return result.country.iso_code, "success"
end
utils.get_asn = function(ip)
if not mmdb.asn_db then
return false, "mmdb asn not loaded"
end
local result, err = mmdb.asn_db:lookup(ip)
if not result then
return nil, err
end
return result.autonomous_system_number, "success"
end
utils.rand = function(nb)
local charset = {}
for i = 48, 57 do table.insert(charset, string.char(i)) end
for i = 65, 90 do table.insert(charset, string.char(i)) end
for i = 97, 122 do table.insert(charset, string.char(i)) end
local result = ""
for i = 1, nb do
result = result .. charset[math.random(1, #charset)]
end
return result
end
return utils

View File

@@ -1,62 +0,0 @@
local M = {}
local dns = require "dns"
local iputils = require "resty.iputils"
local logger = require "logger"
function M.ip_cached_ok ()
return ngx.shared.whitelist_ip_cache:get(ngx.var.remote_addr) == "ok"
end
function M.reverse_cached_ok ()
return ngx.shared.whitelist_reverse_cache:get(ngx.var.remote_addr) == "ok"
end
function M.ip_cached ()
return ngx.shared.whitelist_ip_cache:get(ngx.var.remote_addr) ~= nil
end
function M.reverse_cached ()
return ngx.shared.whitelist_reverse_cache:get(ngx.var.remote_addr) ~= nil
end
function M.check_ip (ip_list)
if #ip_list > 0 then
local whitelist = iputils.parse_cidrs(ip_list)
if iputils.ip_in_cidrs(ngx.var.remote_addr, whitelist) then
ngx.shared.whitelist_ip_cache:set(ngx.var.remote_addr, "ok", 86400)
logger.log(ngx.NOTICE, "WHITELIST", "ip " .. ngx.var.remote_addr .. " is in whitelist")
return true
end
end
ngx.shared.whitelist_ip_cache:set(ngx.var.remote_addr, "ko", 86400)
return false
end
function M.check_reverse (reverse_list, resolvers)
if #reverse_list > 0 then
local rdns = dns.get_reverse(resolvers)
if rdns ~= "" then
local whitelisted = false
for k, v in ipairs(reverse_list) do
if rdns:sub(-#v) == v then
whitelisted = true
break
end
end
if whitelisted then
local ips = dns.get_ips(rdns, resolvers)
for k, v in ipairs(ips) do
if v == ngx.var.remote_addr then
ngx.shared.whitelist_reverse_cache:set(ngx.var.remote_addr, "ok", 86400)
logger.log(ngx.NOTICE, "WHITELIST", "reverse " .. rdns .. " is in whitelist")
return true
end
end
end
end
end
ngx.shared.whitelist_reverse_cache:set(ngx.var.remote_addr, "ko", 86400)
return false
end
return M