UNPKG

@synet/net

Version:

Network abstraction layer for Synet. visit https://syntehtism.ai for more information.

367 lines (366 loc) 15.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FileConfigurationRepository = void 0; const path = __importStar(require("node:path")); const patterns_1 = require("@synet/patterns"); /** * File-based implementation of ConfigurationRepository * Saves and retrieves WireGuard configurations from .conf files */ class FileConfigurationRepository { constructor(fileSystem, configDir = path.join(process.env.HOME || "", ".synet"), logger) { this.fileSystem = fileSystem; this.configDir = configDir; this.logger = logger; } getConfigPath(interfaceName) { return path.join(this.configDir, `${interfaceName}.conf`); } /** * Parse a complete WireGuard configuration file */ parseConfig(content) { return { interfaceConfig: this.parseInterfaceSection(content), peers: this.parsePeerSections(content), }; } /** * Read and parse configuration file */ async readConfig(interfaceName) { const configPath = this.getConfigPath(interfaceName); const exists = await this.fileSystem.exists(configPath); if (!exists) { return { interfaceConfig: null, peers: [] }; } const content = await this.fileSystem.readFile(configPath); return this.parseConfig(content); } /** * Generate a complete WireGuard configuration file content */ generateConfig(interfaceConfig, peers) { let content = ""; // Add interface section if exists if (interfaceConfig) { content += this.generateInterfaceSection(interfaceConfig); // Add a blank line after interface section if there are peers if (peers.length > 0) { content += "\n"; } } // Add peer sections for (let i = 0; i < peers.length; i++) { content += this.generatePeerSection(peers[i]); // Add a blank line between peers, but not after the last peer if (i < peers.length - 1) { content += "\n"; } } return content; } /** * Write configuration to file */ async writeConfig(interfaceName, interfaceConfig, peers) { try { const configPath = this.getConfigPath(interfaceName); await this.fileSystem.ensureDir(this.configDir); const content = this.generateConfig(interfaceConfig, peers); await this.fileSystem.writeFile(configPath, content); await this.fileSystem.chmod(configPath, 0o600); // Secure permissions return patterns_1.Result.success(undefined); } catch (error) { if (error instanceof Error) { return patterns_1.Result.fail(`Failed to write configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to write configuration: Unknown error"); } } async getInterfaceConfig(interfaceName) { try { const { interfaceConfig } = await this.readConfig(interfaceName); return patterns_1.Result.success(interfaceConfig); } catch (error) { this.logger?.error(`Failed to get interface config for ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to read interface configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to read interface configuration: Unknown error"); } } async saveInterfaceConfig(interfaceName, config) { try { const { peers } = await this.readConfig(interfaceName); return await this.writeConfig(interfaceName, config, peers); } catch (error) { this.logger?.error(`Failed to save interface config for ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to save interface configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to save interface configuration: Unknown error"); } } /** * Removes an entire interface configuration file * @param interfaceName Name of the interface to remove */ async removeInterfaceConfig(interfaceName) { try { const configPath = this.getConfigPath(interfaceName); const exists = await this.fileSystem.exists(configPath); if (!exists) { // If file doesn't exist, consider it a success but log a warning this.logger?.warn(`No configuration found for interface ${interfaceName} to remove`); return patterns_1.Result.success(undefined); } await this.fileSystem.deleteFile(configPath); this.logger?.info(`Removed configuration for interface ${interfaceName}`); return patterns_1.Result.success(undefined); } catch (error) { this.logger?.error(`Failed to remove interface ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to remove interface configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to remove interface configuration: Unknown error"); } } async getPeers(interfaceName) { try { const { peers } = await this.readConfig(interfaceName); return patterns_1.Result.success(peers); } catch (error) { this.logger?.error(`Failed to get peers for ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to read peer configurations: ${error.message}`, error); } return patterns_1.Result.fail("Failed to read peer configurations: Unknown error"); } } async savePeer(interfaceName, newPeer) { try { const { interfaceConfig, peers } = await this.readConfig(interfaceName); // Remove any existing peer with the same public key const filteredPeers = peers.filter((peer) => peer.publicKey !== newPeer.publicKey); // Add the new peer filteredPeers.push(newPeer); return await this.writeConfig(interfaceName, interfaceConfig, filteredPeers); } catch (error) { this.logger?.error(`Failed to save peer for ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to save peer configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to save peer configuration: Unknown error"); } } async removePeer(interfaceName, publicKey) { try { const { interfaceConfig, peers } = await this.readConfig(interfaceName); // Filter out the peer to remove const filteredPeers = peers.filter((peer) => peer.publicKey !== publicKey); // If no peers were removed, still succeed but log a warning if (filteredPeers.length === peers.length) { this.logger?.warn(`No peer with public key ${publicKey.substring(0, 10)}... found to remove`); } return await this.writeConfig(interfaceName, interfaceConfig, filteredPeers); } catch (error) { this.logger?.error(`Failed to remove peer from ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to remove peer configuration: ${error.message}`, error); } return patterns_1.Result.fail("Failed to remove peer configuration: Unknown error"); } } async getInterface(interfaceName) { try { const { interfaceConfig, peers } = await this.readConfig(interfaceName); if (!interfaceConfig) { return patterns_1.Result.success(null); } return patterns_1.Result.success({ name: interfaceName, config: interfaceConfig, peers, }); } catch (error) { this.logger?.error(`Failed to get interface ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to retrieve interface: ${error.message}`, error); } return patterns_1.Result.fail("Failed to retrieve interface: Unknown error"); } } // Helper methods for parsing WireGuard configuration sections parseInterfaceSection(content) { const interfaceMatch = content.match(/\[Interface\]([\s\S]*?)(?=\[|$)/); if (!interfaceMatch) { return null; } const section = interfaceMatch[1]; const privateKeyMatch = section.match(/PrivateKey\s*=\s*([^\s]+)/); const addressMatch = section.match(/Address\s*=\s*([^\s]+)/); if (!privateKeyMatch || !addressMatch) { return null; } let config = { privateKey: privateKeyMatch[1].trim(), address: addressMatch[1].trim(), }; // Optional fields const listenPortMatch = section.match(/ListenPort\s*=\s*(\d+)/); if (listenPortMatch) { config = { ...config, listenPort: Number.parseInt(listenPortMatch[1]) }; } const dnsMatch = section.match(/DNS\s*=\s*([^\n]+)/); if (dnsMatch) { config = { ...config, dns: dnsMatch[1].split(",").map((s) => s.trim()) }; } return config; } parsePeerSections(content) { const peers = []; const peerSections = content.match(/\[Peer\]([\s\S]*?)(?=\[|$)/g); if (!peerSections) { return []; } for (const section of peerSections) { const publicKeyMatch = section.match(/PublicKey\s*=\s*([^\s]+)/); const allowedIPsMatch = section.match(/AllowedIPs\s*=\s*([^\n]+)/); if (publicKeyMatch && allowedIPsMatch) { let peer = { publicKey: publicKeyMatch[1].trim(), allowedIPs: allowedIPsMatch[1].split(",").map((s) => s.trim()), }; // Optional fields const endpointMatch = section.match(/Endpoint\s*=\s*([^\s]+)/); if (endpointMatch) { peer = { ...peer, endpoint: endpointMatch[1].trim() }; } const keepaliveMatch = section.match(/PersistentKeepalive\s*=\s*(\d+)/); if (keepaliveMatch) { peer = { ...peer, persistentKeepalive: Number.parseInt(keepaliveMatch[1]), }; } const presharedKeyMatch = section.match(/PresharedKey\s*=\s*([^\s]+)/); if (presharedKeyMatch) { peer = { ...peer, presharedKey: presharedKeyMatch[1].trim() }; } peers.push(peer); } } return peers; } generateInterfaceSection(config) { let content = "[Interface]\n"; content += `PrivateKey = ${config.privateKey}\n`; content += `Address = ${config.address}\n`; if (config.listenPort) { content += `ListenPort = ${config.listenPort}\n`; } if (config.dns && config.dns.length > 0) { content += `DNS = ${config.dns.join(", ")}\n`; } // Add other optional fields if (config.mtu) { content += `MTU = ${config.mtu}\n`; } if (config.table) { content += `Table = ${config.table}\n`; } if (config.preUp) { content += `PreUp = ${config.preUp}\n`; } if (config.postUp) { content += `PostUp = ${config.postUp}\n`; } if (config.preDown) { content += `PreDown = ${config.preDown}\n`; } if (config.postDown) { content += `PostDown = ${config.postDown}\n`; } return content; } generatePeerSection(peer) { let content = "[Peer]\n"; content += `PublicKey = ${peer.publicKey}\n`; if (peer.endpoint) { content += `Endpoint = ${peer.endpoint}\n`; } content += `AllowedIPs = ${peer.allowedIPs.join(", ")}\n`; if (peer.persistentKeepalive) { content += `PersistentKeepalive = ${peer.persistentKeepalive}\n`; } if (peer.presharedKey) { content += `PresharedKey = ${peer.presharedKey}\n`; } return content; } /** * Remove all peers from an interface configuration */ async removeAllPeers(interfaceName) { try { // Get the current interface configuration const { interfaceConfig } = await this.readConfig(interfaceName); if (!interfaceConfig) { this.logger?.warn(`No interface configuration found for ${interfaceName}`); return patterns_1.Result.success(undefined); } // Write back only the interface configuration, with no peers return await this.writeConfig(interfaceName, interfaceConfig, []); } catch (error) { this.logger?.error(`Failed to remove all peers from ${interfaceName}`, error); if (error instanceof Error) { return patterns_1.Result.fail(`Failed to remove all peers: ${error.message}`, error); } return patterns_1.Result.fail("Failed to remove all peers: Unknown error"); } } } exports.FileConfigurationRepository = FileConfigurationRepository;