fail2ban holds the intrusion seat's verbs and composes the jails; mail, forge and proxy declare theirs (hq ADR 0179, to-be 31)
The module gains a runtime carrying only the fail2ban client with the daemon's socket shared in, serving status/banned/ban/unban and its own fail2ban_settings. It declares jailing, so the controller's composition lands in jail.d/mesh.conf and filter.d; mailu, route-proxy and gitea log to the journal and declare a jail reading it by container name. The base is strict: three in a day for a day, twice banned in two weeks for four; the mesh's range stays never banned.
This commit is contained in:
@@ -1,55 +1,62 @@
|
||||
// fail2ban's tools — reading and steering the live ban state. The jails themselves are declared
|
||||
// resources (module.json); these three touch what the running daemon holds: what is banned now,
|
||||
// and the manual ban/unban an operator reaches for. The daemon's state is fail2ban's own, so this
|
||||
// is the only way to see or change it — the mesh reconciles the config, not the bans.
|
||||
// The intrusion prevention's tools: 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
|
||||
// (novox/hq to-be 31, ADR 0179). 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.
|
||||
|
||||
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||
import { Fail2banClient } from "../client.js";
|
||||
|
||||
export function getFail2banTools(fail2ban: Fail2banClient): ToolDefinition[] {
|
||||
export function getSeatVerbs(fail2ban: Fail2banClient): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: "fail2ban_status",
|
||||
name: "status",
|
||||
description:
|
||||
"fail2ban status on this node — the jails and their live bans. Omit `jail` for every jail, or name one for its detail.",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
jail: {
|
||||
type: "string",
|
||||
description: "A specific jail (e.g. sshd, recidive); omit for the overview of all jails.",
|
||||
},
|
||||
},
|
||||
},
|
||||
run: async (args) => ({ status: await fail2ban.status(args.jail as string | undefined) }),
|
||||
"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.",
|
||||
input: { jail: { type: "string", description: "one jail (optional)" } },
|
||||
run: async (args) => fail2ban.status(args.jail ? String(args.jail) : undefined),
|
||||
},
|
||||
{
|
||||
name: "fail2ban_ban",
|
||||
description: "Manually ban an IP address in a jail — a live change to the running daemon, not a mesh-managed file.",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
jail: { type: "string", description: "Jail name (e.g. sshd, recidive)." },
|
||||
ip: { type: "string", description: "IP address to ban." },
|
||||
},
|
||||
required: ["jail", "ip"],
|
||||
},
|
||||
run: async (args) => ({ result: await fail2ban.ban(args.jail as string, args.ip as string) }),
|
||||
name: "banned",
|
||||
description: "Every address banned on this machine right now, with the jail that holds it, when it was banned and when the ban ends.",
|
||||
input: { jail: { type: "string", description: "one jail (optional)" } },
|
||||
run: async (args) => fail2ban.banned(args.jail ? String(args.jail) : undefined),
|
||||
},
|
||||
{
|
||||
name: "fail2ban_unban",
|
||||
description: "Unban an IP address from one jail, or from every jail when `jail` is omitted.",
|
||||
name: "ban",
|
||||
description:
|
||||
"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.",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: {
|
||||
ip: { type: "string", description: "IP address to unban." },
|
||||
jail: { type: "string", description: "A specific jail; omit to unban from all jails." },
|
||||
},
|
||||
required: ["ip"],
|
||||
ip: { type: "string", description: "the address" },
|
||||
jail: { type: "string", description: "the jail to hold it (recidive for the long ban)" },
|
||||
},
|
||||
run: async (args) => ({ result: await fail2ban.unban(args.ip as string, args.jail as string | undefined) }),
|
||||
run: async (args) => fail2ban.ban(String(args.ip ?? ""), String(args.jail ?? "")),
|
||||
},
|
||||
{
|
||||
name: "unban",
|
||||
description: "Let one address go, from one jail or from every jail when none is named.",
|
||||
input: {
|
||||
ip: { type: "string", description: "the address" },
|
||||
jail: { type: "string", description: "one jail (optional)" },
|
||||
},
|
||||
run: async (args) => fail2ban.unban(String(args.ip ?? ""), args.jail ? String(args.jail) : undefined),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
registerModuleTools("fail2ban", () => getFail2banTools(Fail2banClient.fromEnv()));
|
||||
export function getFail2banTools(fail2ban: Fail2banClient): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
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: { jail: { type: "string", description: "the jail" } },
|
||||
run: async (args) => fail2ban.settings(String(args.jail ?? "")),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const fail2ban = Fail2banClient.fromEnv();
|
||||
// The seat's verbs under the seat's name: the runtime serves them on the seat's subjects where this
|
||||
// module holds it (ADR 0159, 0160). The module's own under its own.
|
||||
registerModuleTools("node-intrusion-prevention", () => getSeatVerbs(fail2ban));
|
||||
registerModuleTools("fail2ban", () => getFail2banTools(fail2ban));
|
||||
|
||||
Reference in New Issue
Block a user