import { execSync } from "node:child_process"; import validateIpv4Cidr from "@/src/utils/validate-ipv4-cidr"; import detectFirewallManager, { type FirewallManager, } from "./detect-firewall-manager"; export type AllowSubnetFirewallRulesResult = { success: boolean; manager: FirewallManager | "none"; msg: string; added_rules: string[]; skipped_rules: string[]; }; type Params = { subnet: string; listen_port: number; }; type ShellResult = { success: boolean; output: string; }; const tryShell = ({ command }: { command: string }): ShellResult => { try { const result = execSync(command, { encoding: "utf-8", stdio: "pipe", timeout: 30000, }); return { success: true, output: String(result).trim(), }; } catch (error: any) { const stdout = error?.stdout ? String(error.stdout).trim() : ""; const stderr = error?.stderr ? String(error.stderr).trim() : ""; return { success: false, output: [stdout, stderr].filter(Boolean).join("\n").trim(), }; } }; const isValidPort = ({ port }: { port: number }) => Number.isInteger(port) && port >= 1 && port <= 65535; export default async function allowSubnetFirewallRules({ subnet, listen_port, }: Params): Promise { const subnet_trimmed = subnet?.trim(); if (!validateIpv4Cidr({ value: subnet_trimmed })) { return { success: false, manager: "none", msg: `Invalid subnet "${subnet_trimmed}"`, added_rules: [], skipped_rules: [], }; } if (!isValidPort({ port: listen_port })) { return { success: false, manager: "none", msg: `Invalid listen port ${listen_port}`, added_rules: [], skipped_rules: [], }; } const manager = detectFirewallManager(); const added_rules: string[] = []; const skipped_rules: string[] = []; if (manager == "firewalld") { const subnet_rule = `rule family="ipv4" source address="${subnet_trimmed}" accept`; const subnet_add = tryShell({ command: `firewall-cmd --permanent --add-rich-rule='${subnet_rule}'`, }); if (subnet_add.success) { added_rules.push(`allow all traffic from ${subnet_trimmed}`); } else { skipped_rules.push( `firewalld subnet rule not applied: ${subnet_add.output}`, ); } const port_rule = `${listen_port}/udp`; const port_add = tryShell({ command: `firewall-cmd --permanent --add-port=${port_rule}`, }); if (port_add.success) { added_rules.push(`allow ${port_rule} from anywhere (wireguard)`); } else { skipped_rules.push( `firewalld port rule not applied: ${port_add.output}`, ); } const reload = tryShell({ command: `firewall-cmd --reload` }); if (!reload.success) { skipped_rules.push(`firewalld reload failed: ${reload.output}`); } } else if (manager == "ufw") { const ufw_status = tryShell({ command: `ufw status` }); if ( !ufw_status.success || !ufw_status.output.includes(`Status: active`) ) { return { success: false, manager, msg: `ufw is not active — cannot add firewall rules`, added_rules, skipped_rules, }; } if (ufw_status.output.includes(subnet_trimmed)) { skipped_rules.push(`subnet ${subnet_trimmed} already allowed`); } else { const subnet_add = tryShell({ command: `ufw allow from ${subnet_trimmed}`, }); if (subnet_add.success) { added_rules.push(`allow all traffic from ${subnet_trimmed}`); } else { skipped_rules.push( `ufw subnet rule not applied: ${subnet_add.output}`, ); } } const port_rule = `${listen_port}/udp`; if (ufw_status.output.includes(port_rule)) { skipped_rules.push(`port ${port_rule} already allowed`); } else { const port_add = tryShell({ command: `ufw allow ${port_rule}` }); if (port_add.success) { added_rules.push( `allow ${port_rule} from anywhere (wireguard)`, ); } else { skipped_rules.push( `ufw port rule not applied: ${port_add.output}`, ); } } } if (manager == "none") { return { success: true, manager, msg: `no active firewall manager detected (firewalld/ufw) — no rules added; if you use iptables/nftables directly, allow ${subnet_trimmed} and udp port ${listen_port} manually`, added_rules, skipped_rules, }; } const summary = [ added_rules.length ? `firewall(${manager}): added — ${added_rules.join("; ")}` : `firewall(${manager}): no new rules added`, skipped_rules.length ? `skipped — ${skipped_rules.join("; ")}` : ``, ] .filter(Boolean) .join(` `); return { success: true, manager, msg: summary, added_rules, skipped_rules, }; }