feat(network): add configurable VM egress firewall policies and WireGuard-based host routing

This commit is contained in:
2026-05-01 18:32:08 +00:00
parent 69e66cba00
commit 8c8b692fc1
7 changed files with 998 additions and 57 deletions
+147
View File
@@ -23,6 +23,15 @@ async function getRejectedError(promise: Promise<unknown>): Promise<unknown> {
return undefined;
}
function getThrownError(fn: () => unknown): unknown {
try {
fn();
} catch (err) {
return err;
}
return undefined;
}
function sha256Buffer(buffer: Buffer): string {
return crypto.createHash('sha256').update(buffer).digest('hex');
}
@@ -656,6 +665,118 @@ tap.test('NetworkManager - getGuestNetworkBootArgs() should format correctly', a
expect(bootArgs).toEqual('ip=172.30.0.2::172.30.0.1:255.255.255.0::eth0:off');
});
tap.test('NetworkManager - constructor should accept valid egress firewall config', async () => {
const nm = new NetworkManager({
firewall: {
egress: {
defaultAction: 'deny',
rules: [
{ action: 'allow', to: '1.1.1.1', protocol: 'tcp', ports: [80, 443] },
{ action: 'deny', to: '10.0.0.0/8' },
{ action: 'allow', protocol: 'icmp' },
],
},
},
});
expect(nm).toBeTruthy();
});
tap.test('NetworkManager - constructor should reject invalid firewall CIDRs and ports', async () => {
const invalidCidrError = getThrownError(() => new NetworkManager({
firewall: {
egress: {
rules: [{ action: 'allow', to: '300.1.1.1/32' }],
},
},
}));
expect(invalidCidrError).toBeInstanceOf(SmartVMError);
expect((invalidCidrError as SmartVMError).code).toEqual('INVALID_FIREWALL_CONFIG');
const invalidPortError = getThrownError(() => new NetworkManager({
firewall: {
egress: {
rules: [{ action: 'allow', protocol: 'icmp', ports: 53 }],
},
},
}));
expect(invalidPortError).toBeInstanceOf(SmartVMError);
expect((invalidPortError as SmartVMError).code).toEqual('INVALID_FIREWALL_CONFIG');
});
tap.test('NetworkManager - constructor should accept valid WireGuard managed config', async () => {
const nm = new NetworkManager({
wireguard: {
interfaceName: 'svwgtest0',
routeTable: 51821,
config: `
# comments are ignored
[Interface]
PrivateKey = test-private-key
Address = 10.70.0.2/32
DNS = 1.1.1.1
MTU = 1420
Table = off
[Peer]
PublicKey = test-public-key
AllowedIPs = 0.0.0.0/0
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25
`,
},
});
expect(nm).toBeTruthy();
});
tap.test('NetworkManager - constructor should reject unsafe WireGuard hook fields', async () => {
const error = getThrownError(() => new NetworkManager({
wireguard: {
config: `
[Interface]
PrivateKey = test-private-key
Address = 10.70.0.2/32
PostUp = iptables -A OUTPUT -j ACCEPT
`,
},
}));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_WIREGUARD_CONFIG');
});
tap.test('NetworkManager - constructor should reject IPv6 WireGuard AllowedIPs', async () => {
const error = getThrownError(() => new NetworkManager({
wireguard: {
config: `
[Interface]
PrivateKey = test-private-key
Address = 10.70.0.2/32
[Peer]
PublicKey = test-public-key
AllowedIPs = ::/0
`,
},
}));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_WIREGUARD_CONFIG');
});
tap.test('NetworkManager - constructor should reject mixed WireGuard modes', async () => {
const error = getThrownError(() => new NetworkManager({
wireguard: {
existingInterface: 'wg0',
config: '[Interface]\nPrivateKey = test-private-key\nAddress = 10.70.0.2/32\n',
} as any,
}));
expect(error).toBeInstanceOf(SmartVMError);
expect((error as SmartVMError).code).toEqual('INVALID_WIREGUARD_CONFIG');
});
// ============================================================
// MicroVM Tests
// ============================================================
@@ -783,6 +904,32 @@ tap.test('SmartVM - instantiation with custom options', async () => {
expect(smartvm).toBeTruthy();
});
tap.test('SmartVM - should forward firewall and WireGuard options to NetworkManager', async () => {
const firewall = {
egress: {
defaultAction: 'deny' as const,
rules: [{ action: 'allow' as const, to: '1.1.1.1', protocol: 'tcp' as const, ports: 443 }],
},
};
const wireguard = {
existingInterface: 'wgsmartvm0',
routeAllVmTraffic: false,
};
const smartvm = new SmartVM({
dataDir: '/tmp/smartvm-test',
firecrackerBinaryPath: '/bin/false',
firewall,
wireguard,
});
try {
expect((smartvm.networkManager as any).firewall).toEqual(firewall);
expect((smartvm.networkManager as any).wireguard).toEqual(wireguard);
} finally {
await smartvm.cleanup();
}
});
tap.test('SmartVM - createVM() should track created VMs', async () => {
const smartvm = new SmartVM({
dataDir: '/tmp/smartvm-test',