From 42771b7baae9c6ea56b1c838d5b311105ea50856 Mon Sep 17 00:00:00 2001 From: Bernd Storath <999999bst@gmail.com> Date: Thu, 5 Sep 2024 13:59:07 +0200 Subject: [PATCH] add wireguard helpers --- src/server/utils/WireGuard.ts | 104 ++++++----------- src/server/utils/wgHelper.ts | 111 +++++++++++++++++++ src/services/database/migrations/1.ts | 8 +- src/services/database/repositories/client.ts | 1 + 4 files changed, 147 insertions(+), 77 deletions(-) create mode 100644 src/server/utils/wgHelper.ts diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index 99093c0d..593fc5a7 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -21,37 +21,18 @@ class WireGuard { async #saveWireguardConfig() { const system = await Database.getSystem(); const clients = await Database.getClients(); - const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; - const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; - let result = ` -# Note: Do not edit this file directly. -# Your changes will be overwritten! - -# Server -[Interface] -PrivateKey = ${system.interface.privateKey} -Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block} -ListenPort = ${system.wgPort} -PreUp = ${system.iptables.PreUp} -PostUp = ${system.iptables.PostUp} -PreDown = ${system.iptables.PreDown} -PostDown = ${system.iptables.PostDown} -`; - - for (const [clientId, client] of Object.entries(clients)) { - if (!client.enabled) continue; - - result += ` - -# Client: ${client.name} (${clientId}) -[Peer] -PublicKey = ${client.publicKey} -PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.address4}/32, ${client.address6}/128`; + const result = []; + result.push(wg.generateServerInterface(system)); + + for (const client of Object.values(clients)) { + if (!client.enabled) { + continue; + } + result.push(wg.generateServerPeer(client)); } DEBUG('Config saving...'); - await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result, { + await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result.join('\n'), { mode: 0o600, }); DEBUG('Config saved.'); @@ -59,7 +40,7 @@ AllowedIPs = ${client.address4}/32, ${client.address6}/128`; async #syncWireguardConfig() { DEBUG('Config syncing...'); - await exec('wg syncconf wg0 <(wg-quick strip wg0)'); + await wg.sync(); DEBUG('Config synced.'); } @@ -86,25 +67,16 @@ AllowedIPs = ${client.address4}/32, ${client.address6}/128`; })); // Loop WireGuard status - const dump = await exec('wg show wg0 dump', { - log: false, - }); - dump - .trim() - .split('\n') - .slice(1) - .forEach((line) => { - const [ - publicKey, - _preSharedKey, - endpoint, - _allowedIps, - latestHandshakeAt, - transferRx, - transferTx, - persistentKeepalive, - ] = line.split('\t'); - + const dump = await wg.dump(); + dump.forEach( + ({ + publicKey, + latestHandshakeAt, + endpoint, + transferRx, + transferTx, + persistentKeepalive, + }) => { const client = clients.find((client) => client.publicKey === publicKey); if (!client) return; @@ -116,7 +88,8 @@ AllowedIPs = ${client.address4}/32, ${client.address6}/128`; client.transferRx = Number(transferRx); client.transferTx = Number(transferTx); client.persistentKeepalive = persistentKeepalive ?? null; - }); + } + ); return clients; } @@ -136,22 +109,8 @@ AllowedIPs = ${client.address4}/32, ${client.address6}/128`; async getClientConfiguration({ clientId }: { clientId: string }) { const system = await Database.getSystem(); const client = await this.getClient({ clientId }); - const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; - const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; - - return ` -[Interface] -PrivateKey = ${client.privateKey} -Address = ${client.address4}/${cidr4Block}, ${client.address6}/${cidr6Block} -DNS = ${system.userConfig.defaultDns.join(', ')} -MTU = ${system.userConfig.mtu} - -[Peer] -PublicKey = ${system.interface.publicKey} -PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.allowedIPs.join(', ')} -PersistentKeepalive = ${client.persistentKeepalive} -Endpoint = ${system.wgHost}:${system.wgConfigPort}`; + + return wg.generateClientConfig(system, client); } async getClientQRCodeSVG({ clientId }: { clientId: string }) { @@ -172,11 +131,9 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; const system = await Database.getSystem(); const clients = await Database.getClients(); - const privateKey = await exec('wg genkey'); - const publicKey = await exec(`echo ${privateKey} | wg pubkey`, { - log: 'echo ***hidden*** | wg pubkey', - }); - const preSharedKey = await exec('wg genpsk'); + const privateKey = await wg.generatePrivateKey(); + const publicKey = await wg.getPublicKey(privateKey); + const preSharedKey = await wg.generatePresharedKey(); // Calculate next IP const cidr4 = parseCidr(system.userConfig.address4Range); @@ -239,6 +196,7 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; expiresAt: null, enabled: true, allowedIPs: system.userConfig.allowedIps, + serverAllowedIPs: null, persistentKeepalive: system.userConfig.persistentKeepalive, }; @@ -374,8 +332,8 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; }); DEBUG('Starting Wireguard'); await this.#saveWireguardConfig(); - await exec('wg-quick down wg0').catch(() => {}); - await exec('wg-quick up wg0').catch((err) => { + await wg.down().catch(() => {}); + await wg.up().catch((err) => { if ( err && err.message && @@ -407,7 +365,7 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; // Shutdown wireguard async Shutdown() { - await exec('wg-quick down wg0').catch(() => {}); + await wg.down().catch(() => {}); } async cronJob() { diff --git a/src/server/utils/wgHelper.ts b/src/server/utils/wgHelper.ts new file mode 100644 index 00000000..a1420a98 --- /dev/null +++ b/src/server/utils/wgHelper.ts @@ -0,0 +1,111 @@ +import { parseCidr } from 'cidr-tools'; +import type { Client } from '~~/services/database/repositories/client'; +import type { System } from '~~/services/database/repositories/system'; + +export const wg = { + generateServerPeer: (client: Client) => { + return `# Client: ${client.name} (${client.id}) +[Peer] +PublicKey = ${client.publicKey} +PresharedKey = ${client.preSharedKey} +AllowedIPs = ${client.address4}/32, ${client.address6}/128${client.serverAllowedIPs ? ` ${client.serverAllowedIPs.join(', ')}` : ''}`; + }, + + generateServerInterface: (system: System) => { + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; + + return `# Note: Do not edit this file directly. +# Your changes will be overwritten! + +# Server +[Interface] +PrivateKey = ${system.interface.privateKey} +Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block} +ListenPort = ${system.wgPort} +PreUp = ${system.iptables.PreUp} +PostUp = ${system.iptables.PostUp} +PreDown = ${system.iptables.PreDown} +PostDown = ${system.iptables.PostDown}`; + }, + + generateClientConfig: (system: System, client: Client) => { + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; + + return `[Interface] +PrivateKey = ${client.privateKey} +Address = ${client.address4}/${cidr4Block}, ${client.address6}/${cidr6Block} +DNS = ${system.userConfig.defaultDns.join(', ')} +MTU = ${system.userConfig.mtu} + +[Peer] +PublicKey = ${system.interface.publicKey} +PresharedKey = ${client.preSharedKey} +AllowedIPs = ${client.allowedIPs.join(', ')} +PersistentKeepalive = ${client.persistentKeepalive} +Endpoint = ${system.wgHost}:${system.wgConfigPort}`; + }, + + // TODO?: generate keys using plain javascript + + generatePrivateKey: () => { + return exec('wg genkey'); + }, + + getPublicKey: (privateKey: string) => { + return exec(`echo ${privateKey} | wg pubkey`, { + log: 'echo ***hidden*** | wg pubkey', + }); + }, + + generatePresharedKey: () => { + return exec('wg genpsk'); + }, + + up: () => { + return exec('wg-quick up wg0'); + }, + + down: () => { + return exec('wg-quick down wg0'); + }, + + sync: () => { + return exec('wg syncconf wg0 <(wg-quick strip wg0)'); + }, + + // TODO: properly convert + dump: async () => { + const rawDump = await exec('wg show wg0 dump', { + log: false, + }); + return rawDump + .trim() + .split('\n') + .slice(1) + .map((line) => { + const [ + publicKey, + preSharedKey, + endpoint, + allowedIPs, + latestHandshakeAt, + transferRx, + transferTx, + persistentKeepalive, + ] = line.split('\t'); + + return { + publicKey, + preSharedKey, + endpoint, + allowedIPs, + latestHandshakeAt, + transferRx, + transferTx, + persistentKeepalive, + }; + }); + }, +}; diff --git a/src/services/database/migrations/1.ts b/src/services/database/migrations/1.ts index 79da292b..8ca68493 100644 --- a/src/services/database/migrations/1.ts +++ b/src/services/database/migrations/1.ts @@ -5,14 +5,14 @@ import { parseCidr } from 'cidr-tools'; import { stringifyIp } from 'ip-bigint'; export async function run1(db: Low) { - const privateKey = await exec('wg genkey'); - const publicKey = await exec(`echo ${privateKey} | wg pubkey`, { - log: 'echo ***hidden*** | wg pubkey', - }); + const privateKey = await wg.generatePrivateKey(); + const publicKey = await wg.getPublicKey(privateKey); + const address4Range = '10.8.0.0/24'; const address6Range = 'fdcc:ad94:bacf:61a4::cafe:0/112'; const cidr4 = parseCidr(address4Range); const cidr6 = parseCidr(address6Range); + const database: Database = { migrations: [], system: { diff --git a/src/services/database/repositories/client.ts b/src/services/database/repositories/client.ts index 3d641c23..4122c0ca 100644 --- a/src/services/database/repositories/client.ts +++ b/src/services/database/repositories/client.ts @@ -16,6 +16,7 @@ export type Client = { expiresAt: string | null; endpoint: string | null; allowedIPs: string[]; + serverAllowedIPs: string[] | null; oneTimeLink: OneTimeLink | null; /** ISO String */ createdAt: string;