Allow wildcard rule access host public IP ports
This commit is contained in:
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "bunext-mariadb-preset",
|
"name": "wireguard-ui",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "sudo bun src/server.ts",
|
"dev": "sudo bun src/server.ts",
|
||||||
|
|||||||
@@ -66,12 +66,43 @@ 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 -p tcp -m multiport --dports 80,443 -j ACCEPT",
|
"iptables -A WGUI0FWD -s 10.0.0.3/32 -d 192.168.1.10/32 -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 -d 192.168.1.10/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destination overrides the host subnet scope on input", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.5.0.0/24",
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
wg_ip_address: "10.5.0.2",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
rule_type: "destination",
|
||||||
|
destination: "65.108.81.79",
|
||||||
|
ports: "443",
|
||||||
|
protocol: "tcp",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0IN -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain(
|
||||||
|
"iptables -A WGUI0IN -s 10.5.0.2/32 -d 10.5.0.0/24 -p tcp --dport 443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).not.toContain("-d 192.168.1.10/32");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("protocol any with ports emits tcp and udp", () => {
|
test("protocol any with ports emits tcp and udp", () => {
|
||||||
@@ -310,6 +341,120 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("allow_all_ips clients get full input and forward access", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.7.0.0/24",
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
wg_ip_address: "10.7.0.3",
|
||||||
|
allow_all_ips: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.7.0.3/32 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0IN -s 10.7.0.3/32 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain("-d 10.7.0.0/24");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("allow_all_ips clients still cannot jump to sibling host subnets", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.7.0.0/24",
|
||||||
|
sibling_subnets: ["10.3.0.0/24"],
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
wg_ip_address: "10.7.0.4",
|
||||||
|
allow_all_ips: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
const post_up = res.post_up ?? "";
|
||||||
|
|
||||||
|
const sibling_drop =
|
||||||
|
"iptables -A WGUI0IN -s 10.7.0.4/32 -d 10.3.0.0/24 -j DROP";
|
||||||
|
const input_accept = "iptables -A WGUI0IN -s 10.7.0.4/32 -j ACCEPT";
|
||||||
|
|
||||||
|
expect(post_up).toContain(sibling_drop);
|
||||||
|
expect(post_up).toContain(input_accept);
|
||||||
|
expect(post_up.indexOf(input_accept)).toBeGreaterThan(
|
||||||
|
post_up.indexOf(sibling_drop),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("explicit rules are ignored for allow_all_ips clients", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
wg_ip_address: "10.7.0.5",
|
||||||
|
allow_all_ips: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
rule_type: "destination",
|
||||||
|
destination: "192.0.2.1",
|
||||||
|
ports: "80",
|
||||||
|
protocol: "tcp",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.7.0.5/32 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain("192.0.2.1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("destination rules cannot bypass sibling subnet isolation", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.0.0.0/24",
|
||||||
|
sibling_subnets: ["10.1.0.0/24"],
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
wg_ip_address: "10.0.0.6",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
rule_type: "destination",
|
||||||
|
destination: "10.1.0.25",
|
||||||
|
ports: "80",
|
||||||
|
protocol: "tcp",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
const post_up = res.post_up ?? "";
|
||||||
|
|
||||||
|
const sibling_drop =
|
||||||
|
"iptables -A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.0/24 -j DROP";
|
||||||
|
const accept =
|
||||||
|
"iptables -A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.25/32 -p tcp --dport 80 -j ACCEPT";
|
||||||
|
|
||||||
|
expect(post_up).toContain(sibling_drop);
|
||||||
|
expect(post_up).toContain(accept);
|
||||||
|
expect(post_up.indexOf(accept)).toBeGreaterThan(
|
||||||
|
post_up.indexOf(sibling_drop),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("rejects oversized host id", () => {
|
test("rejects oversized host id", () => {
|
||||||
const res = buildHostIptablesScripts({
|
const res = buildHostIptablesScripts({
|
||||||
...base,
|
...base,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import validateClientRule from "./validate-client-rule";
|
|||||||
type HostIptablesClient = {
|
type HostIptablesClient = {
|
||||||
id?: number | "";
|
id?: number | "";
|
||||||
wg_ip_address?: string;
|
wg_ip_address?: string;
|
||||||
|
allow_all_ips?: boolean;
|
||||||
rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ function acceptLines({
|
|||||||
source,
|
source,
|
||||||
protocol,
|
protocol,
|
||||||
ports,
|
ports,
|
||||||
|
destination,
|
||||||
host_subnet,
|
host_subnet,
|
||||||
}: {
|
}: {
|
||||||
forward_chain: string;
|
forward_chain: string;
|
||||||
@@ -92,6 +94,7 @@ function acceptLines({
|
|||||||
source: string;
|
source: string;
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
ports: string[];
|
ports: string[];
|
||||||
|
destination?: string;
|
||||||
host_subnet?: string;
|
host_subnet?: string;
|
||||||
}) {
|
}) {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
@@ -109,11 +112,14 @@ function acceptLines({
|
|||||||
const port_match_str = proto
|
const port_match_str = proto
|
||||||
? portMatch({ ports: chunk })
|
? portMatch({ ports: chunk })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const direction = destination ? `-d ${destination}` : undefined;
|
||||||
|
const input_destination = destination || host_subnet;
|
||||||
|
|
||||||
lines.push(
|
lines.push(
|
||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${forward_chain}`,
|
`iptables -A ${forward_chain}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
|
direction,
|
||||||
proto ? `-p ${proto}` : undefined,
|
proto ? `-p ${proto}` : undefined,
|
||||||
port_match_str,
|
port_match_str,
|
||||||
`-j ACCEPT`,
|
`-j ACCEPT`,
|
||||||
@@ -124,7 +130,7 @@ function acceptLines({
|
|||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${input_chain}`,
|
`iptables -A ${input_chain}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
host_subnet ? `-d ${host_subnet}` : undefined,
|
input_destination ? `-d ${input_destination}` : undefined,
|
||||||
proto ? `-p ${proto}` : undefined,
|
proto ? `-p ${proto}` : undefined,
|
||||||
port_match_str,
|
port_match_str,
|
||||||
`-j ACCEPT`,
|
`-j ACCEPT`,
|
||||||
@@ -212,6 +218,15 @@ export default function buildHostIptablesScripts({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client.allow_all_ips) {
|
||||||
|
accept_lines.push(
|
||||||
|
`iptables -A ${forward} -s ${source} -j ACCEPT`,
|
||||||
|
`iptables -A ${input} -s ${source} -j ACCEPT`,
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (let r = 0; r < rules.length; r++) {
|
for (let r = 0; r < rules.length; r++) {
|
||||||
const rule = rules[r];
|
const rule = rules[r];
|
||||||
|
|
||||||
@@ -255,6 +270,10 @@ export default function buildHostIptablesScripts({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const destination = normalizeIptablesDestination({
|
||||||
|
destination: rule.destination,
|
||||||
|
});
|
||||||
|
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
...acceptLines({
|
...acceptLines({
|
||||||
forward_chain: forward,
|
forward_chain: forward,
|
||||||
@@ -262,6 +281,7 @@ export default function buildHostIptablesScripts({
|
|||||||
source,
|
source,
|
||||||
protocol: rule.protocol || "any",
|
protocol: rule.protocol || "any",
|
||||||
ports: parsed_ports.ports,
|
ports: parsed_ports.ports,
|
||||||
|
destination,
|
||||||
host_subnet: scoped_subnet,
|
host_subnet: scoped_subnet,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export default async function setupWireguardHost({
|
|||||||
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,
|
||||||
|
allow_all_ips: client.allow_all_ips == 1,
|
||||||
rules:
|
rules:
|
||||||
client.id && typeof client.id == "number"
|
client.id && typeof client.id == "number"
|
||||||
? rules_by_client_id.get(client.id) || []
|
? rules_by_client_id.get(client.id) || []
|
||||||
|
|||||||
Reference in New Issue
Block a user