// fail2ban-tools (novox/hq to-be 31, ADR 0179): the intrusion prevention's tools. One binary, launched // by the machine's tool runtime and speaking MCP to it over stdio through the Go SDK (ADR 0193, ADR // 0198): the node-intrusion-prevention seat's four verbs — who is banned, the jails' state, ban one, // let one go — and the module's own reading of a jail's settings. The jails themselves are composed // by the mesh from the modules a machine runs and written as declared resources; these touch only // what the running daemon holds. // // stdout is the MCP channel; everything this module says, it says on stderr. package main import ( "context" "fmt" "os" "strings" stdio "git.novox.be/novox/mesh-sdk/go" ) // Seat is the role this module holds. const Seat = "node-intrusion-prevention" func main() { if err := stdio.Serve("", tools(Fail2ban{Run: execRunner})); err != nil { fmt.Fprintf(os.Stderr, "[fail2ban] %v\n", err) os.Exit(1) } } func str(description string) map[string]any { return map[string]any{"type": "string", "description": description} } func arg(a map[string]any, k string) string { v, _ := a[k].(string) return strings.TrimSpace(v) } // verb is one of the seat's verbs: listed as `.`, so the runtime serves it on the seat's // subject. The module's own tools keep their bare names. func verb(name, description string, input map[string]any, run func(a map[string]any) (any, error)) stdio.Tool { return stdio.Tool{Name: Seat + "." + name, Description: description, Input: input, Run: run} } func tools(f Fail2ban) []stdio.Tool { ctx := context.Background() oneJail := map[string]any{"jail": str("one jail (optional)")} return []stdio.Tool{ verb("status", "Every jail on this machine with what it watches, how many addresses it is counting failures against and holding now, and the totals since it started; one jail's detail when named.", oneJail, func(a map[string]any) (any, error) { return f.Status(ctx, arg(a, "jail")) }), verb("banned", "Every address banned on this machine right now, with the jail that holds it, when it was banned and when the ban ends.", oneJail, func(a map[string]any) (any, error) { return f.Banned(ctx, arg(a, "jail")) }), verb("ban", "Ban one address in one jail now, for the jail's ban time — an operator's act on the live ban list, which the mesh never writes itself.", map[string]any{"ip": str("the address"), "jail": str("the jail to hold it (recidive for the long ban)")}, func(a map[string]any) (any, error) { return f.Ban(ctx, arg(a, "ip"), arg(a, "jail")) }), verb("unban", "Let one address go, from one jail or from every jail when none is named.", map[string]any{"ip": str("the address"), "jail": str("one jail (optional)")}, func(a map[string]any) (any, error) { return f.Unban(ctx, arg(a, "ip"), arg(a, "jail")) }), {Name: "fail2ban_settings", Description: "One jail's effective settings on this machine: ban time, window, tries, the addresses it never bans, its actions and what it reads.", Input: map[string]any{"jail": str("the jail")}, Run: func(a map[string]any) (any, error) { return f.Settings(ctx, arg(a, "jail")) }}, } }