Files
MikrotikManager/backend/src/services/ipsec-config.test.ts
T

199 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict"
import {
IPSEC_MARKER,
buildClientInstructions,
buildSswanConfig,
clientCertName,
findFreePoolIp,
identityDisplayName,
ipsecManagedComment,
ipsecSlug,
ipsecUserComment,
isIke2Peer,
isIke2RemoteAccessIdentity,
isIpsecManagedComment,
parseIpsecUserComment,
poolRangesFromCidr,
resolveIke2CaName,
resolveIke2ServerCert,
selectIke2Peers,
selectSharedIke2Identity,
userModeConfigName,
} from "./ipsec-config.js"
import { identityRosBody } from "./ipsec-ros.js"
{
assert.equal(ipsecSlug("Alice Cooper"), "alice-cooper")
assert.equal(ipsecSlug("Иван"), "")
assert.equal(clientCertName("Alice Cooper"), "ipsec-user-alice-cooper")
assert.equal(userModeConfigName("Bob"), "mc-ipsec-bob")
}
{
assert.ok(isIpsecManagedComment(ipsecManagedComment("IKEv2 road-warrior")))
assert.ok(isIpsecManagedComment(`${IPSEC_MARKER} user=alice`))
assert.ok(!isIpsecManagedComment("какой-то чужой комментарий"))
assert.ok(!isIpsecManagedComment(undefined))
assert.equal(ipsecUserComment("alice"), `${IPSEC_MARKER} user=alice`)
assert.equal(parseIpsecUserComment(ipsecUserComment("alice")), "alice")
assert.equal(parseIpsecUserComment(ipsecUserComment("Alice Cooper")), "Alice Cooper")
assert.equal(parseIpsecUserComment("foreign user=alice"), null)
assert.equal(parseIpsecUserComment("не managed"), null)
assert.equal(parseIpsecUserComment(null), null)
}
{
assert.equal(poolRangesFromCidr("10.77.0.0/24"), "10.77.0.2-10.77.0.254")
assert.equal(poolRangesFromCidr("10.77.0.1/24"), "10.77.0.2-10.77.0.254")
assert.equal(poolRangesFromCidr("192.168.7.0/28"), "192.168.7.2-192.168.7.14")
assert.equal(poolRangesFromCidr("10.77.0.0/31"), null, "/31 — нет адресов клиентам")
assert.equal(poolRangesFromCidr("10.77.0.0"), null)
assert.equal(poolRangesFromCidr("не-сидр"), null)
}
{
const range = "10.77.0.2-10.77.0.10"
assert.equal(findFreePoolIp(range, []), "10.77.0.2")
assert.equal(findFreePoolIp(range, ["10.77.0.2"]), "10.77.0.3")
assert.equal(findFreePoolIp(range, ["10.77.0.2", "10.77.0.3/32", "10.77.0.4"]), "10.77.0.5")
assert.equal(findFreePoolIp(range, ["10.77.0.2", "10.77.0.3", "10.77.0.4", "10.77.0.5", "10.77.0.6", "10.77.0.7", "10.77.0.8", "10.77.0.9", "10.77.0.10"]), null)
// одиночный адрес без диапазона
assert.equal(findFreePoolIp("10.77.0.7", []), "10.77.0.7")
assert.equal(findFreePoolIp("мусор", []), null)
}
{
// managed: user= из comment
assert.equal(identityDisplayName({ comment: ipsecUserComment("alice"), ".id": "*1" }, []), "alice")
// существующий RouterOS identity: сырой comment
assert.equal(identityDisplayName({ comment: "home office", ".id": "*2" }, []), "home office")
// remote-id
assert.equal(identityDisplayName({ "remote-id": "client@example.com", ".id": "*3" }, []), "client@example.com")
// CN сертификата по remote-certificate
assert.equal(
identityDisplayName(
{ "remote-certificate": "ipsec-user-bob", ".id": "*4" },
[{ name: "ipsec-user-bob", "common-name": "bob" }],
),
"bob",
)
// fallback: peer#shortId
assert.equal(
identityDisplayName({ peer: "ikev2-srv", ".id": "*AB12CD34" }, []),
"ikev2-srv#AB12CD",
)
// fallback: rosId
assert.equal(identityDisplayName({ ".id": "*FF" }, []), "FF")
assert.equal(identityDisplayName({}, []), "identity")
}
{
const sswan = buildSswanConfig({
name: "IKEv2 vpn.example.com",
serverEndpoint: "vpn.example.com",
serverId: "vpn.example.com",
p12B64: "cDEy",
uuid: "fix-me",
})
const parsed = JSON.parse(sswan) as Record<string, unknown>
assert.equal(parsed.version, 1)
assert.equal(parsed.type, "ikev2-cert")
const remote = parsed.remote as Record<string, string>
const local = parsed.local as Record<string, string>
assert.equal(remote.addr, "vpn.example.com")
assert.equal(remote.id, "vpn.example.com")
assert.equal(local.p12, "cDEy")
// ike-proposal/esp-proposal не задаём: верхний регистр strongSwan не парсит,
// а его defaults совместимы с профилем MikroTik.
assert.equal("ike-proposal" in parsed, false)
assert.equal("esp-proposal" in parsed, false)
}
{
const text = buildClientInstructions({
userName: "alice",
serverEndpoint: "vpn.example.com",
p12Filename: "cert_export_ipsec-user-alice.p12",
passphrase: "s3cret",
dns: "10.77.0.1",
})
assert.ok(text.includes("alice"))
assert.ok(text.includes("vpn.example.com"))
assert.ok(text.includes("s3cret"))
assert.ok(text.includes("strongSwan"))
}
// ── IKEv2-only отбор ────────────────────────────────────────────────────────
{
const grePeer = { name: "gre-tunnel", "exchange-mode": "ike2", address: "1.2.3.4", passive: "no" }
const s2sPeer = { name: "s2s", "exchange-mode": "main", address: "5.6.7.8", certificate: "vpn-server" }
const rwPeer = { name: "vpn-server", "exchange-mode": "ike2", address: "0.0.0.0/0", passive: "yes", certificate: "vpn-server" }
assert.ok(isIke2Peer(rwPeer))
assert.ok(!isIke2Peer(s2sPeer))
const selected = selectIke2Peers([grePeer, s2sPeer, rwPeer])
assert.deepEqual(selected.map((p) => p.name), ["vpn-server"])
// fallback: нет строгого набора → ike2 + сертификат
const fallback = selectIke2Peers([grePeer, { name: "ike2-cert", "exchange-mode": "ike2", certificate: "c" }])
assert.deepEqual(fallback.map((p) => p.name), ["ike2-cert"])
const names = ["vpn-server"]
assert.ok(isIke2RemoteAccessIdentity(
{ peer: "vpn-server", "mode-config": "ipsec-vpn", "auth-method": "rsa-key", certificate: "vpn-server", "remote-certificate": "client1" },
names,
))
assert.ok(isIke2RemoteAccessIdentity({ peer: "vpn-server", "auth-method": "rsa-key", certificate: "vpn-server" }, names))
// GRE/site-to-site PSK-туннели отсеиваются
assert.ok(!isIke2RemoteAccessIdentity({ peer: "s2s", "auth-method": "pre-shared-key" }, names))
assert.ok(!isIke2RemoteAccessIdentity({ peer: "vpn-server", "auth-method": "pre-shared-key" }, names))
// без ограничения по peer (набор пуст) — mode-config/серт всё равно нужен
assert.ok(isIke2RemoteAccessIdentity({ peer: "x", "mode-config": "mc" }, []))
assert.ok(!isIke2RemoteAccessIdentity({ peer: "x", "auth-method": "pre-shared-key" }, []))
}
{
const myCa = { name: "MyCA", "key-usage": "key-cert-sign,crl-sign", ca: "", flags: "KAT", authority: "true" }
const vpnServer = { name: "vpn-server", "key-usage": "digital-signature,key-encipherment,tls-server,tls-client", ca: "MyCA" }
const client1 = { name: "client1", "key-usage": "digital-signature,tls-client,tls-server", ca: "MyCA" }
const certs = [myCa, vpnServer, client1]
assert.equal(resolveIke2ServerCert(certs, [{ name: "vpn-server", certificate: "vpn-server" }])?.name, "vpn-server")
assert.equal(resolveIke2ServerCert([myCa], [])?.name, undefined)
assert.equal(resolveIke2CaName(certs, vpnServer), "MyCA")
assert.equal(resolveIke2CaName([vpnServer, client1], vpnServer), undefined)
}
{
// Общая listener-identity: без remote-certificate, сертификатный auth.
const shared = { ".id": "*S", peer: "vpn-server", "auth-method": "rsa-key", certificate: "vpn-server", "mode-config": "ikev2-modeconf" }
const perClient = { ".id": "*C", peer: "vpn-server", "auth-method": "rsa-key", certificate: "vpn-server", "remote-certificate": "client1", "match-by": "certificate" }
const psk = { ".id": "*P", peer: "vpn-server", "auth-method": "pre-shared-key" }
const foreignPeer = { ".id": "*F", peer: "gre-tunnel", "auth-method": "rsa-key", certificate: "vpn-server" }
assert.equal(selectSharedIke2Identity([perClient, psk, foreignPeer, shared], ["vpn-server"])?.[".id"], "*S")
// только per-client identity (с remote-certificate) общей не считается
assert.equal(selectSharedIke2Identity([perClient, psk], ["vpn-server"]), undefined)
// managed-приоритет
const managed = { ".id": "*M", peer: "vpn-server", "auth-method": "rsa-key", certificate: "vpn-server", comment: ipsecManagedComment("listener") }
assert.equal(selectSharedIke2Identity([shared, managed], ["vpn-server"])?.[".id"], "*M")
}
{
const body = identityRosBody({
peerName: "vpn-server",
modeConfig: "mc-ipsec-alice",
comment: ipsecUserComment("alice"),
authMethod: "certificate",
certificate: "vpn-server",
remoteCertificate: "ipsec-user-alice",
placeBefore: "*S",
})
assert.equal(body["auth-method"], "digital-signature")
assert.equal(body["place-before"], undefined)
assert.equal(body["remote-certificate"], "ipsec-user-alice")
assert.equal(body["match-by"], "certificate")
}
console.log("ipsec-config.test.ts: ok")