Files
mesh-catalog/modules/fail2ban/client.ts
T

52 lines
2.1 KiB
TypeScript

// fail2ban's own code, in the module (novox/hq ADR 0039). The jails and the daemon are declared
// resources — the mesh writes /etc/fail2ban/jail.d/* and keeps fail2ban.service running (see
// module.json). This code exists only to read and steer the *live* state the daemon owns at
// runtime: which IPs are banned right now, and the manual ban/unban an operator reaches for. That
// state (the running bans, /var/lib/fail2ban's sqlite) is fail2ban's, not the mesh's — the mesh
// reconciles the config, never the ban list.
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
export class Fail2banClient {
static fromEnv(_env: NodeJS.ProcessEnv = process.env): Fail2banClient {
return new Fail2banClient();
}
/** Overview of every jail, or the detailed status of one — currently-banned IPs and totals. */
async status(jail?: string): Promise<string> {
if (jail) {
const { stdout } = await run("sudo", ["fail2ban-client", "status", jail]);
return stdout;
}
const { stdout: overview } = await run("sudo", ["fail2ban-client", "status"]);
const match = overview.match(/Jail list:\s*(.+)/);
if (!match) return overview;
const jails = match[1].split(",").map((j) => j.trim()).filter(Boolean);
const parts: string[] = [overview.trimEnd(), ""];
for (const j of jails) {
const { stdout } = await run("sudo", ["fail2ban-client", "status", j]);
parts.push(`=== ${j} ===`, stdout.trimEnd(), "");
}
return parts.join("\n");
}
/** Manually ban an IP in a jail. Mutates live state, not a mesh-managed file. */
async ban(jail: string, ip: string): Promise<string> {
const { stdout } = await run("sudo", ["fail2ban-client", "set", jail, "banip", ip]);
return stdout;
}
/** Unban an IP from one jail, or from every jail when no jail is given. */
async unban(ip: string, jail?: string): Promise<string> {
const args = jail
? ["fail2ban-client", "set", jail, "unbanip", ip]
: ["fail2ban-client", "unban", ip];
const { stdout } = await run("sudo", args);
return stdout;
}
}