diff --git a/backend/src/routes/ipsec-mutation.test.ts b/backend/src/routes/ipsec-mutation.test.ts index ab20e8ae..4e0e1103 100644 --- a/backend/src/routes/ipsec-mutation.test.ts +++ b/backend/src/routes/ipsec-mutation.test.ts @@ -6,9 +6,9 @@ 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[0] const original = MikrotikClient.fromServer -let writes = 0, records = 0, fault = "", certificateFixture = false, removedCertificates = 0 +let writes = 0, records = 0, fault = "", certificateFixture = false, removedCertificates = 0, exports = 0, exportedName = "" const identities: Record[] = [{ ".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" }] : [], removeCertificate: async () => { writes++; removedCertificates++ }, put: async () => { writes++ }, post: async () => { writes++ }, delete: async () => { writes++ }, patch: async (path: string, body: Record) => { 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 +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) => { 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) => app.inject({ method: "PATCH", url: "/api/ipsec/users/1/identity:*I", payload }) @@ -22,12 +22,18 @@ writes=records=0; fault=""; r=await patch({remoteId:"fresh"}); assert.equal(r.st // A same-named certificate is a separate object, not a binding inferred from a PSK label. certificateFixture=true;identities.splice(0,identities.length,{".id":"*UNBOUND",peer:"ike","auth-method":"pre-shared-key","remote-id":"key-id:unbound",comment:"MikrotikManager:ipsec user=shared"}); +exports=0;let exported=await app.inject({method:"POST",url:"/api/ipsec/users/1/identity:*UNBOUND/cert",payload:{passphrase:"fixture-pass"}});assert.equal(exported.statusCode,409);assert.equal(exports,0);assert.equal(exported.json().contentB64,undefined); writes=records=removedCertificates=0; let deletion=await app.inject({method:"DELETE",url:"/api/ipsec/users/1/identity:*UNBOUND"});assert.equal(deletion.statusCode,200);assert.equal(writes,1);assert.equal(removedCertificates,0);assert.equal(records,1); + + // Explicit certificate binding still permits deletion when no other references exist. identities.splice(0,identities.length,{".id":"*BOUND",peer:"ike","auth-method":"digital-signature","remote-certificate":"ipsec-user-shared",comment:"MikrotikManager:ipsec user=shared"}); +identities[0]!.comment="MikrotikManager:ipsec user=Renamed label";exports=0;exported=await app.inject({method:"POST",url:"/api/ipsec/users/1/identity:*BOUND/cert",payload:{passphrase:"fixture-pass"}});assert.equal(exported.statusCode,200);assert.equal(exports,1);assert.equal(exportedName,"ipsec-user-shared");assert.equal(exported.json().user,"Renamed label"); writes=records=removedCertificates=0; deletion=await app.inject({method:"DELETE",url:"/api/ipsec/users/1/identity:*BOUND"});assert.equal(deletion.statusCode,200);assert.equal(writes,2);assert.equal(removedCertificates,1);assert.equal(records,1); + + certificateFixture=true;identities.splice(0,identities.length,...["*FIRST","*SECOND"].map((id,n)=>({".id":id,peer:"ike","auth-method":"digital-signature","remote-certificate":"ipsec-user-shared",comment:"MikrotikManager:ipsec user=Shared"+n}))); for(const method of ["PATCH","DELETE"] as const){writes=records=0;const r=await app.inject({method,url:"/api/ipsec/users/1/cert:*CERT",...(method==="PATCH"?{payload:{name:"Wrong"}}:{})});assert.equal(r.statusCode,409);assert.equal(writes,0);assert.equal(records,0)} 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); diff --git a/backend/src/routes/ipsec.ts b/backend/src/routes/ipsec.ts index 38c99c25..a3998f6c 100644 --- a/backend/src/routes/ipsec.ts +++ b/backend/src/routes/ipsec.ts @@ -689,7 +689,10 @@ export function createIpsecRoutes(dependencies = ipsecDefaults): FastifyPluginAs if (!identity) return reply.status(404).send({ error: "Клиент не найден" }) const userName = parseIpsecUserComment(identity.comment) ?? "" if (!userName) return reply.status(400).send({ error: "Не managed-клиент" }) - const certName = identity["remote-certificate"]?.trim() || clientCertName(userName) + const certName = identity["remote-certificate"]?.trim() + if (!certName || identity["auth-method"]?.trim().toLowerCase() === "pre-shared-key") { + return reply.status(409).send({ error: "У выбранного клиента нет привязанного клиентского сертификата. Для отдельного сертификата используйте экспорт по его имени" }) + } if (!(await findCertificate(client, certName))) { return reply.status(404).send({ error: `Сертификат ${certName} не найден на роутере` }) } diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 6248919a..ffcf5e69 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-10: завершён12.5.117 — основной UI набор1243 passed/1 failed28.8m, context221/2217.2m; единственный failure CSS-transition исправлен ожиданием прежних44px/границ, scoped delta1/1.1465 unique accepted, не новый whole-main pass.905 source/config inputs проверены с явными preprocessing/test exceptions,984 production compiled normalizedLF совпали, read-only27 коллекций×2/own fixtures0, servers2/migrations9. Runtime backend/frontend ux-bound116. Следующий118 — legacy PSK certificate export угадывает имя: before200 подтверждён на своём cert, исправить/проверить.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.118 — legacy client export больше не угадывает certificate по managed имени; PSK/отсутствующая binding дают409 до export, explicit certificate binding работает после rename. Backend8 scripts/state114/build/ESLint passed; native before200 с чужим для identity своим cert, after409 без архива/positive bound200, cleanup13×2. Архивы/passphrase не сохранялись. Backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Следующий119 — реальные одновременные IKE sessions с одним ID через isolated Linux clients; image подготовка начата.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). @@ -1207,3 +1207,19 @@ Read-only stand: оба enabled CHR reachable/RouterOS7.24.5 stable;27 колл | Референс | Схема | | --- | --- | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Viewport → окончание анимации → прежний touch target44px → keyboard/file/axe | + +## 12.5.118 — IPsec: legacy экспорт только по явной certificate привязке (2026-10-10) + +Исправлен POST /ipsec/users/:serverId/:rosId/cert: managed display name больше не преобразуется в clientCertName как fallback. PSK либо отсутствие remote-certificate возвращают409 до вызова exporter, без contentB64/passphrase. Для отдельного сертификата остаётся явный export-by-name endpoint. Direct certificate binding поддерживается после смены display label: имя сертификата/ключ не меняются, bundle.user соответствует актуальной подписи. + +Изменены routes/ipsec.ts и mutation test. Все8 backend scripts/state114, backend build и scoped ESLint passed. HTTP test подтвердил PSK409/exportCalls0, positive200/exportedName ровно explicit ref при другом display label. Первый positive mock упал502 из-за отсутствующего getCertificates у заглушки; fixture дополнен и повтор принят. Последняя перестановка независимых HTTP кейсов проверена focused script повторно. UI/source не менялись, full1244/context221 не повторялись после локального изменения одного backend endpoint; границы117 сохранены. + +Native before на backend116: создана своя PSK identity без remote-certificate с именем, совпадающим со slug собственного unused signed decoy cert. Legacy POST200 вернул его P12 archive. After на118: этот запрос409 без archive/passphrase. Для primary certificate identity временно установлен другой managed display label; POST200 вернул archive её прямого remote-certificate, исходный comment восстановлен. Архив/passphrase/instructions использовались только в памяти, не записывались в report/Git. Existing certificate IKE session/CN/FQDN/catalog оставались корректны; это не новая PSK session или клиентская OS приёмка. + +Backend ux-export118/version1.0.0-test.export118 собран из build-bound116/base9da5e55 с одним route override; source/compiled route bytehash совпал. Frontend ux-bound116/version1.0.0-test.bound116 сохранён. Backend/PG healthy; rollback .env pre-export118.env и прежний образ оставлены, label export118 неGitSHA. Оба native запуска очистили собственные certificates/imports/peers/identity/groups/templates/mode-config/profile/proposal/files, baseline13×2 совпал. Native certificate IDs сохранялись до собственного cleanup; logging/default/public/metadata/195.195.195.195/32 сохранены, оба CHR/SSH доступны, пароли прежние. Протокол: [export binding](ux-ui/acceptance/ipsec-export-binding-2026-10-10.json). + +Следующий119 — закрыть controlled-only границу113: несколько реальных established IKE sessions с одним ID и одним certificate principal. Подготовка отдельного official Debian/strongSwan image начата; клиенты будут в собственных Docker namespaces, private credentials временные/свои, host network и приложение не перенастраиваются. До native подтверждения этот пункт остаётся открытым. Конфигурация сверена с [strongSwan](https://docs.strongswan.org/docs/latest/swanctl/swanctlConf.html), [каталогом credentials](https://docs.strongswan.org/docs/latest/swanctl/swanctlDir.html) и [RouterOS](https://manual.mikrotik.com/docs/virtual-private-networks/ipsec/). Внешние Portal/Telegram/Windows/macOS/mobile/multi-WAN/scale остаются отдельно. + +| Референс | Схема | +| --- | --- | +| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранный клиент → явная certificate binding → PKI export либо409 без архива | diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 7c3f48d4..1ff99135 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-10: завершён12.5.117 — основной UI набор1243 passed/1 failed28.8m, context221/2217.2m; единственный failure CSS-transition исправлен ожиданием прежних44px/границ, scoped delta1/1.1465 unique accepted, не новый whole-main pass.905 source/config inputs проверены с явными preprocessing/test exceptions,984 production compiled normalizedLF совпали, read-only27 коллекций×2/own fixtures0, servers2/migrations9. Runtime backend/frontend ux-bound116. Следующий118 — legacy PSK certificate export угадывает имя: before200 подтверждён на своём cert, исправить/проверить.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.118 — legacy client export больше не угадывает certificate по managed имени; PSK/отсутствующая binding дают409 до export, explicit certificate binding работает после rename. Backend8 scripts/state114/build/ESLint passed; native before200 с чужим для identity своим cert, after409 без архива/positive bound200, cleanup13×2. Архивы/passphrase не сохранялись. Backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Следующий119 — реальные одновременные IKE sessions с одним ID через isolated Linux clients; image подготовка начата.12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-10 -Актуальное продолжение 2026-10-10: завершён12.5.117 — основной UI набор1243 passed/1 failed28.8m, context221/2217.2m; единственный failure CSS-transition исправлен ожиданием прежних44px/границ, scoped delta1/1.1465 unique accepted, не новый whole-main pass.905 source/config inputs проверены с явными preprocessing/test exceptions,984 production compiled normalizedLF совпали, read-only27 коллекций×2/own fixtures0, servers2/migrations9. Runtime backend/frontend ux-bound116. Следующий118 — legacy PSK certificate export угадывает имя: before200 подтверждён на своём cert, исправить/проверить.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.118 — legacy client export больше не угадывает certificate по managed имени; PSK/отсутствующая binding дают409 до export, explicit certificate binding работает после rename. Backend8 scripts/state114/build/ESLint passed; native before200 с чужим для identity своим cert, after409 без архива/positive bound200, cleanup13×2. Архивы/passphrase не сохранялись. Backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Следующий119 — реальные одновременные IKE sessions с одним ID через isolated Linux clients; image подготовка начата.12.5 открыт. Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 0a864fbf..138cbcf3 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-10: завершён12.5.117 — основной UI набор1243 passed/1 failed28.8m, context221/2217.2m; единственный failure CSS-transition исправлен ожиданием прежних44px/границ, scoped delta1/1.1465 unique accepted, не новый whole-main pass.905 source/config inputs проверены с явными preprocessing/test exceptions,984 production compiled normalizedLF совпали, read-only27 коллекций×2/own fixtures0, servers2/migrations9. Runtime backend/frontend ux-bound116. Следующий118 — legacy PSK certificate export угадывает имя: before200 подтверждён на своём cert, исправить/проверить.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.118 — legacy client export больше не угадывает certificate по managed имени; PSK/отсутствующая binding дают409 до export, explicit certificate binding работает после rename. Backend8 scripts/state114/build/ESLint passed; native before200 с чужим для identity своим cert, after409 без архива/positive bound200, cleanup13×2. Архивы/passphrase не сохранялись. Backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Следующий119 — реальные одновременные IKE sessions с одним ID через isolated Linux clients; image подготовка начата.12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,23 @@ | 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–117 и ограничения ниже. Runtime backend/frontend ux-bound116. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–118 и ограничения ниже. Runtime backend ux-export118/frontend ux-bound116. | + +## 12.5.118 — IPsec: legacy экспорт только по явной certificate привязке (2026-10-10) + +Исправлен POST /ipsec/users/:serverId/:rosId/cert: managed display name больше не преобразуется в clientCertName как fallback. PSK либо отсутствие remote-certificate возвращают409 до вызова exporter, без contentB64/passphrase. Для отдельного сертификата остаётся явный export-by-name endpoint. Direct certificate binding поддерживается после смены display label: имя сертификата/ключ не меняются, bundle.user соответствует актуальной подписи. + +Изменены routes/ipsec.ts и mutation test. Все8 backend scripts/state114, backend build и scoped ESLint passed. HTTP test подтвердил PSK409/exportCalls0, positive200/exportedName ровно explicit ref при другом display label. Первый positive mock упал502 из-за отсутствующего getCertificates у заглушки; fixture дополнен и повтор принят. Последняя перестановка независимых HTTP кейсов проверена focused script повторно. UI/source не менялись, full1244/context221 не повторялись после локального изменения одного backend endpoint; границы117 сохранены. + +Native before на backend116: создана своя PSK identity без remote-certificate с именем, совпадающим со slug собственного unused signed decoy cert. Legacy POST200 вернул его P12 archive. After на118: этот запрос409 без archive/passphrase. Для primary certificate identity временно установлен другой managed display label; POST200 вернул archive её прямого remote-certificate, исходный comment восстановлен. Архив/passphrase/instructions использовались только в памяти, не записывались в report/Git. Existing certificate IKE session/CN/FQDN/catalog оставались корректны; это не новая PSK session или клиентская OS приёмка. + +Backend ux-export118/version1.0.0-test.export118 собран из build-bound116/base9da5e55 с одним route override; source/compiled route bytehash совпал. Frontend ux-bound116/version1.0.0-test.bound116 сохранён. Backend/PG healthy; rollback .env pre-export118.env и прежний образ оставлены, label export118 неGitSHA. Оба native запуска очистили собственные certificates/imports/peers/identity/groups/templates/mode-config/profile/proposal/files, baseline13×2 совпал. Native certificate IDs сохранялись до собственного cleanup; logging/default/public/metadata/195.195.195.195/32 сохранены, оба CHR/SSH доступны, пароли прежние. Протокол: [export binding](ux-ui/acceptance/ipsec-export-binding-2026-10-10.json). + +Следующий119 — закрыть controlled-only границу113: несколько реальных established IKE sessions с одним ID и одним certificate principal. Подготовка отдельного official Debian/strongSwan image начата; клиенты будут в собственных Docker namespaces, private credentials временные/свои, host network и приложение не перенастраиваются. До native подтверждения этот пункт остаётся открытым. Конфигурация сверена с [strongSwan](https://docs.strongswan.org/docs/latest/swanctl/swanctlConf.html), [каталогом credentials](https://docs.strongswan.org/docs/latest/swanctl/swanctlDir.html) и [RouterOS](https://manual.mikrotik.com/docs/virtual-private-networks/ipsec/). Внешние Portal/Telegram/Windows/macOS/mobile/multi-WAN/scale остаются отдельно. + +| Референс | Схема | +| --- | --- | +| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранный клиент → явная certificate binding → PKI export либо409 без архива | ## 12.5.117 — общая UI-регрессия и сверка действующего source/runtime (2026-10-10) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index e25c292f..a3f120df 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,8 +1,8 @@ # Оставшаяся внешняя приёмка — 2026-10-10 -Это актуальная матрица после 12.5.117. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.118. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend/frontend `ux-bound116`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-export118`, frontend `ux-bound116`; 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 | Реальные одновременные sessions с одним ID, Legacy certificate export binding; 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 | Реальные одновременные sessions с одним ID, Одновременные реальные sessions одного ID; 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 миграция не принята | @@ -25,7 +25,7 @@ ## Следующая последовательность 1. **12.5.95 завершён:** [source/context/compiled audit](ux-ui/acceptance/deployment-source-audit-2026-10-10.json), production source drift не выявлен; нормализация и исключения явно указаны. -2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — repeated native ID groups исправлены на controlled snapshots; 114 — multiple refs/target guards/native conflict приняты в описанных границах. 115 — UI сохранения сертификата/native DELETE принят; 116 — name-based guessing удалён/live PSK collision принят;117 — main1243/1failed+delta1/context221/source runtime приняты в точных границах; следующий118 — legacy export binding. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение. +2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — repeated native ID groups исправлены на controlled snapshots; 114 — multiple refs/target guards/native conflict приняты в описанных границах. 115 — UI сохранения сертификата/native DELETE принят; 116 — name-based guessing удалён/live PSK collision принят;117 — main1243/1failed+delta1/context221/source runtime приняты в точных границах; 118 — legacy export binding/native409/explicit200 принят;119 — isolated Linux multiple IKE sessions. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение. 3. **Заключительный профильный контроль:** после последнего исправления собрать только затронутую часть, проверить её live и обновить матрицу. Общую регрессию выполнить один раз на согласованном runtime. 4. **Внешние зависимости:** Telegram/Portal/client OS/multi-WAN/scale проходят отдельную приёмку при наличии нужных аккаунтов, устройств и разрешений. Лабораторный результат двух CHR не закрывает их автоматически. @@ -122,3 +122,19 @@ Read-only stand: оба enabled CHR reachable/RouterOS7.24.5 stable;27 колл | Референс | Схема | | --- | --- | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Viewport → окончание анимации → прежний touch target44px → keyboard/file/axe | + +## 12.5.118 — IPsec: legacy экспорт только по явной certificate привязке (2026-10-10) + +Исправлен POST /ipsec/users/:serverId/:rosId/cert: managed display name больше не преобразуется в clientCertName как fallback. PSK либо отсутствие remote-certificate возвращают409 до вызова exporter, без contentB64/passphrase. Для отдельного сертификата остаётся явный export-by-name endpoint. Direct certificate binding поддерживается после смены display label: имя сертификата/ключ не меняются, bundle.user соответствует актуальной подписи. + +Изменены routes/ipsec.ts и mutation test. Все8 backend scripts/state114, backend build и scoped ESLint passed. HTTP test подтвердил PSK409/exportCalls0, positive200/exportedName ровно explicit ref при другом display label. Первый positive mock упал502 из-за отсутствующего getCertificates у заглушки; fixture дополнен и повтор принят. Последняя перестановка независимых HTTP кейсов проверена focused script повторно. UI/source не менялись, full1244/context221 не повторялись после локального изменения одного backend endpoint; границы117 сохранены. + +Native before на backend116: создана своя PSK identity без remote-certificate с именем, совпадающим со slug собственного unused signed decoy cert. Legacy POST200 вернул его P12 archive. After на118: этот запрос409 без archive/passphrase. Для primary certificate identity временно установлен другой managed display label; POST200 вернул archive её прямого remote-certificate, исходный comment восстановлен. Архив/passphrase/instructions использовались только в памяти, не записывались в report/Git. Existing certificate IKE session/CN/FQDN/catalog оставались корректны; это не новая PSK session или клиентская OS приёмка. + +Backend ux-export118/version1.0.0-test.export118 собран из build-bound116/base9da5e55 с одним route override; source/compiled route bytehash совпал. Frontend ux-bound116/version1.0.0-test.bound116 сохранён. Backend/PG healthy; rollback .env pre-export118.env и прежний образ оставлены, label export118 неGitSHA. Оба native запуска очистили собственные certificates/imports/peers/identity/groups/templates/mode-config/profile/proposal/files, baseline13×2 совпал. Native certificate IDs сохранялись до собственного cleanup; logging/default/public/metadata/195.195.195.195/32 сохранены, оба CHR/SSH доступны, пароли прежние. Протокол: [export binding](ux-ui/acceptance/ipsec-export-binding-2026-10-10.json). + +Следующий119 — закрыть controlled-only границу113: несколько реальных established IKE sessions с одним ID и одним certificate principal. Подготовка отдельного official Debian/strongSwan image начата; клиенты будут в собственных Docker namespaces, private credentials временные/свои, host network и приложение не перенастраиваются. До native подтверждения этот пункт остаётся открытым. Конфигурация сверена с [strongSwan](https://docs.strongswan.org/docs/latest/swanctl/swanctlConf.html), [каталогом credentials](https://docs.strongswan.org/docs/latest/swanctl/swanctlDir.html) и [RouterOS](https://manual.mikrotik.com/docs/virtual-private-networks/ipsec/). Внешние Portal/Telegram/Windows/macOS/mobile/multi-WAN/scale остаются отдельно. + +| Референс | Схема | +| --- | --- | +| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранный клиент → явная certificate binding → PKI export либо409 без архива | diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index fd8428a1..c7f3148e 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-10: завершён12.5.117 — основной UI набор1243 passed/1 failed28.8m, context221/2217.2m; единственный failure CSS-transition исправлен ожиданием прежних44px/границ, scoped delta1/1.1465 unique accepted, не новый whole-main pass.905 source/config inputs проверены с явными preprocessing/test exceptions,984 production compiled normalizedLF совпали, read-only27 коллекций×2/own fixtures0, servers2/migrations9. Runtime backend/frontend ux-bound116. Следующий118 — legacy PSK certificate export угадывает имя: before200 подтверждён на своём cert, исправить/проверить.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.118 — legacy client export больше не угадывает certificate по managed имени; PSK/отсутствующая binding дают409 до export, explicit certificate binding работает после rename. Backend8 scripts/state114/build/ESLint passed; native before200 с чужим для identity своим cert, after409 без архива/positive bound200, cleanup13×2. Архивы/passphrase не сохранялись. Backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Следующий119 — реальные одновременные IKE sessions с одним ID через isolated Linux clients; image подготовка начата.12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -2512,3 +2512,19 @@ Read-only stand: оба enabled CHR reachable/RouterOS7.24.5 stable;27 колл | Референс | Схема | | --- | --- | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Viewport → окончание анимации → прежний touch target44px → keyboard/file/axe | + +## 12.5.118 — IPsec: legacy экспорт только по явной certificate привязке (2026-10-10) + +Исправлен POST /ipsec/users/:serverId/:rosId/cert: managed display name больше не преобразуется в clientCertName как fallback. PSK либо отсутствие remote-certificate возвращают409 до вызова exporter, без contentB64/passphrase. Для отдельного сертификата остаётся явный export-by-name endpoint. Direct certificate binding поддерживается после смены display label: имя сертификата/ключ не меняются, bundle.user соответствует актуальной подписи. + +Изменены routes/ipsec.ts и mutation test. Все8 backend scripts/state114, backend build и scoped ESLint passed. HTTP test подтвердил PSK409/exportCalls0, positive200/exportedName ровно explicit ref при другом display label. Первый positive mock упал502 из-за отсутствующего getCertificates у заглушки; fixture дополнен и повтор принят. Последняя перестановка независимых HTTP кейсов проверена focused script повторно. UI/source не менялись, full1244/context221 не повторялись после локального изменения одного backend endpoint; границы117 сохранены. + +Native before на backend116: создана своя PSK identity без remote-certificate с именем, совпадающим со slug собственного unused signed decoy cert. Legacy POST200 вернул его P12 archive. After на118: этот запрос409 без archive/passphrase. Для primary certificate identity временно установлен другой managed display label; POST200 вернул archive её прямого remote-certificate, исходный comment восстановлен. Архив/passphrase/instructions использовались только в памяти, не записывались в report/Git. Existing certificate IKE session/CN/FQDN/catalog оставались корректны; это не новая PSK session или клиентская OS приёмка. + +Backend ux-export118/version1.0.0-test.export118 собран из build-bound116/base9da5e55 с одним route override; source/compiled route bytehash совпал. Frontend ux-bound116/version1.0.0-test.bound116 сохранён. Backend/PG healthy; rollback .env pre-export118.env и прежний образ оставлены, label export118 неGitSHA. Оба native запуска очистили собственные certificates/imports/peers/identity/groups/templates/mode-config/profile/proposal/files, baseline13×2 совпал. Native certificate IDs сохранялись до собственного cleanup; logging/default/public/metadata/195.195.195.195/32 сохранены, оба CHR/SSH доступны, пароли прежние. Протокол: [export binding](ux-ui/acceptance/ipsec-export-binding-2026-10-10.json). + +Следующий119 — закрыть controlled-only границу113: несколько реальных established IKE sessions с одним ID и одним certificate principal. Подготовка отдельного official Debian/strongSwan image начата; клиенты будут в собственных Docker namespaces, private credentials временные/свои, host network и приложение не перенастраиваются. До native подтверждения этот пункт остаётся открытым. Конфигурация сверена с [strongSwan](https://docs.strongswan.org/docs/latest/swanctl/swanctlConf.html), [каталогом credentials](https://docs.strongswan.org/docs/latest/swanctl/swanctlDir.html) и [RouterOS](https://manual.mikrotik.com/docs/virtual-private-networks/ipsec/). Внешние Portal/Telegram/Windows/macOS/mobile/multi-WAN/scale остаются отдельно. + +| Референс | Схема | +| --- | --- | +| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранный клиент → явная certificate binding → PKI export либо409 без архива | diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 92d03c33..5b69cfb8 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Сервер161.104.46.222: backend/frontend ux-bound116/version1.0.0-test.bound116. Source905 проверен с явными exceptions, production drift0;984 emitted normalized files совпали. Read-only27×2 own fixtures0, servers2/migrations9, оба CHR доступны/пароли прежние. UI main1243+1 corrected delta/context221 приняты с original failure. Следующий118 — legacy certificate export binding. +Сервер161.104.46.222: backend ux-export118/version1.0.0-test.export118, frontend ux-bound116/version1.0.0-test.bound116. Native legacy PSK export409 без архива, explicit binding/rename positive200, cleanup13×2 passed. Source/compiled route bytehash совпал; оба CHR доступны/пароли прежние. Следующий119 — isolated Linux IKE clients с общим ID. | Параметр | Проверенное состояние | | --- | --- | @@ -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-bound116 и mmapp-test/frontend:ux-bound116; запущены | +| Образы приложения | mmapp-test/backend:ux-export118 и mmapp-test/frontend:ux-bound116; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -938,3 +938,19 @@ Read-only stand: оба enabled CHR reachable/RouterOS7.24.5 stable;27 колл | Референс | Схема | | --- | --- | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Viewport → окончание анимации → прежний touch target44px → keyboard/file/axe | + +## 12.5.118 — IPsec: legacy экспорт только по явной certificate привязке (2026-10-10) + +Исправлен POST /ipsec/users/:serverId/:rosId/cert: managed display name больше не преобразуется в clientCertName как fallback. PSK либо отсутствие remote-certificate возвращают409 до вызова exporter, без contentB64/passphrase. Для отдельного сертификата остаётся явный export-by-name endpoint. Direct certificate binding поддерживается после смены display label: имя сертификата/ключ не меняются, bundle.user соответствует актуальной подписи. + +Изменены routes/ipsec.ts и mutation test. Все8 backend scripts/state114, backend build и scoped ESLint passed. HTTP test подтвердил PSK409/exportCalls0, positive200/exportedName ровно explicit ref при другом display label. Первый positive mock упал502 из-за отсутствующего getCertificates у заглушки; fixture дополнен и повтор принят. Последняя перестановка независимых HTTP кейсов проверена focused script повторно. UI/source не менялись, full1244/context221 не повторялись после локального изменения одного backend endpoint; границы117 сохранены. + +Native before на backend116: создана своя PSK identity без remote-certificate с именем, совпадающим со slug собственного unused signed decoy cert. Legacy POST200 вернул его P12 archive. After на118: этот запрос409 без archive/passphrase. Для primary certificate identity временно установлен другой managed display label; POST200 вернул archive её прямого remote-certificate, исходный comment восстановлен. Архив/passphrase/instructions использовались только в памяти, не записывались в report/Git. Existing certificate IKE session/CN/FQDN/catalog оставались корректны; это не новая PSK session или клиентская OS приёмка. + +Backend ux-export118/version1.0.0-test.export118 собран из build-bound116/base9da5e55 с одним route override; source/compiled route bytehash совпал. Frontend ux-bound116/version1.0.0-test.bound116 сохранён. Backend/PG healthy; rollback .env pre-export118.env и прежний образ оставлены, label export118 неGitSHA. Оба native запуска очистили собственные certificates/imports/peers/identity/groups/templates/mode-config/profile/proposal/files, baseline13×2 совпал. Native certificate IDs сохранялись до собственного cleanup; logging/default/public/metadata/195.195.195.195/32 сохранены, оба CHR/SSH доступны, пароли прежние. Протокол: [export binding](ux-ui/acceptance/ipsec-export-binding-2026-10-10.json). + +Следующий119 — закрыть controlled-only границу113: несколько реальных established IKE sessions с одним ID и одним certificate principal. Подготовка отдельного official Debian/strongSwan image начата; клиенты будут в собственных Docker namespaces, private credentials временные/свои, host network и приложение не перенастраиваются. До native подтверждения этот пункт остаётся открытым. Конфигурация сверена с [strongSwan](https://docs.strongswan.org/docs/latest/swanctl/swanctlConf.html), [каталогом credentials](https://docs.strongswan.org/docs/latest/swanctl/swanctlDir.html) и [RouterOS](https://manual.mikrotik.com/docs/virtual-private-networks/ipsec/). Внешние Portal/Telegram/Windows/macOS/mobile/multi-WAN/scale остаются отдельно. + +| Референс | Схема | +| --- | --- | +| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранный клиент → явная certificate binding → PKI export либо409 без архива | diff --git a/docs/ux-ui/acceptance/ipsec-export-binding-2026-10-10.json b/docs/ux-ui/acceptance/ipsec-export-binding-2026-10-10.json new file mode 100644 index 00000000..7c3fd5c9 --- /dev/null +++ b/docs/ux-ui/acceptance/ipsec-export-binding-2026-10-10.json @@ -0,0 +1,438 @@ +{ + "date": "2026-10-10", + "block": "12.5.118", + "deployment": { + "date": "2026-10-10", + "baseCommit": "9da5e55", + "baseContext": "build-bound116", + "context": "build-export118", + "override": "backend/src/routes/ipsec.ts", + "sourceSha256": "4060f956cf30e26db326248f5f6574c75ed29f8952aa2afa65f0b099bfadb825", + "containers": { + "backend": { + "imageTag": "mmapp-test/backend:ux-export118", + "imageId": "sha256:395f100600ec69ef27a539a3a2ebb074e59bb2e21072c6c58cb3b6ef6281e13b", + "state": "running", + "health": "healthy" + }, + "frontend": { + "imageTag": "mmapp-test/frontend:ux-bound116", + "imageId": "sha256:d641b518fa35291e2c25dcabdef13b730c307a424d3b96bc9847df79022ead28", + "state": "running", + "health": null + }, + "postgres": { + "imageTag": "postgres:18-alpine", + "imageId": "sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873", + "state": "running", + "health": "healthy" + } + }, + "compiledSha256": "1ec5eab50eb8383644c9bb17f190b222c07ce3f7ebb6b98e4c8f81053e6e32be" + }, + "sourceByteHashMatches": true, + "compiledRouteByteHashMatches": true, + "before": { + "date": "2026-10-10", + "block": "12.5.118", + "name": "mm-ipsec118-1791643362094", + "cn": "mm-ipsec118-1791643362094", + "observations": [ + { + "serverId": 1, + "active": [ + { + ".id": "*1", + "id": "CN=mm-ipsec118-1791643362094", + "state": "established", + "dynamic-address": "198.18.118.250", + "remote-address": "104.165.244.7", + "uptime": "1s" + } + ], + "catalog": [ + { + "name": "CN=mm-ipsec118-1791643362094", + "commonName": "CN=mm-ipsec118-1791643362094", + "certName": "ipsec-user-mm-ipsec118-1791643362094-decoy", + "online": false + }, + { + "name": "mm-ipsec118-1791643362094", + "commonName": "mm-ipsec118-1791643362094", + "certName": "ipsec-user-mm-ipsec118-1791643362094", + "online": true, + "activeAddress": "198.18.118.250" + } + ] + }, + { + "serverId": 2, + "active": [ + { + ".id": "*1", + "id": "194.226.121.150", + "state": "established", + "remote-address": "194.226.121.150", + "uptime": "1s" + } + ], + "catalog": [] + } + ], + "cleanup": [ + { + "serverId": 1, + "/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 + }, + { + "serverId": 2, + "/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 + } + ], + "databaseWrites": false, + "certificate": { + "name": "ipsec-user-mm-ipsec118-1791643362094", + "common-name": "mm-ipsec118-1791643362094", + "ca": "mm-ipsec118-1791643362094-ca", + "key-usage": "digital-signature,key-encipherment,tls-client" + }, + "certificates": [ + [ + { + "name": "mm-ipsec118-1791643362094-ca", + "trusted": "true", + "trust-store": "all", + "invalid-before": "2026-10-10 17:42:43", + "invalid-after": "2026-10-12 17:42:43", + "common-name": "mm-ipsec118-1791643362094-CA" + }, + { + "name": "mm-ipsec118-1791643362094-server", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791643362094-ca", + "invalid-before": "2026-10-10 17:42:44", + "invalid-after": "2026-10-12 17:42:44", + "common-name": "194.226.121.150" + }, + { + "name": "ipsec-user-mm-ipsec118-1791643362094", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791643362094-ca", + "invalid-before": "2026-10-10 17:42:44", + "invalid-after": "2026-10-12 17:42:44", + "common-name": "mm-ipsec118-1791643362094" + }, + { + "name": "ipsec-user-mm-ipsec118-1791643362094-decoy", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791643362094-ca", + "invalid-before": "2026-10-10 17:42:45", + "invalid-after": "2026-10-12 17:42:45", + "common-name": "CN=mm-ipsec118-1791643362094" + } + ], + [ + { + "name": "mm-ipsec118-1791643362094-client-imported", + "trusted": "true", + "trust-store": "ipsec", + "invalid-before": "2026-10-10 16:42:44", + "invalid-after": "2026-10-12 16:42:44", + "common-name": "mm-ipsec118-1791643362094" + }, + { + "name": "mm-ipsec118-1791643362094-ca-imported", + "trusted": "true", + "trust-store": "ipsec", + "invalid-before": "2026-10-10 16:42:43", + "invalid-after": "2026-10-12 16:42:43", + "common-name": "mm-ipsec118-1791643362094-CA" + } + ] + ], + "catalogCorrect": true, + "dnPhase": { + "active": [ + { + ".id": "*1", + "id": "CN=mm-ipsec118-1791643362094", + "state": "established", + "dynamic-address": "198.18.118.250", + "remote-address": "104.165.244.7", + "uptime": "1s" + } + ], + "clients": [ + { + "name": "CN=mm-ipsec118-1791643362094", + "commonName": "CN=mm-ipsec118-1791643362094", + "certName": "ipsec-user-mm-ipsec118-1791643362094-decoy", + "online": false + }, + { + "name": "mm-ipsec118-1791643362094", + "commonName": "mm-ipsec118-1791643362094", + "certName": "ipsec-user-mm-ipsec118-1791643362094", + "online": true, + "activeAddress": "198.18.118.250" + } + ], + "decoyOnline": false + }, + "fqdnPhase": { + "configuredRemoteId": "fqdn:mm-ipsec118-1791643362094.example", + "nativeId": "mm-ipsec118-1791643362094.example", + "assignedAddress": "198.18.118.250", + "originalOnline": true, + "decoyOnline": false + }, + "export": { + "status": 200, + "identityAuth": "pre-shared-key", + "remoteCertificateAbsent": true, + "inferredCertificateName": "ipsec-user-mm-ipsec118-1791643362094-decoy", + "archiveReturned": true, + "archiveFilename": "cert_export_ipsec-user-mm-ipsec118-1791643362094-decoy.p12" + }, + "certificateNativeIdPreserved": true + }, + "native": { + "date": "2026-10-10", + "block": "12.5.118", + "name": "mm-ipsec118-1791645331520", + "cn": "mm-ipsec118-1791645331520", + "observations": [ + { + "serverId": 1, + "active": [ + { + ".id": "*1", + "id": "CN=mm-ipsec118-1791645331520", + "state": "established", + "dynamic-address": "198.18.118.250", + "remote-address": "104.165.244.7", + "uptime": "1s" + } + ], + "catalog": [ + { + "name": "CN=mm-ipsec118-1791645331520", + "commonName": "CN=mm-ipsec118-1791645331520", + "certName": "ipsec-user-mm-ipsec118-1791645331520-decoy", + "online": false + }, + { + "name": "mm-ipsec118-1791645331520", + "commonName": "mm-ipsec118-1791645331520", + "certName": "ipsec-user-mm-ipsec118-1791645331520", + "online": true, + "activeAddress": "198.18.118.250" + } + ] + }, + { + "serverId": 2, + "active": [ + { + ".id": "*1", + "id": "194.226.121.150", + "state": "established", + "remote-address": "194.226.121.150", + "uptime": "1s" + } + ], + "catalog": [] + } + ], + "cleanup": [ + { + "serverId": 1, + "/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 + }, + { + "serverId": 2, + "/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 + } + ], + "databaseWrites": false, + "certificate": { + "name": "ipsec-user-mm-ipsec118-1791645331520", + "common-name": "mm-ipsec118-1791645331520", + "ca": "mm-ipsec118-1791645331520-ca", + "key-usage": "digital-signature,key-encipherment,tls-client" + }, + "certificates": [ + [ + { + "name": "mm-ipsec118-1791645331520-ca", + "trusted": "true", + "trust-store": "all", + "invalid-before": "2026-10-10 18:15:33", + "invalid-after": "2026-10-12 18:15:33", + "common-name": "mm-ipsec118-1791645331520-CA" + }, + { + "name": "mm-ipsec118-1791645331520-server", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791645331520-ca", + "invalid-before": "2026-10-10 18:15:34", + "invalid-after": "2026-10-12 18:15:34", + "common-name": "194.226.121.150" + }, + { + "name": "ipsec-user-mm-ipsec118-1791645331520", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791645331520-ca", + "invalid-before": "2026-10-10 18:15:35", + "invalid-after": "2026-10-12 18:15:35", + "common-name": "mm-ipsec118-1791645331520" + }, + { + "name": "ipsec-user-mm-ipsec118-1791645331520-decoy", + "trusted": "false", + "trust-store": "all", + "ca": "mm-ipsec118-1791645331520-ca", + "invalid-before": "2026-10-10 18:15:39", + "invalid-after": "2026-10-12 18:15:39", + "common-name": "CN=mm-ipsec118-1791645331520" + } + ], + [ + { + "name": "mm-ipsec118-1791645331520-client-imported", + "trusted": "true", + "trust-store": "ipsec", + "invalid-before": "2026-10-10 17:15:35", + "invalid-after": "2026-10-12 17:15:35", + "common-name": "mm-ipsec118-1791645331520" + }, + { + "name": "mm-ipsec118-1791645331520-ca-imported", + "trusted": "true", + "trust-store": "ipsec", + "invalid-before": "2026-10-10 17:15:33", + "invalid-after": "2026-10-12 17:15:33", + "common-name": "mm-ipsec118-1791645331520-CA" + } + ] + ], + "catalogCorrect": true, + "dnPhase": { + "active": [ + { + ".id": "*1", + "id": "CN=mm-ipsec118-1791645331520", + "state": "established", + "dynamic-address": "198.18.118.250", + "remote-address": "104.165.244.7", + "uptime": "1s" + } + ], + "clients": [ + { + "name": "CN=mm-ipsec118-1791645331520", + "commonName": "CN=mm-ipsec118-1791645331520", + "certName": "ipsec-user-mm-ipsec118-1791645331520-decoy", + "online": false + }, + { + "name": "mm-ipsec118-1791645331520", + "commonName": "mm-ipsec118-1791645331520", + "certName": "ipsec-user-mm-ipsec118-1791645331520", + "online": true, + "activeAddress": "198.18.118.250" + } + ], + "decoyOnline": false + }, + "fqdnPhase": { + "configuredRemoteId": "fqdn:mm-ipsec118-1791645331520.example", + "nativeId": "mm-ipsec118-1791645331520.example", + "assignedAddress": "198.18.118.250", + "originalOnline": true, + "decoyOnline": false + }, + "export": { + "status": 409, + "identityAuth": "pre-shared-key", + "remoteCertificateAbsent": true, + "inferredCertificateName": "ipsec-user-mm-ipsec118-1791645331520-decoy", + "archiveReturned": false + }, + "boundExport": { + "status": 200, + "displayLabelDifferentFromCertificateName": true, + "explicitCertificateArchiveReturned": true + }, + "certificateNativeIdPreserved": true + }, + "local": { + "profileScripts": 8, + "stateChecks": 114, + "backendBuildPassed": true, + "eslintPassed": true, + "initialPositiveMockFailure": "Mock missing getCertificates; completed fixture and reran accepted", + "focusedHttpRerunPassed": true + }, + "bounds": [ + "Legacy managed user export only; explicit certificate-by-name endpoint unchanged", + "Archives/passphrases/instructions were inspected only in memory, never in report/Git", + "Positive response/archive returned, no new client OS or crypto interoperability claim", + "Frontend remains bound116; full UI regression117 historical, not rerun118", + "Native certificate session stayed live; no new PSK established session" + ] +}