Add new RCON operations for audit log retrieval and bridge status reporting, extend rate limit tracking, and improve response handling
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
-- Returns the most recent audit entries, newest first. RDB_AuditLog.getEntries()
|
||||||
|
-- is a ring buffer in slot order, not chronological order once it's wrapped
|
||||||
|
-- past its capacity, so entries are sorted by ts before slicing.
|
||||||
|
--
|
||||||
|
-- Registration deferred to register(), called from RDB_Bootstrap's
|
||||||
|
-- onServerStarted, see RDB_OpWorldGetStats.lua.
|
||||||
|
|
||||||
|
RDB_OpAuditLogGet = {}
|
||||||
|
|
||||||
|
local DEFAULT_LIMIT = 50
|
||||||
|
local MAX_LIMIT = 200
|
||||||
|
|
||||||
|
function RDB_OpAuditLogGet.register()
|
||||||
|
RDB_OpRegistry.register("get_audit_log", {
|
||||||
|
handler = function(args)
|
||||||
|
local limit = DEFAULT_LIMIT
|
||||||
|
if type(args.limit) == "number" then
|
||||||
|
limit = math.min(math.max(1, math.floor(args.limit)), MAX_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = {}
|
||||||
|
for _, entry in ipairs(RDB_AuditLog.getEntries()) do
|
||||||
|
entries[#entries + 1] = entry
|
||||||
|
end
|
||||||
|
table.sort(entries, function(a, b) return (a.ts or 0) > (b.ts or 0) end)
|
||||||
|
|
||||||
|
local page = {}
|
||||||
|
for i = 1, math.min(limit, #entries) do
|
||||||
|
page[#page + 1] = entries[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, { entries = page }
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- Registration deferred to register(), called from RDB_Bootstrap's
|
||||||
|
-- onServerStarted, see RDB_OpWorldGetStats.lua.
|
||||||
|
|
||||||
|
RDB_OpBridgeStatus = {}
|
||||||
|
|
||||||
|
function RDB_OpBridgeStatus.register()
|
||||||
|
RDB_OpRegistry.register("get_bridge_status", {
|
||||||
|
handler = function(_)
|
||||||
|
local bootedAt = RDB_Bootstrap.getBootTimestamp()
|
||||||
|
local now = getTimestampMs()
|
||||||
|
|
||||||
|
return true, {
|
||||||
|
version = RDB_Constants.PROTOCOL_VERSION,
|
||||||
|
bootedAt = bootedAt,
|
||||||
|
uptimeSeconds = (now - bootedAt) / 1000,
|
||||||
|
rateLimit = RDB_Security.getRateLimitStatus(),
|
||||||
|
config = RDB_Config.getAll(),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
@@ -12,6 +12,16 @@ require("RconDataBridge.Ops.RDB_OpWorldGetStats")
|
|||||||
require("RconDataBridge.Ops.RDB_OpPlayerGet")
|
require("RconDataBridge.Ops.RDB_OpPlayerGet")
|
||||||
require("RconDataBridge.Ops.RDB_OpPlayerList")
|
require("RconDataBridge.Ops.RDB_OpPlayerList")
|
||||||
require("RconDataBridge.Ops.RDB_OpServerSave")
|
require("RconDataBridge.Ops.RDB_OpServerSave")
|
||||||
|
require("RconDataBridge.Ops.RDB_OpBridgeStatus")
|
||||||
|
require("RconDataBridge.Ops.RDB_OpAuditLogGet")
|
||||||
|
|
||||||
|
RDB_Bootstrap = {}
|
||||||
|
|
||||||
|
local bootTimestamp
|
||||||
|
|
||||||
|
function RDB_Bootstrap.getBootTimestamp()
|
||||||
|
return bootTimestamp
|
||||||
|
end
|
||||||
|
|
||||||
local function runSelfTests()
|
local function runSelfTests()
|
||||||
if not RDB_Constants.DEBUG then
|
if not RDB_Constants.DEBUG then
|
||||||
@@ -47,6 +57,8 @@ local function registerOps()
|
|||||||
RDB_OpPlayerGet.register()
|
RDB_OpPlayerGet.register()
|
||||||
RDB_OpPlayerList.register()
|
RDB_OpPlayerList.register()
|
||||||
RDB_OpServerSave.register()
|
RDB_OpServerSave.register()
|
||||||
|
RDB_OpBridgeStatus.register()
|
||||||
|
RDB_OpAuditLogGet.register()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function publishProtocolVersion()
|
local function publishProtocolVersion()
|
||||||
@@ -59,6 +71,9 @@ local function publishProtocolVersion()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function onServerStarted()
|
local function onServerStarted()
|
||||||
|
if not bootTimestamp then
|
||||||
|
bootTimestamp = getTimestampMs()
|
||||||
|
end
|
||||||
runSelfTests()
|
runSelfTests()
|
||||||
RDB_Config.init()
|
RDB_Config.init()
|
||||||
registerBaseOptions()
|
registerBaseOptions()
|
||||||
|
|||||||
@@ -43,3 +43,16 @@ function RDB_Config.set(key, value)
|
|||||||
|
|
||||||
store[key] = value
|
store[key] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Shallow copy, so a caller can't mutate live config through the returned table.
|
||||||
|
function RDB_Config.getAll()
|
||||||
|
if not store then
|
||||||
|
RDB_Config.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
local copy = {}
|
||||||
|
for key, value in pairs(store) do
|
||||||
|
copy[key] = value
|
||||||
|
end
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|||||||
@@ -92,7 +92,12 @@ local function processRequest(rawBase64)
|
|||||||
if RDB_Idempotency.has(id) then
|
if RDB_Idempotency.has(id) then
|
||||||
local cached = RDB_Idempotency.get(id)
|
local cached = RDB_Idempotency.get(id)
|
||||||
publishResponse(cached)
|
publishResponse(cached)
|
||||||
audit(id, op, cached.ok, cached.ok and nil or (cached.error and cached.error.code))
|
|
||||||
|
local cachedErrCode = nil
|
||||||
|
if not cached.ok then
|
||||||
|
cachedErrCode = cached.error and cached.error.code
|
||||||
|
end
|
||||||
|
audit(id, op, cached.ok, cachedErrCode)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -114,7 +119,12 @@ local function processRequest(rawBase64)
|
|||||||
|
|
||||||
RDB_Idempotency.put(id, response)
|
RDB_Idempotency.put(id, response)
|
||||||
publishResponse(response)
|
publishResponse(response)
|
||||||
audit(id, op, dispatchOk, dispatchOk and nil or dataOrCode)
|
|
||||||
|
local errCode = nil
|
||||||
|
if not dispatchOk then
|
||||||
|
errCode = dataOrCode
|
||||||
|
end
|
||||||
|
audit(id, op, dispatchOk, errCode)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onTick()
|
local function onTick()
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ end
|
|||||||
|
|
||||||
local requestTimestamps = {}
|
local requestTimestamps = {}
|
||||||
|
|
||||||
function RDB_Security.checkRateLimit()
|
-- Drops timestamps that have aged out of the current window and returns how
|
||||||
|
-- many remain. Shared by checkRateLimit (which then appends the new request)
|
||||||
|
-- and getRateLimitStatus (which only observes).
|
||||||
|
local function pruneAndCount()
|
||||||
local now = getTimestampMs()
|
local now = getTimestampMs()
|
||||||
local windowMs = RDB_Config.get("rateLimitWindowSeconds") * 1000
|
local windowMs = RDB_Config.get("rateLimitWindowSeconds") * 1000
|
||||||
local maxRequests = RDB_Config.get("rateLimitMaxRequests")
|
|
||||||
|
|
||||||
local kept = {}
|
local kept = {}
|
||||||
for _, ts in ipairs(requestTimestamps) do
|
for _, ts in ipairs(requestTimestamps) do
|
||||||
@@ -54,10 +56,24 @@ function RDB_Security.checkRateLimit()
|
|||||||
end
|
end
|
||||||
requestTimestamps = kept
|
requestTimestamps = kept
|
||||||
|
|
||||||
if #requestTimestamps >= maxRequests then
|
return #requestTimestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
function RDB_Security.checkRateLimit()
|
||||||
|
local maxRequests = RDB_Config.get("rateLimitMaxRequests")
|
||||||
|
|
||||||
|
if pruneAndCount() >= maxRequests then
|
||||||
return false, RDB_Constants.ERROR_CODES.RATE_LIMITED, "Too many requests, slow down"
|
return false, RDB_Constants.ERROR_CODES.RATE_LIMITED, "Too many requests, slow down"
|
||||||
end
|
end
|
||||||
|
|
||||||
requestTimestamps[#requestTimestamps + 1] = now
|
requestTimestamps[#requestTimestamps + 1] = getTimestampMs()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function RDB_Security.getRateLimitStatus()
|
||||||
|
return {
|
||||||
|
currentWindowRequests = pruneAndCount(),
|
||||||
|
maxRequests = RDB_Config.get("rateLimitMaxRequests"),
|
||||||
|
windowSeconds = RDB_Config.get("rateLimitWindowSeconds"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user