Files
smartvpn/test/test.wireguard.node.ts

354 lines
10 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import {
VpnConfig,
VpnServer,
WgConfigGenerator,
} from '../ts/index.js';
import type {
IVpnClientConfig,
IVpnServerConfig,
IVpnServerOptions,
IWgPeerConfig,
} from '../ts/index.js';
// ============================================================================
// WireGuard config validation — client
// ============================================================================
// A valid 32-byte key in base64 (44 chars)
const VALID_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
const VALID_KEY_2 = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=';
tap.test('WG client config: valid wireguard config passes validation', async () => {
const config: IVpnClientConfig = {
serverUrl: '', // not needed for WG
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgPrivateKey: VALID_KEY_2,
wgAddress: '10.8.0.2',
wgEndpoint: 'vpn.example.com:51820',
wgAllowedIps: ['0.0.0.0/0'],
};
VpnConfig.validateClientConfig(config);
});
tap.test('WG client config: rejects missing wgPrivateKey', async () => {
const config: IVpnClientConfig = {
serverUrl: '',
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgAddress: '10.8.0.2',
wgEndpoint: 'vpn.example.com:51820',
};
let threw = false;
try {
VpnConfig.validateClientConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('wgPrivateKey');
}
expect(threw).toBeTrue();
});
tap.test('WG client config: rejects missing wgAddress', async () => {
const config: IVpnClientConfig = {
serverUrl: '',
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgPrivateKey: VALID_KEY_2,
wgEndpoint: 'vpn.example.com:51820',
};
let threw = false;
try {
VpnConfig.validateClientConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('wgAddress');
}
expect(threw).toBeTrue();
});
tap.test('WG client config: rejects missing wgEndpoint', async () => {
const config: IVpnClientConfig = {
serverUrl: '',
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgPrivateKey: VALID_KEY_2,
wgAddress: '10.8.0.2',
};
let threw = false;
try {
VpnConfig.validateClientConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('wgEndpoint');
}
expect(threw).toBeTrue();
});
tap.test('WG client config: rejects invalid key length', async () => {
const config: IVpnClientConfig = {
serverUrl: '',
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgPrivateKey: 'tooshort',
wgAddress: '10.8.0.2',
wgEndpoint: 'vpn.example.com:51820',
};
let threw = false;
try {
VpnConfig.validateClientConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('44 characters');
}
expect(threw).toBeTrue();
});
tap.test('WG client config: rejects invalid CIDR in allowedIps', async () => {
const config: IVpnClientConfig = {
serverUrl: '',
serverPublicKey: VALID_KEY,
transport: 'wireguard',
wgPrivateKey: VALID_KEY_2,
wgAddress: '10.8.0.2',
wgEndpoint: 'vpn.example.com:51820',
wgAllowedIps: ['not-a-cidr'],
};
let threw = false;
try {
VpnConfig.validateClientConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('CIDR');
}
expect(threw).toBeTrue();
});
// ============================================================================
// WireGuard config validation — server
// ============================================================================
tap.test('WG server config: valid config passes validation', async () => {
const config: IVpnServerConfig = {
listenAddr: '',
privateKey: VALID_KEY,
publicKey: VALID_KEY_2,
subnet: '10.8.0.0/24',
transportMode: 'wireguard',
wgPeers: [
{
publicKey: VALID_KEY_2,
allowedIps: ['10.8.0.2/32'],
},
],
};
VpnConfig.validateServerConfig(config);
});
tap.test('WG server config: rejects empty wgPeers', async () => {
const config: IVpnServerConfig = {
listenAddr: '',
privateKey: VALID_KEY,
publicKey: VALID_KEY_2,
subnet: '10.8.0.0/24',
transportMode: 'wireguard',
wgPeers: [],
};
let threw = false;
try {
VpnConfig.validateServerConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('wgPeers');
}
expect(threw).toBeTrue();
});
tap.test('WG server config: rejects peer without publicKey', async () => {
const config: IVpnServerConfig = {
listenAddr: '',
privateKey: VALID_KEY,
publicKey: VALID_KEY_2,
subnet: '10.8.0.0/24',
transportMode: 'wireguard',
wgPeers: [
{
publicKey: '',
allowedIps: ['10.8.0.2/32'],
},
],
};
let threw = false;
try {
VpnConfig.validateServerConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('publicKey');
}
expect(threw).toBeTrue();
});
tap.test('WG server config: rejects invalid wgListenPort', async () => {
const config: IVpnServerConfig = {
listenAddr: '',
privateKey: VALID_KEY,
publicKey: VALID_KEY_2,
subnet: '10.8.0.0/24',
transportMode: 'wireguard',
wgListenPort: 0,
wgPeers: [
{
publicKey: VALID_KEY_2,
allowedIps: ['10.8.0.2/32'],
},
],
};
let threw = false;
try {
VpnConfig.validateServerConfig(config);
} catch (e) {
threw = true;
expect((e as Error).message).toContain('wgListenPort');
}
expect(threw).toBeTrue();
});
// ============================================================================
// WireGuard keypair generation via daemon
// ============================================================================
let server: VpnServer;
tap.test('WG: spawn server daemon for keypair generation', async () => {
const options: IVpnServerOptions = {
transport: { transport: 'stdio' },
};
server = new VpnServer(options);
const started = await server['bridge'].start();
expect(started).toBeTrue();
expect(server.running).toBeTrue();
});
tap.test('WG: generateWgKeypair returns valid keypair', async () => {
const keypair = await server.generateWgKeypair();
expect(keypair.publicKey).toBeTypeofString();
expect(keypair.privateKey).toBeTypeofString();
// WireGuard keys: base64 of 32 bytes = 44 characters
expect(keypair.publicKey.length).toEqual(44);
expect(keypair.privateKey.length).toEqual(44);
// Verify they decode to 32 bytes
const pubBuf = Buffer.from(keypair.publicKey, 'base64');
const privBuf = Buffer.from(keypair.privateKey, 'base64');
expect(pubBuf.length).toEqual(32);
expect(privBuf.length).toEqual(32);
});
tap.test('WG: generateWgKeypair returns unique keys each time', async () => {
const kp1 = await server.generateWgKeypair();
const kp2 = await server.generateWgKeypair();
expect(kp1.publicKey).not.toEqual(kp2.publicKey);
expect(kp1.privateKey).not.toEqual(kp2.privateKey);
});
tap.test('WG: stop server daemon', async () => {
server.stop();
await new Promise((resolve) => setTimeout(resolve, 500));
expect(server.running).toBeFalse();
});
// ============================================================================
// WireGuard config file generation
// ============================================================================
tap.test('WgConfigGenerator: generate client config', async () => {
const conf = WgConfigGenerator.generateClientConfig({
privateKey: 'clientPrivateKeyBase64====================',
address: '10.8.0.2/24',
dns: ['1.1.1.1', '8.8.8.8'],
mtu: 1420,
peer: {
publicKey: 'serverPublicKeyBase64====================',
endpoint: 'vpn.example.com:51820',
allowedIps: ['0.0.0.0/0', '::/0'],
persistentKeepalive: 25,
},
});
expect(conf).toContain('[Interface]');
expect(conf).toContain('PrivateKey = clientPrivateKeyBase64====================');
expect(conf).toContain('Address = 10.8.0.2/24');
expect(conf).toContain('DNS = 1.1.1.1, 8.8.8.8');
expect(conf).toContain('MTU = 1420');
expect(conf).toContain('[Peer]');
expect(conf).toContain('PublicKey = serverPublicKeyBase64====================');
expect(conf).toContain('Endpoint = vpn.example.com:51820');
expect(conf).toContain('AllowedIPs = 0.0.0.0/0, ::/0');
expect(conf).toContain('PersistentKeepalive = 25');
});
tap.test('WgConfigGenerator: generate client config without optional fields', async () => {
const conf = WgConfigGenerator.generateClientConfig({
privateKey: 'key1',
address: '10.0.0.2/32',
peer: {
publicKey: 'key2',
endpoint: 'server:51820',
allowedIps: ['10.0.0.0/24'],
},
});
expect(conf).toContain('[Interface]');
expect(conf).not.toContain('DNS');
expect(conf).not.toContain('MTU');
expect(conf).not.toContain('PresharedKey');
expect(conf).not.toContain('PersistentKeepalive');
});
tap.test('WgConfigGenerator: generate server config with NAT', async () => {
const conf = WgConfigGenerator.generateServerConfig({
privateKey: 'serverPrivKey',
address: '10.8.0.1/24',
listenPort: 51820,
dns: ['1.1.1.1'],
enableNat: true,
natInterface: 'ens3',
peers: [
{
publicKey: 'peer1PubKey',
allowedIps: ['10.8.0.2/32'],
presharedKey: 'psk1',
persistentKeepalive: 25,
},
{
publicKey: 'peer2PubKey',
allowedIps: ['10.8.0.3/32'],
},
],
});
expect(conf).toContain('[Interface]');
expect(conf).toContain('ListenPort = 51820');
expect(conf).toContain('PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE');
expect(conf).toContain('PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE');
// Two [Peer] sections
const peerCount = (conf.match(/\[Peer\]/g) || []).length;
expect(peerCount).toEqual(2);
expect(conf).toContain('PresharedKey = psk1');
});
tap.test('WgConfigGenerator: generate server config without NAT', async () => {
const conf = WgConfigGenerator.generateServerConfig({
privateKey: 'serverPrivKey',
address: '10.8.0.1/24',
listenPort: 51820,
peers: [
{
publicKey: 'peerKey',
allowedIps: ['10.8.0.2/32'],
},
],
});
expect(conf).not.toContain('PostUp');
expect(conf).not.toContain('PostDown');
});
export default tap.start();