Add firewall rules logic for all hosts
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
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<AllowSubnetFirewallRulesResult> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user