fix(ipsec): убрать неподдерживаемый параметр при добавлении сертификата
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -48,6 +48,9 @@ const anakondra: RosCertificateRow = { ...client1, name: "anakondra", "common-na
|
|||||||
// reference-based важнее key-usage: у client1 в key-usage есть tls-server, но он client
|
// reference-based важнее key-usage: у client1 в key-usage есть tls-server, но он client
|
||||||
assert.equal(certificateRole(client1, ctx), "client")
|
assert.equal(certificateRole(client1, ctx), "client")
|
||||||
assert.equal(certificateRole(anakondra, 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)
|
// без контекста — по key-usage (default содержит оба, поэтому server)
|
||||||
assert.equal(certificateRole({ name: "x", "key-usage": "tls-client" }), "client")
|
assert.equal(certificateRole({ name: "x", "key-usage": "tls-client" }), "client")
|
||||||
assert.equal(certificateRole({ name: "y", "key-usage": "digital-signature" }), "other")
|
assert.equal(certificateRole({ name: "y", "key-usage": "digital-signature" }), "other")
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ export function certificateRole(
|
|||||||
if (isCaCertificate(row)) return "ca"
|
if (isCaCertificate(row)) return "ca"
|
||||||
if (name && new Set(ctx.peerCertNames ?? []).has(name)) return "server"
|
if (name && new Set(ctx.peerCertNames ?? []).has(name)) return "server"
|
||||||
if (name && new Set(ctx.identityCertNames ?? []).has(name)) return "client"
|
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"] ?? ""
|
const usage = row["key-usage"] ?? ""
|
||||||
if (usage.includes("tls-server")) return "server"
|
if (usage.includes("tls-server")) return "server"
|
||||||
if (usage.includes("tls-client")) return "client"
|
if (usage.includes("tls-client")) return "client"
|
||||||
|
|||||||
@@ -415,6 +415,13 @@ export function encodeRosId(rosId: string): string {
|
|||||||
|
|
||||||
// ── MikrotikClient ─────────────────────────────────────────────────────────────
|
// ── 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 {
|
export class MikrotikClient {
|
||||||
constructor(private readonly params: MikrotikConnectParams) {}
|
constructor(private readonly params: MikrotikConnectParams) {}
|
||||||
|
|
||||||
@@ -691,7 +698,19 @@ export class MikrotikClient {
|
|||||||
|
|
||||||
/** Создание ключевой пары + заявки: /certificate add (поля common-name, key-size, key-usage…). */
|
/** Создание ключевой пары + заявки: /certificate add (поля common-name, key-size, key-usage…). */
|
||||||
async addCertificate(body: Record<string, string>, timeoutMs = 30_000): Promise<unknown> {
|
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 небыстрый — увеличенный таймаут. */
|
/** Подпись сертификата локальным CA; sign небыстрый — увеличенный таймаут. */
|
||||||
|
|||||||
Reference in New Issue
Block a user