199 lines
8.8 KiB
TypeScript
199 lines
8.8 KiB
TypeScript
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")
|