Files
mesh-catalog/modules/fail2ban/cmd/fail2ban-tools/main.go
T
jschoubben d43de93e49 fail2ban: its tools in Go
Go is the default for module code. One binary, fail2ban-tools, serving the node-intrusion-prevention
seat's four verbs and fail2ban_settings over the SDK, with the same parsing and the same tests; read
back against the control node's live daemon.
2026-10-05 12:01:25 +02:00

65 lines
3.1 KiB
Go

// 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 `<seat>.<verb>`, 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")) }},
}
}