Iptables generator funciton bugfix followup: allow host network ports

This commit is contained in:
2026-09-28 21:24:44 +01:00
parent 19486bbfa0
commit c42f0440ac
3 changed files with 84 additions and 83 deletions
@@ -4,7 +4,6 @@ import buildHostIptablesScripts from "./build-host-iptables-scripts";
const base = { const base = {
host_id: 0, host_id: 0,
interface_name: "wgui0", interface_name: "wgui0",
target_interface: "eth0",
}; };
describe("buildHostIptablesScripts", () => { describe("buildHostIptablesScripts", () => {
@@ -39,10 +38,7 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -o eth0 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -o wgui0 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.2/32 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.2/32 -j ACCEPT",
@@ -70,10 +66,7 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.3/32 -o eth0 -p tcp -m multiport --dports 80,443 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.3/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.3/32 -o wgui0 -p tcp -m multiport --dports 80,443 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.3/32 -p tcp -m multiport --dports 80,443 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.3/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
@@ -101,16 +94,10 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -o eth0 -p tcp --dport 53 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -o eth0 -p udp --dport 53 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -o wgui0 -p tcp --dport 53 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -o wgui0 -p udp --dport 53 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
@@ -196,7 +183,7 @@ describe("buildHostIptablesScripts", () => {
); );
}); });
test("all access scopes same-host forward traffic to the host's own subnet", () => { test("all access scopes input traffic to the host's own subnet", () => {
const res = buildHostIptablesScripts({ const res = buildHostIptablesScripts({
...base, ...base,
host_subnet: "10.6.0.0/24", host_subnet: "10.6.0.0/24",
@@ -211,10 +198,7 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.7/32 -o eth0 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.7/32 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.7/32 -o wgui0 -d 10.6.0.0/24 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.7/32 -d 10.6.0.0/24 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.7/32 -d 10.6.0.0/24 -j ACCEPT",
@@ -241,17 +225,14 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.8/32 -o eth0 -p tcp --dport 53 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.8/32 -o wgui0 -p tcp --dport 53 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
); );
}); });
test("client-to-client traffic on the same host is accepted via the scoped forward rule", () => { test("client-to-client traffic on the same host is accepted on forward and input", () => {
const res = buildHostIptablesScripts({ const res = buildHostIptablesScripts({
...base, ...base,
host_subnet: "10.0.0.0/24", host_subnet: "10.0.0.0/24",
@@ -266,10 +247,10 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -o wgui0 -d 10.0.0.0/24 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
); );
expect(res.post_up).toContain( expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -o eth0 -j ACCEPT", "iptables -A WGUI0IN -s 10.0.0.2/32 -d 10.0.0.0/24 -j ACCEPT",
); );
}); });
@@ -277,6 +258,7 @@ describe("buildHostIptablesScripts", () => {
const res = buildHostIptablesScripts({ const res = buildHostIptablesScripts({
...base, ...base,
host_subnet: "10.0.0.0/24", host_subnet: "10.0.0.0/24",
sibling_subnets: ["10.1.0.0/24", "10.2.0.0/24"],
clients: [ clients: [
{ {
id: 1, id: 1,
@@ -286,7 +268,7 @@ describe("buildHostIptablesScripts", () => {
{ {
id: 2, id: 2,
wg_ip_address: "10.0.0.3", wg_ip_address: "10.0.0.3",
rules: [{ rule_type: "all" }], rules: [{ rule_type: "destination", ports: "80", protocol: "tcp" }],
}, },
], ],
}); });
@@ -294,21 +276,38 @@ describe("buildHostIptablesScripts", () => {
expect(res.success).toBe(true); expect(res.success).toBe(true);
const post_up = res.post_up ?? ""; const post_up = res.post_up ?? "";
expect(post_up).not.toContain( const sibling_drop = "iptables -A WGUI0FWD -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
const sibling_drop_2 = "iptables -A WGUI0FWD -s 10.0.0.2/32 -d 10.2.0.0/24 -j DROP";
const sibling_input_drop = "iptables -A WGUI0IN -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
const accept = "iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT";
expect(post_up).toContain(sibling_drop);
expect(post_up).toContain(sibling_drop_2);
expect(post_up).toContain(sibling_input_drop);
expect(post_up.indexOf(accept)).toBeGreaterThan(
post_up.indexOf(sibling_drop_2),
);
});
test("a sibling subnet equal to the host's own subnet is ignored", () => {
const res = buildHostIptablesScripts({
...base,
host_subnet: "10.1.0.0/24",
sibling_subnets: ["10.1.0.0/24", "10.1.0.0/24"],
clients: [
{
id: 1,
wg_ip_address: "10.0.0.2",
rules: [{ rule_type: "all" }],
},
],
});
expect(res.success).toBe(true);
expect(res.post_up).not.toContain("-d 10.1.0.0/24 -j DROP");
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT", "iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
); );
expect(post_up).not.toContain(
"iptables -A WGUI0FWD -s 10.0.0.3/32 -j ACCEPT",
);
const scoped_accept_index = post_up.indexOf(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -o wgui0 -d 10.0.0.0/24 -j ACCEPT",
);
const catch_all_drop_index = post_up.indexOf(
"iptables -A WGUI0FWD -j DROP",
);
expect(scoped_accept_index).toBeGreaterThan(-1);
expect(catch_all_drop_index).toBeGreaterThan(scoped_accept_index);
}); });
test("rejects oversized host id", () => { test("rejects oversized host id", () => {
@@ -15,8 +15,8 @@ type HostIptablesClient = {
type Params = { type Params = {
host_id: number; host_id: number;
interface_name: string; interface_name: string;
target_interface: string;
host_subnet?: string; host_subnet?: string;
sibling_subnets?: string[];
clients?: HostIptablesClient[]; clients?: HostIptablesClient[];
}; };
@@ -85,8 +85,6 @@ function acceptLines({
source, source,
protocol, protocol,
ports, ports,
wg_interface,
target_interface,
host_subnet, host_subnet,
}: { }: {
forward_chain: string; forward_chain: string;
@@ -94,8 +92,6 @@ function acceptLines({
source: string; source: string;
protocol?: string; protocol?: string;
ports: string[]; ports: string[];
wg_interface: string;
target_interface: string;
host_subnet?: string; host_subnet?: string;
}) { }) {
const lines: string[] = []; const lines: string[] = [];
@@ -118,19 +114,6 @@ function acceptLines({
joinArgs([ joinArgs([
`iptables -A ${forward_chain}`, `iptables -A ${forward_chain}`,
`-s ${source}`, `-s ${source}`,
`-o ${target_interface}`,
proto ? `-p ${proto}` : undefined,
port_match_str,
`-j ACCEPT`,
]),
);
lines.push(
joinArgs([
`iptables -A ${forward_chain}`,
`-s ${source}`,
`-o ${wg_interface}`,
host_subnet ? `-d ${host_subnet}` : undefined,
proto ? `-p ${proto}` : undefined, proto ? `-p ${proto}` : undefined,
port_match_str, port_match_str,
`-j ACCEPT`, `-j ACCEPT`,
@@ -156,8 +139,8 @@ function acceptLines({
export default function buildHostIptablesScripts({ export default function buildHostIptablesScripts({
host_id, host_id,
interface_name, interface_name,
target_interface,
host_subnet, host_subnet,
sibling_subnets,
clients, clients,
}: Params) { }: Params) {
if (!Number.isSafeInteger(host_id) || host_id < 0 || host_id > 999999999) { if (!Number.isSafeInteger(host_id) || host_id < 0 || host_id > 999999999) {
@@ -174,13 +157,6 @@ export default function buildHostIptablesScripts({
}; };
} }
if (!SAFE_IFACE.test(target_interface)) {
return {
success: false,
msg: `Invalid target interface`,
};
}
const scoped_subnet = host_subnet?.trim(); const scoped_subnet = host_subnet?.trim();
if (scoped_subnet && !validateIpv4Cidr({ value: scoped_subnet })) { if (scoped_subnet && !validateIpv4Cidr({ value: scoped_subnet })) {
@@ -190,6 +166,12 @@ export default function buildHostIptablesScripts({
}; };
} }
const sibling_host_subnets = (sibling_subnets || []).filter(
(subnet) =>
subnet.trim() !== scoped_subnet &&
validateIpv4Cidr({ value: subnet.trim() }),
);
const { forward, input } = deriveIptablesChainNames({ host_id }); const { forward, input } = deriveIptablesChainNames({ host_id });
const masquerade_match = scoped_subnet ? `-s ${scoped_subnet} ` : ``; const masquerade_match = scoped_subnet ? `-s ${scoped_subnet} ` : ``;
@@ -222,6 +204,14 @@ export default function buildHostIptablesScripts({
const source = `${source_ip}/32`; const source = `${source_ip}/32`;
const rules = client.rules || []; const rules = client.rules || [];
for (let s = 0; s < sibling_host_subnets.length; s++) {
const sibling_host_subnet = sibling_host_subnets[s];
accept_lines.push(
`iptables -A ${forward} -s ${source} -d ${sibling_host_subnet} -j DROP`,
`iptables -A ${input} -s ${source} -d ${sibling_host_subnet} -j DROP`,
);
}
for (let r = 0; r < rules.length; r++) { for (let r = 0; r < rules.length; r++) {
const rule = rules[r]; const rule = rules[r];
@@ -243,14 +233,6 @@ export default function buildHostIptablesScripts({
joinArgs([ joinArgs([
`iptables -A ${forward}`, `iptables -A ${forward}`,
`-s ${source}`, `-s ${source}`,
`-o ${target_interface}`,
`-j ACCEPT`,
]),
joinArgs([
`iptables -A ${forward}`,
`-s ${source}`,
`-o ${interface_name}`,
scoped_subnet ? `-d ${scoped_subnet}` : undefined,
`-j ACCEPT`, `-j ACCEPT`,
]), ]),
joinArgs([ joinArgs([
@@ -280,8 +262,6 @@ export default function buildHostIptablesScripts({
source, source,
protocol: rule.protocol || "any", protocol: rule.protocol || "any",
ports: parsed_ports.ports, ports: parsed_ports.ports,
wg_interface: interface_name,
target_interface,
host_subnet: scoped_subnet, host_subnet: scoped_subnet,
}), }),
); );
@@ -5,7 +5,6 @@ import type {
} from "@/db/types/db"; } from "@/db/types/db";
import grabDirNames from "@/src/utils/grab-dir-names"; import grabDirNames from "@/src/utils/grab-dir-names";
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import grabHostNetworkInterface from "./grab-host-network-interface";
import type { APIResponseObject } from "@moduletrace/bunext/types"; import type { APIResponseObject } from "@moduletrace/bunext/types";
import BunSQLite from "@moduletrace/bun-sqlite"; import BunSQLite from "@moduletrace/bun-sqlite";
import type { TableType, User } from "@/src/types"; import type { TableType, User } from "@/src/types";
@@ -92,8 +91,31 @@ export default async function setupWireguardHost({
rules_by_client_id.set(rule.client_id, existing_rules); rules_by_client_id.set(rule.client_id, existing_rules);
} }
const TARGET_INTERFACE = const sibling_hosts_res = await BunSQLite.select<
host?.interface || (await grabHostNetworkInterface()); BUN_SQLITE_WGUI_HOSTS,
TableType
>({
table: "hosts",
});
const sibling_subnets = [
...new Set(
(sibling_hosts_res.payload || [])
.filter(
(host_row) =>
host_row.id != HOST_ID &&
typeof host_row.wg_ip_address == "string" &&
host_row.wg_ip_address.trim() != "",
)
.map((host_row) => {
const parts = String(host_row.wg_ip_address)
.trim()
.split(".");
return `${parts.slice(0, 3).join(".")}.0/24`;
}),
),
];
const HOST_WG_IP = host?.wg_ip_address || wg_subnet_ip; const HOST_WG_IP = host?.wg_ip_address || wg_subnet_ip;
if (!HOST_WG_IP) { if (!HOST_WG_IP) {
@@ -178,8 +200,8 @@ export default async function setupWireguardHost({
const iptables_scripts = buildHostIptablesScripts({ const iptables_scripts = buildHostIptablesScripts({
host_id: Number(HOST_ID), host_id: Number(HOST_ID),
interface_name: INTERFACE_NAME, interface_name: INTERFACE_NAME,
target_interface: TARGET_INTERFACE || "eth0",
host_subnet: HOST_SUBNET, host_subnet: HOST_SUBNET,
sibling_subnets,
clients: clients.map((client) => ({ clients: clients.map((client) => ({
id: client.id, id: client.id,
wg_ip_address: client.wg_ip_address, wg_ip_address: client.wg_ip_address,