@synet/net
Version:
Network abstraction layer for Synet. visit https://syntehtism.ai for more information.
367 lines (366 loc) • 15.3 kB
JavaScript
;
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;