Iptables generator funciton bugfix followup: allow host network ports
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user