From 11ae7593f4c4bd3b3dec75025b0654de48274851 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 12 Sep 2026 22:06:27 +0700 Subject: [PATCH] =?UTF-8?q?fix(ipsec):=20=D1=83=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BD=D0=B5=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D1=80=20=D0=BF=D1=80=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../src/services/certificate-parse.test.ts | 3 +++ backend/src/services/certificate-parse.ts | 4 ++++ backend/src/services/mikrotik.ts | 21 ++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/src/services/certificate-parse.test.ts b/backend/src/services/certificate-parse.test.ts index 3c1cf8b..3599013 100644 --- a/backend/src/services/certificate-parse.test.ts +++ b/backend/src/services/certificate-parse.test.ts @@ -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") diff --git a/backend/src/services/certificate-parse.ts b/backend/src/services/certificate-parse.ts index f8aa83e..4f0e83a 100644 --- a/backend/src/services/certificate-parse.ts +++ b/backend/src/services/certificate-parse.ts @@ -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" diff --git a/backend/src/services/mikrotik.ts b/backend/src/services/mikrotik.ts index dc8a793..f43ef0b 100644 --- a/backend/src/services/mikrotik.ts +++ b/backend/src/services/mikrotik.ts @@ -415,6 +415,13 @@ export function encodeRosId(rosId: string): string { // ── MikrotikClient ───────────────────────────────────────────────────────────── +/** Имя параметра из ошибки RouterOS `unknown parameter ` (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, timeoutMs = 30_000): Promise { - return this.post("/certificate/add", body, timeoutMs) + // Набор параметров `/certificate/add` зависит от версии RouterOS (напр. `comment`). + // Деградируем: при 400 «unknown parameter X» убираем X из тела и повторяем. + const payload: Record = { ...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 небыстрый — увеличенный таймаут. */