fail2ban
The fail2ban utility provided the inspiration for this plugin. However, this approach isn't limited by simple string matching and supports various actions. It monitors events for failures using failed(). When a key exceeds its threshold within window seconds, ban() is called. Events always pass through downstream regardless of failure status.
Once a key is banned (i.e. ban() returns a non-null event), it is recorded permanently for the lifetime of the pipeline. Subsequent events matching that key pass through unchanged without calling ban() again.
The sandbox provides this.execute(key) which runs logbus-fail2ban <key> as a subprocess and returns the exit code (0 = success).
Config
Required:
failed: JS function(event) => { key, ts, limit } | null. Return an object if the event represents a failure; returnnull(or any falsy value) to ignore it. All three fields are required — if any field is missing or the wrong type the object is treated as a non-failure and a warning is printed to stderr.key: string that groups related failures (e.g."ssh~1.2.3.4")ts: event timestamp in milliseconds — used for sliding-window eviction so out-of-order or replayed events are handled correctlylimit: number of failures withinwindowbeforeban()is called for this key. Set per event so different failure types can have different sensitivity (e.g.limit: 1for SSH probes,limit: 10for iptables hits)
ban: JS function(event, key) => event | null. Called when a key exceeds its limit. Return the (possibly mutated) event to permanently ban the key and pass it downstream. Returnnullif the ban could not be applied (e.g.this.execute()failed) — the original event passes through unchanged, the key stays unbanned, andban()will be called again on future failures.
Optional:
window: Sliding time window in seconds (default:60). Eviction uses thetsvalues returned byfailed(), not wall clock, so out-of-order or replayed events are handled correctly.
Example
Per-key limits allow for different ban sensitivities (e.g. by process):
pipeline:
fail2ban:
config:
window: 300
failed: |
(event) => {
if (event.process === 'sshd') {
const m = event.message.match(/invalid user \S+ (?<ip>\S+)/u);
if (m) return { key: `ssh~${m.groups.ip}`, ts: event.ts, limit: 1 };
}
if (event.source && event.event && event.event.dataset === 'iptables')
return { key: `iptables~${event.source.ip}`, ts: event.ts, limit: 10 };
return null;
}
ban: |
function(event, key) {
if (this.execute(key) !== 0) return null;
event._banned = true;
return event;
}