fix(ipsec): показывать все привязки общего сертификата

This commit is contained in:
Denozordec committed 2026-10-10 20:59:24 +07:00
1 parent 60b2e099f0
commit 58f5c48a97
17 files changed
+1213 -56

No files matched your search

+43 -41
View File
@@ -272,10 +272,10 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] {
const findActive = (remoteId: string) => activeByRemote.get(remoteId)
?? activeByRemote.get(remoteId.replace(/^(key-id|fqdn|user-fqdn|address):/, ""))
const identityByRemoteCert = new Map<string, RosIpsecIdentity>()
const identityByRemoteCert = new Map<string, RosIpsecIdentity[]>()
for (const i of state.identities) {
const rc = (i["remote-certificate"] ?? "").trim()
if (rc) identityByRemoteCert.set(rc, i)
if (rc) identityByRemoteCert.set(rc, [...(identityByRemoteCert.get(rc) ?? []), i])
}
const personalStaticIp = (mcNameRaw: string): string | undefined => {
@@ -303,44 +303,46 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] {
const referenced = identityByRemoteCert.has(certName)
if (!isUser && !referenced && !(caName && signedBy === caName)) continue
const identity = identityByRemoteCert.get(certName)
const comment = identity?.comment ?? ""
const mcName = (identity?.["mode-config"] ?? "").trim()
const cn = String(cert["common-name"] ?? "").trim()
const subject = routerOsCertificateDn(cert)
const bySubject = subject && subjectCounts.get(subject) === 1 ? activeByRemote.get(subject) : undefined
const uniqueCn = cn && cnCounts.get(cn) === 1
// A literal CN is not a typed remote-id. It may also resemble another
// certificate's complete DN, in which case the bare-CN alias is unsafe.
const byCommonName = uniqueCn && !subjectCounts.has(cn) ? activeByRemote.get(cn) : undefined
const active = bySubject ?? byCommonName
?? (identity?.["remote-id"] ? findActive(String(identity["remote-id"]).trim()) : undefined)
// Multiple established sessions confirm presence, but no single lease/uptime.
const single = active?.length === 1 ? active[0] : undefined
out.push({
id: `${server.id}:${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`,
rosId: `${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`,
serverId: String(server.id),
serverName,
name: identity ? identityDisplayName(identity, state.certs) : (cn || certName),
authMethod: "certificate",
kind: "cert",
certificateName: certName,
certName,
signedBy: signedBy || undefined,
commonName: cn || undefined,
remoteId: (identity?.["remote-id"] ?? "").trim() || undefined,
staticIp: personalStaticIp(mcName),
modeConfigName: mcName || undefined,
peerName: (identity?.peer ?? "").trim() || undefined,
online: Boolean(active),
activeAddress: single?.["dynamic-address"] || single?.["remote-address"] || single?.address || undefined,
activeSince: single?.uptime || single?.established || undefined,
disabled: asBool(identity?.disabled),
comment: comment || undefined,
managed: isUser || isIpsecManagedComment(comment),
})
claimSession(active)
const identities = identityByRemoteCert.get(certName) ?? [undefined]
for (const identity of identities) {
const comment = identity?.comment ?? ""
const mcName = (identity?.["mode-config"] ?? "").trim()
const cn = String(cert["common-name"] ?? "").trim()
const subject = routerOsCertificateDn(cert)
const bySubject = subject && subjectCounts.get(subject) === 1 ? activeByRemote.get(subject) : undefined
const uniqueCn = cn && cnCounts.get(cn) === 1
// A literal CN is not a typed remote-id. It may also resemble another
// certificate's complete DN, in which case the bare-CN alias is unsafe.
const byCommonName = uniqueCn && !subjectCounts.has(cn) ? activeByRemote.get(cn) : undefined
const active = bySubject ?? byCommonName
?? (identity?.["remote-id"] ? findActive(String(identity["remote-id"]).trim()) : undefined)
// Multiple established sessions confirm presence, but no single lease/uptime.
const single = active?.length === 1 ? active[0] : undefined
out.push({
id: `${server.id}:${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`,
rosId: `${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`,
serverId: String(server.id),
serverName,
name: identity ? identityDisplayName(identity, state.certs) : (cn || certName),
authMethod: "certificate",
kind: "cert",
certificateName: certName,
certName,
signedBy: signedBy || undefined,
commonName: cn || undefined,
remoteId: (identity?.["remote-id"] ?? "").trim() || undefined,
staticIp: personalStaticIp(mcName),
modeConfigName: mcName || undefined,
peerName: (identity?.peer ?? "").trim() || undefined,
online: Boolean(active),
activeAddress: single?.["dynamic-address"] || single?.["remote-address"] || single?.address || undefined,
activeSince: single?.uptime || single?.established || undefined,
disabled: asBool(identity?.disabled),
comment: comment || undefined,
managed: isUser || isIpsecManagedComment(comment),
})
claimSession(active)
}
}
// 2. PSK-identity (managed или IKEv2 remote-access).
@@ -397,7 +399,7 @@ export function resolveIpsecClientTarget(state: IpsecServerState, token: string)
const clients = mapClients(state)
const identity = kind === "cert" ? undefined : state.identities.find(i => i[".id"] === raw && clients.some(c => c.rosId === "identity:" + raw))
const certRow = kind === "identity" ? undefined : state.certs.find(c => (c[".id"] === raw || c.name === raw) && clients.some(user => user.kind === "cert" && user.certName === c.name))
return { identity, certRow, ambiguous: Boolean(!kind && identity && certRow) }
return { identity, certRow, ambiguous: Boolean((!kind && identity && certRow) || (certRow && state.identities.filter(i => (i["remote-certificate"] ?? "").trim() === certRow.name).length > 1)) }
}
export function mapServerSummary(state: IpsecServerState, clients: IpsecClientDto[]): IpsecServerSummaryDto {
+16
View File
@@ -197,4 +197,20 @@ aliasesState.active[1]!.state = "dying"
assert.equal(mapClients(aliasesState)[0]?.activeAddress, aliasesState.active[0]!["dynamic-address"]); checks++
aliasesState.active[0]!.state = "starting"
assert.equal(mapClients(aliasesState)[0]?.online, false); checks++
const sharedCertState = { ...namespaceState, certs: [namespaceState.certs[0]!], identities: [0,1].map(n => ({ ".id": "*SHARED" + n, peer: "ike", "auth-method": "digital-signature", "remote-certificate": "ipsec-user-plain", "mode-config": "mc" + n, comment: ipsecManagedComment("user=Client" + n) })), modeConfigs: [0,1].map(n => ({ ".id": "*MC" + n, name: "mc" + n, address: "198.18.114." + (250+n) })), active: [{ ".id": "*SHAREDSESSION", id: "CN=alice", state: "established", "dynamic-address": "198.18.114.250" }] } as typeof state
for (const reverse of [false,true]) {
if (reverse) sharedCertState.identities.reverse()
const rows = mapClients(sharedCertState)
assert.equal(rows.length,2)
assert(rows.every(c => c.sessionAmbiguous && !c.online && !c.activeAddress))
assert.equal(rows.find(c => c.rosId === "identity:*SHARED0")?.staticIp,"198.18.114.250")
assert.equal(rows.find(c => c.rosId === "identity:*SHARED1")?.staticIp,"198.18.114.251"); checks++
}
assert(resolveIpsecClientTarget(sharedCertState,"cert:*PLAIN").ambiguous); checks++
for(const id of ["*SHARED0","*SHARED1"]) {assert.equal(resolveIpsecClientTarget(sharedCertState,"identity:"+id).identity?.[".id"],id);checks++}
sharedCertState.identities.forEach((i,n) => { i["remote-id"] = "fqdn:client"+n+".example" })
sharedCertState.active[0]!.id="client0.example"
const distinctRows=mapClients(sharedCertState)
assert.equal(distinctRows.filter(c => c.online).length,1)
assert(distinctRows.every(c => !c.sessionAmbiguous)); checks++
console.log(`ipsec-state: ${checks} checks passed`)