Files
MikrotikManager/backend/src/services/wireguard-import.ts
T

62 lines
5.0 KiB
TypeScript

import { isIP } from "node:net"
import { parseWgConfig, WG_CONFIG_LIMITS, type WgParsedConfig } from "./wireguard-config.js"
import { canonicalBgpPolicyPrefix } from "./bgp-community-policy-model.js"
function validKey(value: string): boolean {
return /^[A-Za-z0-9+/]{43}=$/.test(value) && Buffer.from(value, "base64").length === 32 && Buffer.from(value, "base64").toString("base64") === value && Buffer.from(value, "base64").some(byte => byte !== 0)
}
function validNetwork(value: string): boolean {
const parts = value.split("/")
const [address, prefix] = parts
const version = isIP(address ?? "")
return parts.length === 2 && version !== 0 && !value.includes("%") && /^\d+$/.test(prefix ?? "") && Number(prefix) <= (version === 4 ? 32 : 128)
}
function safeText(value: string | undefined, maximum: number, multilineComment = false) { return value === undefined || value.length <= maximum && !(multilineComment ? /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/ : /[\u0000-\u001f\u007f]/).test(value) }
function validHost(value: string) { return value === "" || value.length <= 253 && !value.includes("%") && (isIP(value) !== 0 || /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.?$/i.test(value)) }
/** Validate the complete import before the first RouterOS mutation. Never echo keys. */
export function parseWireguardImport(content: string, format: "auto" | "rsc" | "conf"): WgParsedConfig {
const parsed = parseWgConfig(content, format)
if (parsed.format === "conf") {
if (!parsed.interface.privateKey) {
throw new Error("Заполните PrivateKey: клиентский шаблон нельзя применить без приватного ключа")
}
}
if (!parsed.interface.name.trim() || !safeText(parsed.interface.name, 64)) throw new Error("Укажите корректное имя интерфейса")
if (!safeText(parsed.interface.comment, 1000, true)) throw new Error("Некорректный комментарий интерфейса WireGuard")
if (parsed.interface.privateKey && !validKey(parsed.interface.privateKey)) {
throw new Error("Некорректный PrivateKey: нужен ключ WireGuard длиной 32 байта в base64")
}
if (parsed.interface.address && !validNetwork(parsed.interface.address)) {
throw new Error("Некорректный Address: укажите IPv4 или IPv6 адрес с префиксом")
}
if (parsed.interface.listenPort !== undefined && (!Number.isInteger(parsed.interface.listenPort) || parsed.interface.listenPort < 1 || parsed.interface.listenPort > 65535)) {
throw new Error("ListenPort должен быть от 1 до 65535")
}
if (parsed.interface.mtu !== undefined && (!Number.isInteger(parsed.interface.mtu) || parsed.interface.mtu <= 0 || parsed.interface.mtu > 65535)) {
throw new Error("MTU должен быть целым числом от 1 до 65535")
}
if (parsed.peers.length > WG_CONFIG_LIMITS.peers || new Set(parsed.peers.map(peer => peer.publicKey)).size !== parsed.peers.length) throw new Error("Превышен предел peers или повторяется PublicKey")
for (const peer of parsed.peers) {
if (!validKey(peer.publicKey)) throw new Error("Некорректный PublicKey пира")
if (!peer.allowedAddresses.length || peer.allowedAddresses.length > WG_CONFIG_LIMITS.addressesPerPeer || peer.allowedAddresses.some(value => !validNetwork(value))) {
throw new Error("Заполните AllowedIPs / allowed-address каждого пира корректными сетями с префиксом")
}
if (new Set(peer.allowedAddresses.map(canonicalBgpPolicyPrefix)).size !== peer.allowedAddresses.length) throw new Error("Повторяющиеся AllowedIPs / allowed-address")
if (peer.privateKey !== undefined && !["auto", "none"].includes(peer.privateKey) && !validKey(peer.privateKey)) throw new Error("Некорректный приватный ключ пира WireGuard")
if (peer.endpointAddress !== undefined && !validHost(peer.endpointAddress)) throw new Error("Некорректный адрес Endpoint")
if (!safeText(peer.name, 128) || !safeText(peer.comment, 1000, true) || [peer.clientAddress, peer.clientDns, peer.clientEndpoint].some(value => !safeText(value, 1000))) throw new Error("Некорректные текстовые поля пира WireGuard")
if (peer.endpointPort !== undefined && (!Number.isInteger(peer.endpointPort) || peer.endpointPort < 1 || peer.endpointPort > 65535)) {
throw new Error("Порт Endpoint должен быть от 1 до 65535")
}
if (parsed.format === "conf" && peer.endpointAddress && peer.endpointPort === undefined) {
throw new Error("Укажите порт Endpoint после адреса")
}
if (peer.persistentKeepalive !== undefined && (!Number.isInteger(peer.persistentKeepalive) || peer.persistentKeepalive < 0 || peer.persistentKeepalive > 65535)) {
throw new Error("PersistentKeepalive должен быть целым числом от 0 до 65535")
}
}
return parsed
}