Initial commit - realms platform
This commit is contained in:
parent
c590ab6d18
commit
c717c3751c
234 changed files with 74103 additions and 15231 deletions
118
openresty/lua/uberban.lua
Normal file
118
openresty/lua/uberban.lua
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
-- Site-wide uberban checking module
|
||||
-- Blocks uberbanned fingerprints from accessing any endpoint
|
||||
|
||||
local redis = require "resty.redis"
|
||||
local fingerprint = require "fingerprint"
|
||||
|
||||
local _M = {}
|
||||
|
||||
-- Cache for ban status (5 second TTL to reduce Redis load)
|
||||
local uberban_cache = ngx.shared.uberban_cache
|
||||
local CACHE_TTL = 5 -- seconds
|
||||
|
||||
-- Redis password cached at module load time
|
||||
local REDIS_PASSWORD = os.getenv("REDIS_PASS")
|
||||
|
||||
-- Get Redis connection (same pattern as redis_helper.lua)
|
||||
local function get_redis_connection()
|
||||
local red = redis:new()
|
||||
red:set_timeouts(1000, 1000, 1000) -- connect, send, read timeout in ms
|
||||
|
||||
local host = "redis"
|
||||
local port = tonumber(os.getenv("REDIS_PORT")) or 6379
|
||||
|
||||
local ok, err = red:connect(host, port)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "[uberban] Failed to connect to Redis: ", err)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Authenticate if password is set
|
||||
if REDIS_PASSWORD and REDIS_PASSWORD ~= "" then
|
||||
local res, err = red:auth(REDIS_PASSWORD)
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, "[uberban] Failed to authenticate to Redis: ", err)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Select database 1 (where chat-service stores fingerprint bans)
|
||||
local db = tonumber(os.getenv("REDIS_DB")) or 1
|
||||
local res, err = red:select(db)
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, "[uberban] Failed to select Redis db ", db, ": ", err)
|
||||
return nil
|
||||
end
|
||||
|
||||
return red
|
||||
end
|
||||
|
||||
local function close_redis_connection(red)
|
||||
local ok, err = red:set_keepalive(10000, 100)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "[uberban] Failed to set keepalive: ", err)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if a fingerprint is uberbanned
|
||||
-- Returns true if banned, false if not banned
|
||||
function _M.is_banned(fp)
|
||||
if not fp then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check cache first
|
||||
if uberban_cache then
|
||||
local cached = uberban_cache:get(fp)
|
||||
if cached ~= nil then
|
||||
return cached == "1"
|
||||
end
|
||||
end
|
||||
|
||||
-- Query Redis
|
||||
local red = get_redis_connection()
|
||||
if not red then
|
||||
-- SECURITY FIX: Fail closed - if Redis is unavailable, deny access
|
||||
-- This prevents banned users from accessing the system during outages
|
||||
ngx.log(ngx.ERR, "[uberban] Redis unavailable - blocking request (fail closed)")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Check if fingerprint is in the uberbanned set
|
||||
local res, err = red:sismember("chat:banned:fingerprints", fp)
|
||||
close_redis_connection(red)
|
||||
|
||||
if not res then
|
||||
ngx.log(ngx.ERR, "[uberban] Redis SISMEMBER failed: ", err)
|
||||
return false
|
||||
end
|
||||
|
||||
local is_banned = (res == 1)
|
||||
|
||||
-- Cache the result
|
||||
if uberban_cache then
|
||||
uberban_cache:set(fp, is_banned and "1" or "0", CACHE_TTL)
|
||||
end
|
||||
|
||||
return is_banned
|
||||
end
|
||||
|
||||
-- Check fingerprint and block if uberbanned
|
||||
-- Call this in access_by_lua_block
|
||||
function _M.check_and_block()
|
||||
-- Generate server-side fingerprint
|
||||
local fp = fingerprint.generate()
|
||||
if not fp then
|
||||
return -- Can't generate fingerprint, allow request
|
||||
end
|
||||
|
||||
-- Check if banned
|
||||
if _M.is_banned(fp) then
|
||||
ngx.status = ngx.HTTP_FORBIDDEN
|
||||
ngx.header["Content-Type"] = "application/json"
|
||||
ngx.say('{"error": "Access denied", "banned": true}')
|
||||
return ngx.exit(ngx.HTTP_FORBIDDEN)
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
||||
Loading…
Add table
Add a link
Reference in a new issue