fix(ipsec): проверять CRL-цепочку до удаления identity

This commit is contained in:
Denozordec committed 2026-10-11 02:56:09 +07:00
1 parent a31a65fbe6
commit 7867914cbc
9 files changed
+349 -14

No files matched your search

+13 -1
View File
@@ -6,9 +6,11 @@ import { MikrotikClient, MikrotikError } from "../services/mikrotik.js"
import { fetchIpsecState } from "../services/ipsec-live.js"
const server = { id: 1, name: "test", host: "192.0.2.1", enabled: true } as Parameters<typeof fetchIpsecState>[0]
const original = MikrotikClient.fromServer
let pkiMode = ""
let writes = 0, records = 0, fault = "", certificateFixture = false, removedCertificates = 0, exports = 0, exportedName = ""
const identities: Record<string,string>[] = [{ ".id": "*I", peer: "ike", "auth-method": "pre-shared-key", "remote-id": "key-id:alice", "mode-config": "mc-ipsec-alice", comment: "MikrotikManager:ipsec user=Alice" }, { ".id": "*OTHER", peer: "ike", "auth-method": "pre-shared-key", "remote-id": "key-id:other", "mode-config": "mc-ipsec-other", comment: "MikrotikManager:ipsec user=Other" }]
MikrotikClient.fromServer = () => ({ get: async (p: string) => certificateFixture && p === "/certificate" ? [{ ".id": "*CERT", name: "ipsec-user-shared", "common-name": "shared", "key-usage": "tls-client" }] : p === "/ip/ipsec/peer" ? [{ ".id": "*P", name: "ike", "exchange-mode": "ike2" }] : p === "/ip/ipsec/identity" ? identities : p === "/ip/ipsec/mode-config" ? [{ ".id": "*MC", name: "mc-ipsec-alice", address: "198.18.81.250" }, { ".id": "*OTHERMC", name: "mc-ipsec-other", address: "198.18.81.249" }] : [], getCertificates: async () => certificateFixture ? [{name:"ipsec-user-shared"}] : [], exportCertificatePkcs12: async (args: {name:string}) => {exports++;exportedName=args.name;return {fileName:args.name+".p12",content:Buffer.from("fixture-only-archive"),passphrase:"fixture-only-pass"}}, removeCertificate: async () => { writes++; removedCertificates++ }, put: async () => { writes++ }, post: async () => { writes++ }, delete: async () => { writes++ }, patch: async (path: string, body: Record<string,string>) => { writes++; if (fault === "partial" && writes === 2) throw new MikrotikError(403, path, "rejected"); Object.assign(identities.find(i => path.endsWith(i[".id"]))!, body); if (fault === "lost-ack") throw Error("socket closed after application") } }) as unknown as MikrotikClient
function certificateRows(): Record<string,string>[] {const leaf:Record<string,string>={".id":"*CERT",name:"ipsec-user-shared","common-name":"shared","key-usage":"tls-client"};if(!pkiMode)return [leaf];leaf.ca="test-crl-ca";if(pkiMode==="issued")leaf.issued="true";if(pkiMode==="revoked")leaf.revoked="true";if(pkiMode==="imported")leaf.issued="false";return [leaf,{".id":"*CA",name:"test-crl-ca","common-name":"test-crl-ca","key-usage":"key-cert-sign,crl-sign",crl:"true"}];}
MikrotikClient.fromServer = () => ({ get: async (p: string) => certificateFixture && p === "/certificate" ? certificateRows() : p === "/ip/ipsec/peer" ? [{ ".id": "*P", name: "ike", "exchange-mode": "ike2" }] : p === "/ip/ipsec/identity" ? identities : p === "/ip/ipsec/mode-config" ? [{ ".id": "*MC", name: "mc-ipsec-alice", address: "198.18.81.250" }, { ".id": "*OTHERMC", name: "mc-ipsec-other", address: "198.18.81.249" }] : [], getCertificates: async () => certificateFixture ? [{name:"ipsec-user-shared"}] : [], exportCertificatePkcs12: async (args: {name:string}) => {exports++;exportedName=args.name;return {fileName:args.name+".p12",content:Buffer.from("fixture-only-archive"),passphrase:"fixture-only-pass"}}, removeCertificate: async () => { writes++; removedCertificates++ }, put: async () => { writes++ }, post: async () => { writes++ }, delete: async () => { writes++ }, patch: async (path: string, body: Record<string,string>) => { writes++; if (fault === "partial" && writes === 2) throw new MikrotikError(403, path, "rejected"); Object.assign(identities.find(i => path.endsWith(i[".id"]))!, body); if (fault === "lost-ack") throw Error("socket closed after application") } }) as unknown as MikrotikClient
const app = Fastify(); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler)
await app.register(createIpsecRoutes({ findServer: async () => server, recordRevision: async () => { records++; return {created:false,revision:{} as never} } }), { prefix: "/api" })
const patch = (payload: Record<string, unknown>) => app.inject({ method: "PATCH", url: "/api/ipsec/users/1/identity:*I", payload })
@@ -39,6 +41,16 @@ for(const method of ["PATCH","DELETE"] as const){writes=records=0;const r=await
writes=records=0;r=await app.inject({method:"DELETE",url:"/api/ipsec/users/1/identity:*FIRST"});assert.equal(r.statusCode,409);assert.equal(writes,0);assert.equal(records,0);
writes=records=0;r=await app.inject({method:"PATCH",url:"/api/ipsec/users/1/identity:*FIRST",payload:{name:"Selected"}});assert.equal(r.statusCode,200);assert.equal(writes,1);assert.equal(identities[0]!.comment,"MikrotikManager:ipsec user=Selected");assert.equal(identities[1]!.comment,"MikrotikManager:ipsec user=Shared1");
writes=records=0;r=await app.inject({method:"DELETE",url:"/api/ipsec/users/1/identity:*FIRST?removeCertificate=false"});assert.equal(r.statusCode,200);assert.equal(writes,1);assert.equal(records,1);
// A CRL issuer protects both issued and already-revoked native certificates.
for(const mode of ["issued","revoked"]) {
pkiMode=mode;identities.splice(0,identities.length,{".id":"*CRL-BOUND",peer:"ike","auth-method":"digital-signature","remote-certificate":"ipsec-user-shared",comment:"MikrotikManager:ipsec user=CRL client"});
for(const url of ["/api/ipsec/users/1/identity:*CRL-BOUND","/api/ipsec/users/1/cert:*CERT"]) {writes=records=removedCertificates=0;const r=await app.inject({method:"DELETE",url});assert.equal(r.statusCode,409);assert.match(r.json().error,/CA с CRL/);assert.equal(writes,0);assert.equal(records,0);}
for(const force of ["","?force=true"]) {writes=records=0;const r=await app.inject({method:"DELETE",url:"/api/ipsec/certs/1"+force,payload:{name:"ipsec-user-shared"}});assert.equal(r.statusCode,409);assert.equal(writes,0);assert.equal(records,0);}
writes=records=removedCertificates=0;const r=await app.inject({method:"DELETE",url:"/api/ipsec/users/1/identity:*CRL-BOUND?removeCertificate=false"});assert.equal(r.statusCode,200);assert.equal(writes,1);assert.equal(removedCertificates,0);assert.equal(records,1);
}
for(const force of ["","?force=true"]) {writes=records=0;const r=await app.inject({method:"DELETE",url:"/api/ipsec/certs/1"+force,payload:{name:"test-crl-ca"}});assert.equal(r.statusCode,409);assert.match(r.json().error,/всю цепочку/);assert.equal(writes,0);assert.equal(records,0);}
pkiMode="imported";identities.splice(0,identities.length);writes=records=0;r=await app.inject({method:"DELETE",url:"/api/ipsec/certs/1",payload:{name:"ipsec-user-shared"}});assert.equal(r.statusCode,200);assert.equal(writes,1);assert.equal(records,1);pkiMode="";
} finally { await app.close(); MikrotikClient.fromServer=original }
console.log("IPsec HTTP mutations: no-write conflicts, acknowledged partial failure, applied-but-lost ack and remote ID normalization")
+20
View File
@@ -101,6 +101,21 @@ function rosIdParam(v: string): string {
return decodeURIComponent(v)
}
function certificateDeleteConflict(state: Awaited<ReturnType<typeof fetchIpsecState>>, name: string): string | undefined {
const cert = state.certs.find(c => (c.name ?? "").trim() === name)
if (!cert) return undefined
// Removing a CA cascades to its issued children, including foreign clients.
if (state.certs.some(c => (c.ca ?? "").trim() === name)) {
return "CA содержит выданные сертификаты. Его удаление затронет всю цепочку; отдельное удаление CA здесь недоступно"
}
const issuer = state.certs.find(c => (c.name ?? "").trim() === (cert.ca ?? "").trim())
const revoked = cert.revoked && !["false", "no", "0"].includes(cert.revoked)
if (issuer?.crl === "true" && (cert.issued === "true" || revoked)) {
return "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно"
}
return undefined
}
function errReply(reply: FastifyReply, e: unknown, progress?: IpsecMutationProgress) {
if (e instanceof IpsecNatInterfaceConflict) return reply.status(409).send({ error: `${e.message}. Изменений не было` })
if (e instanceof IpsecPolicyGroupConflict) return reply.status(409).send({ error: `${e.message}. Изменений не было` })
@@ -549,6 +564,9 @@ export function createIpsecRoutes(dependencies = ipsecDefaults): FastifyPluginAs
|| state.peers.some(p => (p.certificate ?? "").trim() === deletionCertName)
)) return reply.status(409).send({ error: "Сертификат используется другой identity или peer. Удалите только выбранную identity, сохранив сертификат" })
const certificateConflict = removeCertificate && deletionCertName ? certificateDeleteConflict(state, deletionCertName) : undefined
if (certificateConflict) return reply.status(409).send({ error: certificateConflict })
if (identity) {
await deleteIdentity(client, identity[".id"]!)
if (personal && personal !== sharedMcName) await deleteUserModeConfig(client, personal)
@@ -649,6 +667,8 @@ export function createIpsecRoutes(dependencies = ipsecDefaults): FastifyPluginAs
const name = parsed.data.name
const cert = state.certs.find((c) => String(c.name ?? "").trim() === name)
if (!cert?.[".id"]) return reply.status(404).send({ error: `Сертификат ${name} не найден` })
const certificateConflict = certificateDeleteConflict(state, name)
if (certificateConflict) return reply.status(409).send({ error: certificateConflict })
const usedBy = [
...state.peers.filter((p) => (p.certificate ?? "").trim() === name).map((p) => `peer ${(p.name ?? "").trim()}`),
...state.identities
+16 -2
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
@@ -696,7 +696,7 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
@@ -1333,3 +1333,17 @@ Reference: [Certificates/CRL](https://manual.mikrotik.com/docs/authentication-au
| Референс | Схема |
| --- | --- |
| [ReUI List9](https://reui.io/preview/base/list-9?ref=mcp) | CA/CRL policy → валидный клиент → отзыв → reconnect отказ → возврат исходных settings |
## 12.5.127 — удаление CRL-issued сертификатов без частичных изменений (2026-10-11)
Исправлена выявленная126 последовательность: перед удалением identity API проверяет явно привязанный cert/issuer. Локально issued либо revoked certificate CA с crl=true получает409 до любых writes. Пользователь может удалить только identity с removeCertificate=false. В /ipsec/certs дополнительно запрещено отдельное удаление CA с дочерними certs: RouterOS каскадно удаляет цепочку, поэтому force=true это ограничение не обходит. Автоматический отзыв/удаление чужой цепочки не добавлялся. Imported non-issued/non-revoked leaf сохраняет прежний сценарий удаления.
Добавлены13 профильных HTTP cases с нулевыми writes/history при отказе, сохранением cert при identity-only, обеими issued/revoked формами native metadata, CA force guard и положительным imported случаем. Прошли8 IPsec scripts (state127), backend production build/scoped ESLint. Backend ux-crl127/version1.0.0-test.crl127 собран из121 с одним route override; локальные и серверные source/compiled SHA256 совпали. Frontend ux-ike124 сохранён, backup pre-crl127.env/прежний image оставлены. DTO/vendor не менялись.
Native собственная CRL chain: семь409 (user/cert targets, direct leaf/CA с/безforce и revoked leaf) сохранили certs; до identity-only identity также оставалась. Identity-only200 удалил выбранную identity, сохранил все3 certs. Первый control остановился на Ping3/3 без достаточной диагностики; cleanup прошёл, отказ сохранён. Повторение проверяло выданный native VIP и записывало bounded Ping attempts, затем завершило API-сценарии. Мгновенная связность/SLA не заявляется.
Settings восстановлены точно, own CA cascade после проверки всех детей/ссылок очистил только собственную цепочку; baseline17×2/Docker/private credentials restored. Оба CHR доступны, пароли/default/public/metadata/195.195.195.195/32 сохранены. UI и общая регрессия117 повторно не запускались: guards/backend-only, сохранение modal при API error уже принято предыдущими UI блоками. [Протокол](ux-ui/acceptance/ipsec-crl-delete-preflight-2026-10-11.json). Следующий128 — expired/not-yet-valid certs без изменения часов.
| Референс | Схема |
| --- | --- |
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Проверка cert/CA →409 без изменений → выбор сохранить cert → подтверждённое identity-only удаление |
+3 -3
View File
@@ -1,12 +1,12 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-10
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
@@ -371,6 +371,6 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
+16 -2
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -36,7 +36,21 @@
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–126 и ограничения ниже. Runtime backend ux-san121/frontend ux-ike124. |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–127 и ограничения ниже. Runtime backend ux-crl127/frontend ux-ike124. |
## 12.5.127 — удаление CRL-issued сертификатов без частичных изменений (2026-10-11)
Исправлена выявленная126 последовательность: перед удалением identity API проверяет явно привязанный cert/issuer. Локально issued либо revoked certificate CA с crl=true получает409 до любых writes. Пользователь может удалить только identity с removeCertificate=false. В /ipsec/certs дополнительно запрещено отдельное удаление CA с дочерними certs: RouterOS каскадно удаляет цепочку, поэтому force=true это ограничение не обходит. Автоматический отзыв/удаление чужой цепочки не добавлялся. Imported non-issued/non-revoked leaf сохраняет прежний сценарий удаления.
Добавлены13 профильных HTTP cases с нулевыми writes/history при отказе, сохранением cert при identity-only, обеими issued/revoked формами native metadata, CA force guard и положительным imported случаем. Прошли8 IPsec scripts (state127), backend production build/scoped ESLint. Backend ux-crl127/version1.0.0-test.crl127 собран из121 с одним route override; локальные и серверные source/compiled SHA256 совпали. Frontend ux-ike124 сохранён, backup pre-crl127.env/прежний image оставлены. DTO/vendor не менялись.
Native собственная CRL chain: семь409 (user/cert targets, direct leaf/CA с/безforce и revoked leaf) сохранили certs; до identity-only identity также оставалась. Identity-only200 удалил выбранную identity, сохранил все3 certs. Первый control остановился на Ping3/3 без достаточной диагностики; cleanup прошёл, отказ сохранён. Повторение проверяло выданный native VIP и записывало bounded Ping attempts, затем завершило API-сценарии. Мгновенная связность/SLA не заявляется.
Settings восстановлены точно, own CA cascade после проверки всех детей/ссылок очистил только собственную цепочку; baseline17×2/Docker/private credentials restored. Оба CHR доступны, пароли/default/public/metadata/195.195.195.195/32 сохранены. UI и общая регрессия117 повторно не запускались: guards/backend-only, сохранение modal при API error уже принято предыдущими UI блоками. [Протокол](ux-ui/acceptance/ipsec-crl-delete-preflight-2026-10-11.json). Следующий128 — expired/not-yet-valid certs без изменения часов.
| Референс | Схема |
| --- | --- |
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Проверка cert/CA →409 без изменений → выбор сохранить cert → подтверждённое identity-only удаление |
## 12.5.126 — CRL-enabled отзыв и проверенная очистка цепочки (2026-10-11)
+17 -3
View File
@@ -1,8 +1,8 @@
# Оставшаяся внешняя приёмка — 2026-10-11
Это актуальная матрица после 12.5.126. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.127. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-san121`, frontend `ux-ike124`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-crl127`, frontend `ux-ike124`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -12,7 +12,7 @@
| GRE | Native lifecycle, metadata, live формы/меню/URL/Ping53–57; GRE используется в OSPF/BGP/IPsec последующих блоков; native IPv6 ULA inner109 | IPv6 outer и application IPv6 interface address lifecycle, независимые WAN, масштаб и длительное восстановление; принятие GRE не означает приёмку всех сценариев автоматического расписания |
| WireGuard | Native handshake/data plane58, deletion60, live create/peer/disable/enable/delete62, mobile64, export/import67–74; native IPv6 ULA inner/allowedIps/handshake109 | Клиентские ОС и произвольные конфигурации, IPv6 outer/scale и application IPv6 interface address lifecycle, физический lost ACK и устойчивое восстановление; `.conf` с placeholders — шаблон, не принятый клиентский tunnel |
| VXLAN | Собственная пара/private Ping/catalog/URL/export75–76; bridge/FDB/port loss/recovery107; IPv6 ULA inner по IPv4 outer108 | API приложения read-only: create/delete проверены native. IPv6 outer, масштаб/производительность, профильные mutation RBAC/concurrency отдельно не приняты |
| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113; shared certificate rows/target guards114; preserve-certificate UI/native DELETE115; explicit deletion binding/PSK collision116; legacy export binding118; real duplicate DN sessions/Linux Ping/UI119; DN DER/ASCII tag и FQDN SAN native120; implicit unique DNS SAN/mixed IDs/API/UI121; короткий CHILD/IKE rekey/reconnect122; server pin и unbound same-CA certificate refusal123; own CRL-enabled valid/revoked control126; local revoked flag/CRL-disabled reconnect observation125; certificate reauth/CHILD absence boundary/UI124 | Реальные duplicate certificate-derived DN sessions приняты119; CRL-issued cert/CA deletion preflight127; expiry/external CRL failures; reauth/CHILD loss принят124; длительный rekey/переходная потеря пакетов; прочие ID типы/ADDR4/manual DN/custom PKI; native edit identical matching rules rejected; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput |
| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113; shared certificate rows/target guards114; preserve-certificate UI/native DELETE115; explicit deletion binding/PSK collision116; legacy export binding118; real duplicate DN sessions/Linux Ping/UI119; DN DER/ASCII tag и FQDN SAN native120; implicit unique DNS SAN/mixed IDs/API/UI121; короткий CHILD/IKE rekey/reconnect122; server pin и unbound same-CA certificate refusal123; CRL-issued cert/CA no-write delete guards127; own CRL-enabled valid/revoked control126; local revoked flag/CRL-disabled reconnect observation125; certificate reauth/CHILD absence boundary/UI124 | Реальные duplicate certificate-derived DN sessions приняты119; Time-invalid certificates128; external CRL failures; reauth/CHILD loss принят124; длительный rekey/переходная потеря пакетов; прочие ID типы/ADDR4/manual DN/custom PKI; native edit identical matching rules rejected; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput |
| OSPF/BFD | Real Full/own routes84–85, раздельные наблюдения BFD86, effective disabled/optimizer cost/partial/RBAC87, alternate path90 и CIDR91 | Sustained BFD-only gate не принят: в86 сосед мог вновь статьFull при доступныхHello. В90 переходный Ping18/40 с55% loss: бесшовность не принята. REST timing — наблюдаемая граница, не SLA; IPv6/scale/independent WAN открыты |
| BGP | Established83, exact /32 exchange/API/UI/administrative withdrawal92, BFD primary GRE loss/alternate/recovery93 | Hold timer/TCP loss безBFD принят104; packet loss в переходе, разные внешние peers/WAN policy, IPv6/scale. Два логических пути используют тот же public WAN; physical redundancy не принята |
| Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята |
@@ -248,3 +248,17 @@ Reference: [Certificates/CRL](https://manual.mikrotik.com/docs/authentication-au
| Референс | Схема |
| --- | --- |
| [ReUI List9](https://reui.io/preview/base/list-9?ref=mcp) | CA/CRL policy → валидный клиент → отзыв → reconnect отказ → возврат исходных settings |
## 12.5.127 — удаление CRL-issued сертификатов без частичных изменений (2026-10-11)
Исправлена выявленная126 последовательность: перед удалением identity API проверяет явно привязанный cert/issuer. Локально issued либо revoked certificate CA с crl=true получает409 до любых writes. Пользователь может удалить только identity с removeCertificate=false. В /ipsec/certs дополнительно запрещено отдельное удаление CA с дочерними certs: RouterOS каскадно удаляет цепочку, поэтому force=true это ограничение не обходит. Автоматический отзыв/удаление чужой цепочки не добавлялся. Imported non-issued/non-revoked leaf сохраняет прежний сценарий удаления.
Добавлены13 профильных HTTP cases с нулевыми writes/history при отказе, сохранением cert при identity-only, обеими issued/revoked формами native metadata, CA force guard и положительным imported случаем. Прошли8 IPsec scripts (state127), backend production build/scoped ESLint. Backend ux-crl127/version1.0.0-test.crl127 собран из121 с одним route override; локальные и серверные source/compiled SHA256 совпали. Frontend ux-ike124 сохранён, backup pre-crl127.env/прежний image оставлены. DTO/vendor не менялись.
Native собственная CRL chain: семь409 (user/cert targets, direct leaf/CA с/безforce и revoked leaf) сохранили certs; до identity-only identity также оставалась. Identity-only200 удалил выбранную identity, сохранил все3 certs. Первый control остановился на Ping3/3 без достаточной диагностики; cleanup прошёл, отказ сохранён. Повторение проверяло выданный native VIP и записывало bounded Ping attempts, затем завершило API-сценарии. Мгновенная связность/SLA не заявляется.
Settings восстановлены точно, own CA cascade после проверки всех детей/ссылок очистил только собственную цепочку; baseline17×2/Docker/private credentials restored. Оба CHR доступны, пароли/default/public/metadata/195.195.195.195/32 сохранены. UI и общая регрессия117 повторно не запускались: guards/backend-only, сохранение modal при API error уже принято предыдущими UI блоками. [Протокол](ux-ui/acceptance/ipsec-crl-delete-preflight-2026-10-11.json). Следующий128 — expired/not-yet-valid certs без изменения часов.
| Референс | Схема |
| --- | --- |
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Проверка cert/CA →409 без изменений → выбор сохранить cert → подтверждённое identity-only удаление |
+15 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-11: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Актуальное продолжение 2026-10-11: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -2638,3 +2638,17 @@ Reference: [Certificates/CRL](https://manual.mikrotik.com/docs/authentication-au
| Референс | Схема |
| --- | --- |
| [ReUI List9](https://reui.io/preview/base/list-9?ref=mcp) | CA/CRL policy → валидный клиент → отзыв → reconnect отказ → возврат исходных settings |
## 12.5.127 — удаление CRL-issued сертификатов без частичных изменений (2026-10-11)
Исправлена выявленная126 последовательность: перед удалением identity API проверяет явно привязанный cert/issuer. Локально issued либо revoked certificate CA с crl=true получает409 до любых writes. Пользователь может удалить только identity с removeCertificate=false. В /ipsec/certs дополнительно запрещено отдельное удаление CA с дочерними certs: RouterOS каскадно удаляет цепочку, поэтому force=true это ограничение не обходит. Автоматический отзыв/удаление чужой цепочки не добавлялся. Imported non-issued/non-revoked leaf сохраняет прежний сценарий удаления.
Добавлены13 профильных HTTP cases с нулевыми writes/history при отказе, сохранением cert при identity-only, обеими issued/revoked формами native metadata, CA force guard и положительным imported случаем. Прошли8 IPsec scripts (state127), backend production build/scoped ESLint. Backend ux-crl127/version1.0.0-test.crl127 собран из121 с одним route override; локальные и серверные source/compiled SHA256 совпали. Frontend ux-ike124 сохранён, backup pre-crl127.env/прежний image оставлены. DTO/vendor не менялись.
Native собственная CRL chain: семь409 (user/cert targets, direct leaf/CA с/безforce и revoked leaf) сохранили certs; до identity-only identity также оставалась. Identity-only200 удалил выбранную identity, сохранил все3 certs. Первый control остановился на Ping3/3 без достаточной диагностики; cleanup прошёл, отказ сохранён. Повторение проверяло выданный native VIP и записывало bounded Ping attempts, затем завершило API-сценарии. Мгновенная связность/SLA не заявляется.
Settings восстановлены точно, own CA cascade после проверки всех детей/ссылок очистил только собственную цепочку; baseline17×2/Docker/private credentials restored. Оба CHR доступны, пароли/default/public/metadata/195.195.195.195/32 сохранены. UI и общая регрессия117 повторно не запускались: guards/backend-only, сохранение modal при API error уже принято предыдущими UI блоками. [Протокол](ux-ui/acceptance/ipsec-crl-delete-preflight-2026-10-11.json). Следующий128 — expired/not-yet-valid certs без изменения часов.
| Референс | Схема |
| --- | --- |
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Проверка cert/CA →409 без изменений → выбор сохранить cert → подтверждённое identity-only удаление |
+16 -2
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Сервер161.104.46.222: завершён12.5.126 — при собственной CRL цепочке и временном crl-use/download=yes валидный client Ping3/3, отозванный reconnect AUTH_FAILED/API offline. Settings возвращены, baseline17×2/Docker/credentials restored. Первый cleanup issued leaf failed; проверенная own CA cascade recovery и итоговое повторение прошли. Backend ux-san121/frontend ux-ike124. Следующий127 — preflight удаления CRL-issued cert/CA до операций.12.5 открыт.
Сервер161.104.46.222: завершён12.5.127 — preflight CRL-issued/revoked leaf и CA children409 до writes, force не обходит цепочку; identity-only200 сохраняетcerts.8 IPsec scripts/127 state/13 новых HTTP cases/build/ESLint;7 native refusals+1 preserve delete. Backend ux-crl127/frontend ux-ike124, hashes совпали, settings/baseline17×2/Docker/credentials restored. Первый initial Ping failed сохранён; следующий128 — time-invalid certificates.12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -14,7 +14,7 @@
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
| Образы приложения | mmapp-test/backend:ux-san121 и mmapp-test/frontend:ux-ike124; запущены |
| Образы приложения | mmapp-test/backend:ux-crl127 и mmapp-test/frontend:ux-ike124; запущены |
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
@@ -1064,3 +1064,17 @@ Reference: [Certificates/CRL](https://manual.mikrotik.com/docs/authentication-au
| Референс | Схема |
| --- | --- |
| [ReUI List9](https://reui.io/preview/base/list-9?ref=mcp) | CA/CRL policy → валидный клиент → отзыв → reconnect отказ → возврат исходных settings |
## 12.5.127 — удаление CRL-issued сертификатов без частичных изменений (2026-10-11)
Исправлена выявленная126 последовательность: перед удалением identity API проверяет явно привязанный cert/issuer. Локально issued либо revoked certificate CA с crl=true получает409 до любых writes. Пользователь может удалить только identity с removeCertificate=false. В /ipsec/certs дополнительно запрещено отдельное удаление CA с дочерними certs: RouterOS каскадно удаляет цепочку, поэтому force=true это ограничение не обходит. Автоматический отзыв/удаление чужой цепочки не добавлялся. Imported non-issued/non-revoked leaf сохраняет прежний сценарий удаления.
Добавлены13 профильных HTTP cases с нулевыми writes/history при отказе, сохранением cert при identity-only, обеими issued/revoked формами native metadata, CA force guard и положительным imported случаем. Прошли8 IPsec scripts (state127), backend production build/scoped ESLint. Backend ux-crl127/version1.0.0-test.crl127 собран из121 с одним route override; локальные и серверные source/compiled SHA256 совпали. Frontend ux-ike124 сохранён, backup pre-crl127.env/прежний image оставлены. DTO/vendor не менялись.
Native собственная CRL chain: семь409 (user/cert targets, direct leaf/CA с/безforce и revoked leaf) сохранили certs; до identity-only identity также оставалась. Identity-only200 удалил выбранную identity, сохранил все3 certs. Первый control остановился на Ping3/3 без достаточной диагностики; cleanup прошёл, отказ сохранён. Повторение проверяло выданный native VIP и записывало bounded Ping attempts, затем завершило API-сценарии. Мгновенная связность/SLA не заявляется.
Settings восстановлены точно, own CA cascade после проверки всех детей/ссылок очистил только собственную цепочку; baseline17×2/Docker/private credentials restored. Оба CHR доступны, пароли/default/public/metadata/195.195.195.195/32 сохранены. UI и общая регрессия117 повторно не запускались: guards/backend-only, сохранение modal при API error уже принято предыдущими UI блоками. [Протокол](ux-ui/acceptance/ipsec-crl-delete-preflight-2026-10-11.json). Следующий128 — expired/not-yet-valid certs без изменения часов.
| Референс | Схема |
| --- | --- |
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Проверка cert/CA →409 без изменений → выбор сохранить cert → подтверждённое identity-only удаление |
@@ -0,0 +1,233 @@
{
"date": "2026-10-11",
"block": "12.5.127",
"runtime": {
"backend": "ux-crl127",
"version": "1.0.0-test.crl127",
"frontend": "ux-ike124",
"buildContext": "build-san121 plus routes/ipsec.ts override",
"sourceSha256": "b4cb5add56bfd355cd14942a1f5127554f8b92307f9e69094344192780603b75",
"compiledSha256": "cd28e9eee7d7c2712ef96de9d144fc36d0c0d6109a79bef7fb95a948317c4d1c",
"hashesMatchLocal": true
},
"checks": {
"ipsecScripts": 8,
"stateChecks": 127,
"newHttpCases": 13,
"backendBuild": true,
"scopedEslint": true
},
"native": {
"date": "2026-10-11",
"block": "12.5.127",
"name": "mm-ipsec127-1791662017580",
"linux": [
{
"variant": "certificate-derived",
"initiated": true,
"vip": "198.18.127.248",
"addressList": [
"127.0.0.1",
"::1",
"172.19.0.2",
"198.18.127.248"
],
"attempts": [
{
"attempt": 1,
"success": true,
"output": "PING 198.18.127.1 (198.18.127.1) from 198.18.127.248 : 56(84) bytes of data.\n64 bytes from 198.18.127.1: icmp_seq=1 ttl=64 time=2.89 ms\n64 bytes from 198.18.127.1: icmp_seq=2 ttl=64 time=2.99 ms\n64 bytes from 198.18.127.1: icmp_seq=3 ttl=64 time=2.67 ms\n\n--- 198.18.127.1 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2003ms\nrtt min/avg/max/mdev = 2.670/2.851/2.991/0.134 ms\n"
}
],
"ping3of3": true,
"native": [
{
"id": "CN=mm-ipsec127-1791662017580-client",
"state": "established",
"vip": "198.18.127.248"
}
],
"catalog": {
"online": true,
"activeAddress": "198.18.127.248"
}
}
],
"cleanup": [
{
"serverId": 1,
"/certificate/crl": true,
"/system/logging": true,
"/ip/ipsec/identity": true,
"/ip/ipsec/peer": true,
"/ip/ipsec/policy": true,
"/ip/ipsec/policy/group": true,
"/ip/ipsec/mode-config": true,
"/ip/ipsec/proposal": true,
"/ip/ipsec/profile": true,
"/certificate": true,
"/file": true,
"/ip/address": true,
"/ip/route": true,
"/ip/firewall/nat": true,
"/ip/pool": true,
"/interface/bridge": true,
"/ipv6/address": true
},
{
"serverId": 2,
"/certificate/crl": true,
"/system/logging": true,
"/ip/ipsec/identity": true,
"/ip/ipsec/peer": true,
"/ip/ipsec/policy": true,
"/ip/ipsec/policy/group": true,
"/ip/ipsec/mode-config": true,
"/ip/ipsec/proposal": true,
"/ip/ipsec/profile": true,
"/certificate": true,
"/file": true,
"/ip/address": true,
"/ip/route": true,
"/ip/firewall/nat": true,
"/ip/pool": true,
"/interface/bridge": true,
"/ipv6/address": true
}
],
"clientImage": "sha256:c9d01c801de88b2349f685c1cb631e67a8b1c697439a00148d4045c23a584054",
"settingsBefore": {
"builtin-trust-store": "default",
"crl-download": "false",
"crl-store": "ram",
"crl-use": "false",
"current-defaults": "fetch,mqtt,email,netwatch,container,lora,wiliot,dns,www,reverse-proxy"
},
"subject": {
"derHex": "302b3129302706035504030c206d6d2d69707365633132372d313739313636323031373538302d636c69656e74",
"attributes": [
{
"oidHex": "550403",
"stringTag": 12,
"value": "mm-ipsec127-1791662017580-client"
}
],
"san": "X509v3 Subject Alternative Name: \n DNS:mm-ipsec127-1791662017580.example"
},
"settingsEnabled": {
"builtin-trust-store": "default",
"crl-download": "true",
"crl-store": "ram",
"crl-use": "true",
"current-defaults": "fetch,mqtt,email,netwatch,container,lora,wiliot,dns,www,reverse-proxy"
},
"crlsBeforeControl": [],
"lifecycle": [
{
"phase": "initial",
"established": 1,
"online": true,
"vip": "198.18.127.248",
"addressList": [
"127.0.0.1",
"::1",
"172.19.0.2",
"198.18.127.248"
],
"ping3of3": true,
"sas": [
{
"spi": "0xF4F86B61",
"state": "mature",
"src": "161.104.46.222:4500",
"dst": "194.226.121.150:4500"
},
{
"spi": "0xCA52296A",
"state": "mature",
"src": "194.226.121.150:4500",
"dst": "161.104.46.222:4500"
}
],
"attempts": [
{
"attempt": 1,
"success": true,
"output": "PING 198.18.127.1 (198.18.127.1) from 198.18.127.248 : 56(84) bytes of data.\n64 bytes from 198.18.127.1: icmp_seq=1 ttl=64 time=3.77 ms\n64 bytes from 198.18.127.1: icmp_seq=2 ttl=64 time=2.57 ms\n64 bytes from 198.18.127.1: icmp_seq=3 ttl=64 time=2.49 ms\n\n--- 198.18.127.1 ping statistics ---\n3 packets transmitted, 3 received, 0% packet loss, time 2004ms\nrtt min/avg/max/mdev = 2.494/2.944/3.767/0.582 ms\n"
}
]
}
],
"preflight": [
{
"kind": "user identity",
"status": 409,
"error": "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно",
"identityPreserved": true,
"certificatesPreserved": 3
},
{
"kind": "cert target",
"status": 409,
"error": "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно",
"identityPreserved": true,
"certificatesPreserved": 3
},
{
"kind": "certificate",
"status": 409,
"error": "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно",
"identityPreserved": true,
"certificatesPreserved": 3
},
{
"kind": "CA chain",
"status": 409,
"error": "CA содержит выданные сертификаты. Его удаление затронет всю цепочку; отдельное удаление CA здесь недоступно",
"identityPreserved": true,
"certificatesPreserved": 3
},
{
"kind": "certificate?force=true",
"status": 409,
"error": "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно",
"identityPreserved": true,
"certificatesPreserved": 3
},
{
"kind": "CA chain?force=true",
"status": 409,
"error": "CA содержит выданные сертификаты. Его удаление затронет всю цепочку; отдельное удаление CA здесь недоступно",
"identityPreserved": true,
"certificatesPreserved": 3
}
],
"identityOnly": {
"status": 200,
"identityRemoved": true,
"certificatesPreserved": 3
},
"revokedCertificate": {
"status": 409,
"error": "Сертификат выдан CA с CRL и не удаляется отдельно в RouterOS. Удалите только identity, сохранив сертификат. Отзыв выполняется отдельно",
"preserved": true
},
"settingsRestored": true,
"dockerCleanup": true,
"ownedCaCascadeCleanup": true,
"credentialFilesRemoved": true
},
"earlierControlFailure": {
"initialPing3of3": false,
"exactCauseUnproven": true,
"cleanupCompletedBeforeRetry": true,
"retainedRemoteProtocol": "artifacts/wg-real-lifecycle/ipsec127-initial-ping-failure.json"
},
"bounds": [
"CA children always block direct deletion, including force=true; no automatic revoke or foreign-chain cleanup",
"Issued/revoked local cert with CRL issuer returns409 before identity deletion; keep-certificate explicitly allowed",
"Imported non-issued certificate with no revoked flag retains prior deletion behavior (mocked HTTP case)",
"Native final control records assigned VIP/Ping attempts; first control failure not converted to success",
"UI/vendor/DTO unchanged; no full regression repeat"
]
}