Iptables hardening and dev server bind fix
This commit is contained in:
@@ -20,7 +20,9 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
expect(res.post_up).toContain("iptables -A WGUI0IN -j DROP");
|
expect(res.post_up).toContain("iptables -A WGUI0IN -j DROP");
|
||||||
expect(res.post_up).not.toContain("WGUI0FWD -s");
|
expect(res.post_up).not.toContain("WGUI0FWD -s");
|
||||||
expect(res.post_down).toContain("iptables -X WGUI0FWD");
|
expect(res.post_down).toContain("iptables -X WGUI0FWD");
|
||||||
expect(res.post_down).toContain("iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE");
|
expect(res.post_down).toContain(
|
||||||
|
"iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE 2>/dev/null || true",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("all access accepts the client on forward and input", () => {
|
test("all access accepts the client on forward and input", () => {
|
||||||
@@ -67,7 +69,7 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.3/32 -d 192.168.1.10/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).not.toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.3/32 -d 192.168.1.10/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",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -131,4 +133,110 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
"iptables -D FORWARD -i wgui0 -j ACCEPT 2>/dev/null || true",
|
"iptables -D FORWARD -i wgui0 -j ACCEPT 2>/dev/null || true",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("masquerade is scoped to the vpn subnet when host_subnet is set", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.2.0.0/24",
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
wg_ip_address: "10.2.0.10",
|
||||||
|
rules: [{ rule_type: "all" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -t nat -C POSTROUTING -s 10.2.0.0/24 -o eth0 -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s 10.2.0.0/24 -o eth0 -j MASQUERADE",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain(
|
||||||
|
"iptables -t nat -A POSTROUTING -i eth0 -j MASQUERADE",
|
||||||
|
);
|
||||||
|
expect(res.post_down).toContain(
|
||||||
|
"iptables -t nat -D POSTROUTING -s 10.2.0.0/24 -o eth0 -j MASQUERADE 2>/dev/null || true",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("masquerade falls back to the target interface without a subnet", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
wg_ip_address: "10.0.0.9",
|
||||||
|
rules: [{ rule_type: "all" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -t nat -C POSTROUTING -o eth0 -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("all access with host_subnet is scoped to the subnet", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_subnet: "10.6.0.0/24",
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
wg_ip_address: "10.0.0.7",
|
||||||
|
rules: [{ rule_type: "all" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.0.0.7/32 -d 10.6.0.0/24 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0IN -s 10.0.0.7/32 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.0.0.7/32 -o eth0 -j ACCEPT",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("port-only rules are not mirrored onto the input chain", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
wg_ip_address: "10.0.0.8",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
rule_type: "destination",
|
||||||
|
ports: "53",
|
||||||
|
protocol: "any",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
expect(res.post_up).toContain(
|
||||||
|
"iptables -A WGUI0FWD -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
|
);
|
||||||
|
expect(res.post_up).not.toContain(
|
||||||
|
"iptables -A WGUI0IN -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects oversized host id", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
host_id: 1e15,
|
||||||
|
clients: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(false);
|
||||||
|
expect(res.msg).toBe("Invalid host id");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -128,7 +128,11 @@ export default function buildHostIptablesScripts({
|
|||||||
host_subnet,
|
host_subnet,
|
||||||
clients,
|
clients,
|
||||||
}: Params) {
|
}: Params) {
|
||||||
if (!Number.isInteger(host_id) || host_id < 0) {
|
if (
|
||||||
|
!Number.isSafeInteger(host_id) ||
|
||||||
|
host_id < 0 ||
|
||||||
|
host_id > 999999999
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
msg: `Invalid host id`,
|
msg: `Invalid host id`,
|
||||||
@@ -159,6 +163,10 @@ export default function buildHostIptablesScripts({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { forward, input } = deriveIptablesChainNames({ host_id });
|
const { forward, input } = deriveIptablesChainNames({ host_id });
|
||||||
|
|
||||||
|
const masquerade_match = scoped_subnet
|
||||||
|
? `-s ${scoped_subnet} -o ${target_interface}`
|
||||||
|
: `-o ${target_interface}`;
|
||||||
const accept_lines: string[] = [];
|
const accept_lines: string[] = [];
|
||||||
const host_clients = clients || [];
|
const host_clients = clients || [];
|
||||||
|
|
||||||
@@ -208,7 +216,6 @@ export default function buildHostIptablesScripts({
|
|||||||
if (scoped_subnet) {
|
if (scoped_subnet) {
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
`iptables -A ${forward} -s ${source} -d ${scoped_subnet} -j ACCEPT`,
|
`iptables -A ${forward} -s ${source} -d ${scoped_subnet} -j ACCEPT`,
|
||||||
`iptables -A ${forward} -s ${source} -o ${target_interface} -j ACCEPT`,
|
|
||||||
`iptables -A ${input} -s ${source} -j ACCEPT`,
|
`iptables -A ${input} -s ${source} -j ACCEPT`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -241,13 +248,6 @@ export default function buildHostIptablesScripts({
|
|||||||
protocol: rule.protocol || "any",
|
protocol: rule.protocol || "any",
|
||||||
ports: parsed_ports.ports,
|
ports: parsed_ports.ports,
|
||||||
}),
|
}),
|
||||||
...acceptLines({
|
|
||||||
chain: input,
|
|
||||||
source,
|
|
||||||
destination,
|
|
||||||
protocol: rule.protocol || "any",
|
|
||||||
ports: parsed_ports.ports,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ export default function buildHostIptablesScripts({
|
|||||||
`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 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 -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`,
|
`iptables -t nat -C POSTROUTING ${masquerade_match} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING ${masquerade_match} -j MASQUERADE`,
|
||||||
``,
|
``,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
@@ -298,7 +298,7 @@ export default function buildHostIptablesScripts({
|
|||||||
`iptables -X ${forward} 2>/dev/null || true`,
|
`iptables -X ${forward} 2>/dev/null || true`,
|
||||||
`iptables -F ${input} 2>/dev/null || true`,
|
`iptables -F ${input} 2>/dev/null || true`,
|
||||||
`iptables -X ${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`,
|
`iptables -t nat -D POSTROUTING ${masquerade_match} -j MASQUERADE 2>/dev/null || true`,
|
||||||
``,
|
``,
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ const port = SiteData["ServerPort"];
|
|||||||
await bunext.bunextInit();
|
await bunext.bunextInit();
|
||||||
|
|
||||||
const server = Bun.serve({
|
const server = Bun.serve({
|
||||||
hostname: development ? "localhost" : "0.0.0.0",
|
hostname: "0.0.0.0",
|
||||||
async fetch(req, server) {
|
async fetch(req, server) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
|
|||||||
Reference in New Issue
Block a user