fix(ipsec): убрать неподдерживаемый параметр при добавлении сертификата

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-12 22:06:27 +07:00
co-authored by Cursor
parent 3eb75ea0b8
commit 11ae7593f4
3 changed files with 27 additions and 1 deletions
@@ -48,6 +48,9 @@ const anakondra: RosCertificateRow = { ...client1, name: "anakondra", "common-na
// reference-based важнее key-usage: у client1 в key-usage есть tls-server, но он client
assert.equal(certificateRole(client1, ctx), "client")
assert.equal(certificateRole(anakondra, ctx), "client")
// без reference-контекста: подпись локальным CA делает серт клиентским, несмотря на tls-server в usage
assert.equal(certificateRole(client1), "client")
assert.equal(certificateRole(anakondra), "client")
// без контекста — по key-usage (default содержит оба, поэтому server)
assert.equal(certificateRole({ name: "x", "key-usage": "tls-client" }), "client")
assert.equal(certificateRole({ name: "y", "key-usage": "digital-signature" }), "other")
@@ -50,6 +50,10 @@ export function certificateRole(
if (isCaCertificate(row)) return "ca"
if (name && new Set(ctx.peerCertNames ?? []).has(name)) return "server"
if (name && new Set(ctx.identityCertNames ?? []).has(name)) return "client"
// Серт, подписанный локальным CA (`ca` заполнен), но не используемый как peer-серт — клиентский.
// `client1`/`anakondra` имеют дефолтный key-usage (tls-server+tls-client), поэтому по usage
// они выглядели бы «server».
if ((row.ca ?? "").trim() !== "") return "client"
const usage = row["key-usage"] ?? ""
if (usage.includes("tls-server")) return "server"
if (usage.includes("tls-client")) return "client"
+20 -1
View File
@@ -415,6 +415,13 @@ export function encodeRosId(rosId: string): string {
// ── MikrotikClient ─────────────────────────────────────────────────────────────
/** Имя параметра из ошибки RouterOS `unknown parameter <name>` (400) — для деградации запроса. */
function unknownParameterName(error: MikrotikError): string | undefined {
if (error.statusCode !== 400) return undefined
const match = error.body.match(/unknown parameter\s+"?([A-Za-z0-9_-]+)"?/i)
return match?.[1]
}
export class MikrotikClient {
constructor(private readonly params: MikrotikConnectParams) {}
@@ -691,7 +698,19 @@ export class MikrotikClient {
/** Создание ключевой пары + заявки: /certificate add (поля common-name, key-size, key-usage…). */
async addCertificate(body: Record<string, string>, timeoutMs = 30_000): Promise<unknown> {
return this.post("/certificate/add", body, timeoutMs)
// Набор параметров `/certificate/add` зависит от версии RouterOS (напр. `comment`).
// Деградируем: при 400 «unknown parameter X» убираем X из тела и повторяем.
const payload: Record<string, string> = { ...body }
for (let attempt = 0; attempt < 4; attempt += 1) {
try {
return await this.post("/certificate/add", payload, timeoutMs)
} catch (error) {
const param = error instanceof MikrotikError ? unknownParameterName(error) : undefined
if (!param || !(param in payload)) throw error
delete payload[param]
}
}
throw new Error(`RouterOS: не удалось добавить сертификат (несовместимые параметры): ${Object.keys(body).join(", ")}`)
}
/** Подпись сертификата локальным CA; sign небыстрый — увеличенный таймаут. */