mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Refactor ban functionality and improve ban listing
This commit is contained in:
parent
73c2ea42f0
commit
a737bad334
4 changed files with 71 additions and 22 deletions
|
|
@ -33,7 +33,6 @@ local get_body_data = ngx_req.get_body_data
|
|||
local get_body_file = ngx_req.get_body_file
|
||||
local decode = cjson.decode
|
||||
local encode = cjson.encode
|
||||
local floor = math.floor
|
||||
local match = string.match
|
||||
local require_plugin = helpers.require_plugin
|
||||
local new_plugin = helpers.new_plugin
|
||||
|
|
@ -216,7 +215,26 @@ api.global.POST["^/ban$"] = function(self)
|
|||
if not ok then
|
||||
return self:response(HTTP_INTERNAL_SERVER_ERROR, "error", "can't decode JSON : " .. ip)
|
||||
end
|
||||
datastore:set("bans_ip_" .. ip["ip"], "manual", ip["exp"])
|
||||
local ban = {
|
||||
ip = "",
|
||||
exp = 86400,
|
||||
reason = "manual",
|
||||
}
|
||||
ban.ip = ip["ip"]
|
||||
if ip["exp"] then
|
||||
ban.exp = ip["exp"]
|
||||
end
|
||||
if ip["reason"] then
|
||||
ban.reason = ip["reason"]
|
||||
end
|
||||
datastore:set(
|
||||
"bans_ip_" .. ban["ip"],
|
||||
encode({
|
||||
reason = ban["reason"],
|
||||
date = os.time(),
|
||||
}),
|
||||
ban["exp"]
|
||||
)
|
||||
return self:response(HTTP_OK, "success", "ip " .. ip["ip"] .. " banned")
|
||||
end
|
||||
|
||||
|
|
@ -224,12 +242,12 @@ api.global.GET["^/bans$"] = function(self)
|
|||
local data = {}
|
||||
for _, k in ipairs(datastore:keys()) do
|
||||
if k:find("^bans_ip_") then
|
||||
local reason, err = datastore:get(k)
|
||||
local result, err = datastore:get(k)
|
||||
if err then
|
||||
return self:response(
|
||||
HTTP_INTERNAL_SERVER_ERROR,
|
||||
"error",
|
||||
"can't access " .. k .. " from datastore : " .. reason
|
||||
"can't access " .. k .. " from datastore : " .. result
|
||||
)
|
||||
end
|
||||
local ok, ttl = datastore:ttl(k)
|
||||
|
|
@ -240,7 +258,9 @@ api.global.GET["^/bans$"] = function(self)
|
|||
"can't access ttl " .. k .. " from datastore : " .. ttl
|
||||
)
|
||||
end
|
||||
local ban = { ip = k:sub(9, #k), reason = reason, exp = floor(ttl) }
|
||||
local ban_data = decode(result)
|
||||
local ban =
|
||||
{ ip = k:sub(9, #k), reason = ban_data["reason"], date = ban_data["date"], exp = math.floor(ttl) }
|
||||
table.insert(data, ban)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -638,15 +638,16 @@ end
|
|||
|
||||
utils.is_banned = function(ip)
|
||||
-- Check on local datastore
|
||||
local reason, err = datastore:get("bans_ip_" .. ip)
|
||||
if not reason and err ~= "not found" then
|
||||
return nil, "datastore:get() error : " .. reason
|
||||
elseif reason and err ~= "not found" then
|
||||
local result, err = datastore:get("bans_ip_" .. ip)
|
||||
if not result and err ~= "not found" then
|
||||
return nil, "datastore:get() error : " .. result
|
||||
elseif result and err ~= "not found" then
|
||||
local ok, ttl = datastore:ttl("bans_ip_" .. ip)
|
||||
local ban_data = decode(result)
|
||||
if not ok then
|
||||
return true, reason, -1
|
||||
return true, ban_data, -1
|
||||
end
|
||||
return true, reason, ttl
|
||||
return true, ban_data, ttl
|
||||
end
|
||||
-- Redis case
|
||||
local use_redis, err = utils.get_variable("USE_REDIS", false)
|
||||
|
|
@ -701,7 +702,11 @@ end
|
|||
|
||||
utils.add_ban = function(ip, reason, ttl)
|
||||
-- Set on local datastore
|
||||
local ok, err = datastore:set("bans_ip_" .. ip, reason, ttl)
|
||||
local ban_data = encode({
|
||||
reason = reason,
|
||||
date = os.time(),
|
||||
})
|
||||
local ok, err = datastore:set("bans_ip_" .. ip, ban_data, ttl)
|
||||
if not ok then
|
||||
return false, "datastore:set() error : " .. err
|
||||
end
|
||||
|
|
@ -719,7 +724,7 @@ utils.add_ban = function(ip, reason, ttl)
|
|||
return false, "can't connect to redis server : " .. err
|
||||
end
|
||||
-- SET call
|
||||
ok, err = clusterstore:call("set", "bans_ip_" .. ip, reason, "EX", ttl)
|
||||
ok, err = clusterstore:call("set", "bans_ip_" .. ip, ban_data, "EX", ttl)
|
||||
if not ok then
|
||||
clusterstore:close()
|
||||
return false, "redis SET failed : " .. err
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from datetime import datetime
|
||||
from json import dumps, loads
|
||||
from time import time
|
||||
from dotenv import dotenv_values
|
||||
from os import getenv, sep
|
||||
from os.path import join
|
||||
|
|
@ -19,10 +22,19 @@ from logger import setup_logger # type: ignore
|
|||
|
||||
|
||||
def format_remaining_time(seconds):
|
||||
days, seconds = divmod(seconds, 86400)
|
||||
hours, seconds = divmod(seconds, 3600)
|
||||
years, seconds = divmod(seconds, 60 * 60 * 24 * 365)
|
||||
months, seconds = divmod(seconds, 60 * 60 * 24 * 30)
|
||||
while months >= 12:
|
||||
years += 1
|
||||
months -= 12
|
||||
days, seconds = divmod(seconds, 60 * 60 * 24)
|
||||
hours, seconds = divmod(seconds, 60 * 60)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
time_parts = []
|
||||
if years > 0:
|
||||
time_parts.append(f"{int(years)} year{'' if years == 1 else 's'}")
|
||||
if months > 0:
|
||||
time_parts.append(f"{int(months)} month{'' if months == 1 else 's'}")
|
||||
if days > 0:
|
||||
time_parts.append(f"{int(days)} day{'' if days == 1 else 's'}")
|
||||
if hours > 0:
|
||||
|
|
@ -206,14 +218,15 @@ class CLI(ApiCaller):
|
|||
return True, f"IP {ip} has been unbanned"
|
||||
return False, "error"
|
||||
|
||||
def ban(self, ip: str, exp: float) -> Tuple[bool, str]:
|
||||
def ban(self, ip: str, exp: float, reason: str) -> Tuple[bool, str]:
|
||||
if self.__redis:
|
||||
ok = self.__redis.set(f"bans_ip_{ip}", "manual", ex=exp)
|
||||
ok = self.__redis.set(f"bans_ip_{ip}", dumps({"reason": reason, "date": time()}))
|
||||
if not ok:
|
||||
self.__logger.error(f"Failed to ban {ip} in redis")
|
||||
self.__redis.expire(f"bans_ip_{ip}", int(exp))
|
||||
|
||||
if self.send_to_apis("POST", "/ban", data={"ip": ip, "exp": exp}):
|
||||
return (True, f"IP {ip} has been banned for {format_remaining_time(exp)}")
|
||||
if self.send_to_apis("POST", "/ban", data={"ip": ip, "exp": exp, "reason": reason}):
|
||||
return (True, f"IP {ip} has been banned for {format_remaining_time(exp)} with reason {reason}")
|
||||
return False, "error"
|
||||
|
||||
def bans(self) -> Tuple[bool, str]:
|
||||
|
|
@ -230,8 +243,13 @@ class CLI(ApiCaller):
|
|||
servers["redis"] = []
|
||||
for key in self.__redis.scan_iter("bans_ip_*"):
|
||||
ip = key.decode("utf-8").replace("bans_ip_", "")
|
||||
data = self.__redis.get(key)
|
||||
if not data:
|
||||
continue
|
||||
exp = self.__redis.ttl(key)
|
||||
servers["redis"].append({"ip": ip, "exp": exp, "reason": "manual"})
|
||||
servers["redis"].append({"ip": ip, "exp": exp} | loads(data))
|
||||
|
||||
servers = {k: sorted(v, key=lambda x: x["date"]) for k, v in servers.items()}
|
||||
|
||||
cli_str = ""
|
||||
for server, bans in servers.items():
|
||||
|
|
@ -240,7 +258,7 @@ class CLI(ApiCaller):
|
|||
cli_str += "No ban found\n"
|
||||
|
||||
for ban in bans:
|
||||
cli_str += f"- {ban['ip']} for {format_remaining_time(ban['exp'])} : {ban.get('reason', 'no reason given')}\n"
|
||||
cli_str += f"- {ban['ip']} ; banned the {datetime.fromtimestamp(ban['date']).strftime('%d-%m-%Y at %H:%M:%S')} for {format_remaining_time(ban['exp'])} remaining with reason \"{ban.get('reason', 'no reason given')}\"\n"
|
||||
cli_str += "\n"
|
||||
|
||||
return True, cli_str
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ if __name__ == "__main__":
|
|||
help=f"banning time in seconds (default : {ban_time})",
|
||||
default=ban_time,
|
||||
)
|
||||
parser_ban.add_argument(
|
||||
"-reason",
|
||||
type=str,
|
||||
help="reason for ban (default : manual)",
|
||||
default="manual",
|
||||
)
|
||||
|
||||
# Bans subparser
|
||||
parser_bans = subparsers.add_parser("bans", help="list current bans")
|
||||
|
|
@ -55,7 +61,7 @@ if __name__ == "__main__":
|
|||
if args.command == "unban":
|
||||
ret, err = cli.unban(args.ip)
|
||||
elif args.command == "ban":
|
||||
ret, err = cli.ban(args.ip, args.exp)
|
||||
ret, err = cli.ban(args.ip, args.exp, args.reason)
|
||||
elif args.command == "bans":
|
||||
ret, err = cli.bans()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue