Updates
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
|
||||
import parsePortList from "@/src/utils/parse-port-list";
|
||||
import validateIpv4 from "@/src/utils/validate-ipv4";
|
||||
import deriveIptablesChainNames from "./derive-iptables-chain-names";
|
||||
import normalizeIptablesDestination from "./normalize-iptables-destination";
|
||||
import validateClientRule from "./validate-client-rule";
|
||||
|
||||
type HostIptablesClient = {
|
||||
id?: number | "";
|
||||
wg_ip_address?: string;
|
||||
rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
||||
};
|
||||
|
||||
type Params = {
|
||||
host_id: number;
|
||||
interface_name: string;
|
||||
target_interface: string;
|
||||
clients?: HostIptablesClient[];
|
||||
};
|
||||
|
||||
const SAFE_IFACE = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
||||
const MULTIPORT_LIMIT = 15;
|
||||
|
||||
function joinArgs(parts: (string | undefined)[]) {
|
||||
return parts.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function protocolsForRule({
|
||||
protocol,
|
||||
has_ports,
|
||||
}: {
|
||||
protocol?: string;
|
||||
has_ports: boolean;
|
||||
}) {
|
||||
if (has_ports) {
|
||||
if (protocol == "udp") {
|
||||
return ["udp"];
|
||||
}
|
||||
|
||||
if (protocol == "tcp") {
|
||||
return ["tcp"];
|
||||
}
|
||||
|
||||
return ["tcp", "udp"];
|
||||
}
|
||||
|
||||
if (protocol == "tcp" || protocol == "udp") {
|
||||
return [protocol];
|
||||
}
|
||||
|
||||
return [undefined];
|
||||
}
|
||||
|
||||
function portMatch({ ports }: { ports: string[] }) {
|
||||
if (!ports[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ports.length == 1) {
|
||||
return `--dport ${ports[0]}`;
|
||||
}
|
||||
|
||||
return `-m multiport --dports ${ports.join(",")}`;
|
||||
}
|
||||
|
||||
function chunkPorts({ ports }: { ports: string[] }) {
|
||||
if (ports.length <= MULTIPORT_LIMIT) {
|
||||
return [ports];
|
||||
}
|
||||
|
||||
const chunks: string[][] = [];
|
||||
|
||||
for (let i = 0; i < ports.length; i += MULTIPORT_LIMIT) {
|
||||
chunks.push(ports.slice(i, i + MULTIPORT_LIMIT));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function acceptLines({
|
||||
chain,
|
||||
source,
|
||||
destination,
|
||||
protocol,
|
||||
ports,
|
||||
}: {
|
||||
chain: string;
|
||||
source: string;
|
||||
destination?: string;
|
||||
protocol?: string;
|
||||
ports: string[];
|
||||
}) {
|
||||
const lines: string[] = [];
|
||||
const protocols = protocolsForRule({
|
||||
protocol,
|
||||
has_ports: Boolean(ports[0]),
|
||||
});
|
||||
const port_chunks = ports[0] ? chunkPorts({ ports }) : [[]];
|
||||
|
||||
for (let p = 0; p < protocols.length; p++) {
|
||||
const proto = protocols[p];
|
||||
|
||||
for (let c = 0; c < port_chunks.length; c++) {
|
||||
const chunk = port_chunks[c] || [];
|
||||
|
||||
lines.push(
|
||||
joinArgs([
|
||||
`iptables -A ${chain}`,
|
||||
`-s ${source}`,
|
||||
destination ? `-d ${destination}` : undefined,
|
||||
proto ? `-p ${proto}` : undefined,
|
||||
proto ? portMatch({ ports: chunk }) : undefined,
|
||||
`-j ACCEPT`,
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
export default function buildHostIptablesScripts({
|
||||
host_id,
|
||||
interface_name,
|
||||
target_interface,
|
||||
clients,
|
||||
}: Params) {
|
||||
if (!Number.isInteger(host_id) || host_id < 0) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Invalid host id`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!SAFE_IFACE.test(interface_name)) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Invalid interface name`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!SAFE_IFACE.test(target_interface)) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Invalid target interface`,
|
||||
};
|
||||
}
|
||||
|
||||
const { forward, input } = deriveIptablesChainNames({ host_id });
|
||||
const accept_lines: string[] = [];
|
||||
const host_clients = clients || [];
|
||||
|
||||
for (let i = 0; i < host_clients.length; i++) {
|
||||
const client = host_clients[i];
|
||||
|
||||
if (!client) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const source_ip = client.wg_ip_address?.trim();
|
||||
|
||||
if (!source_ip) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Client ${client.id || i} is missing a WireGuard IP`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!validateIpv4({ ip: source_ip })) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Client ${client.id || i} has an invalid WireGuard IP`,
|
||||
};
|
||||
}
|
||||
|
||||
const source = `${source_ip}/32`;
|
||||
const rules = client.rules || [];
|
||||
|
||||
for (let r = 0; r < rules.length; r++) {
|
||||
const rule = rules[r];
|
||||
|
||||
if (!rule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const valid = validateClientRule({ rule });
|
||||
|
||||
if (!valid.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Client ${client.id || i}: ${valid.msg}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (rule.rule_type == "all") {
|
||||
accept_lines.push(
|
||||
`iptables -A ${forward} -s ${source} -j ACCEPT`,
|
||||
);
|
||||
accept_lines.push(
|
||||
`iptables -A ${input} -s ${source} -j ACCEPT`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const destination = normalizeIptablesDestination({
|
||||
destination: rule.destination,
|
||||
});
|
||||
const parsed_ports = parsePortList({ ports: rule.ports });
|
||||
|
||||
if (!parsed_ports.success) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Client ${client.id || i}: ${parsed_ports.msg}`,
|
||||
};
|
||||
}
|
||||
|
||||
accept_lines.push(
|
||||
...acceptLines({
|
||||
chain: forward,
|
||||
source,
|
||||
destination,
|
||||
protocol: rule.protocol || "any",
|
||||
ports: parsed_ports.ports,
|
||||
}),
|
||||
...acceptLines({
|
||||
chain: input,
|
||||
source,
|
||||
destination,
|
||||
protocol: rule.protocol || "any",
|
||||
ports: parsed_ports.ports,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const post_up = [
|
||||
`#!/bin/bash`,
|
||||
``,
|
||||
`iptables -D INPUT -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D OUTPUT -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D FORWARD -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D FORWARD -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
``,
|
||||
`iptables -N ${forward} 2>/dev/null || true`,
|
||||
`iptables -F ${forward}`,
|
||||
`iptables -N ${input} 2>/dev/null || true`,
|
||||
`iptables -F ${input}`,
|
||||
``,
|
||||
`iptables -A ${forward} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
||||
`iptables -A ${input} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
||||
``,
|
||||
...accept_lines,
|
||||
``,
|
||||
`iptables -A ${forward} -j DROP`,
|
||||
`iptables -A ${input} -j DROP`,
|
||||
``,
|
||||
`iptables -C FORWARD -i ${interface_name} -j ${forward} 2>/dev/null || iptables -I FORWARD 1 -i ${interface_name} -j ${forward}`,
|
||||
`iptables -C FORWARD -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || iptables -I FORWARD 1 -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
||||
`iptables -C INPUT -i ${interface_name} -j ${input} 2>/dev/null || iptables -I INPUT 1 -i ${interface_name} -j ${input}`,
|
||||
``,
|
||||
`iptables -t nat -C POSTROUTING -o ${target_interface} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o ${target_interface} -j MASQUERADE`,
|
||||
``,
|
||||
].join("\n");
|
||||
|
||||
const post_down = [
|
||||
`#!/bin/bash`,
|
||||
``,
|
||||
`iptables -D FORWARD -i ${interface_name} -j ${forward} 2>/dev/null || true`,
|
||||
`iptables -D FORWARD -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D INPUT -i ${interface_name} -j ${input} 2>/dev/null || true`,
|
||||
`iptables -D INPUT -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D OUTPUT -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D FORWARD -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -D FORWARD -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||
`iptables -F ${forward} 2>/dev/null || true`,
|
||||
`iptables -X ${forward} 2>/dev/null || true`,
|
||||
`iptables -F ${input} 2>/dev/null || true`,
|
||||
`iptables -X ${input} 2>/dev/null || true`,
|
||||
`iptables -t nat -D POSTROUTING -o ${target_interface} -j MASQUERADE 2>/dev/null || true`,
|
||||
``,
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
post_up,
|
||||
post_down,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user