fix(network): скрыть GRE PSK и сохранить признак IPsec

This commit is contained in:
Denozordec committed 2026-10-13 20:45:27 +07:00
1 parent a404da768d
commit 3f3f06f147
24 files changed
+1570 -23

No files matched your search

+1 -1
View File
@@ -753,7 +753,7 @@ function GrePageContent({ selectedServerId, selectedTunnelId, allTunnelsHref, on
open={!!codePreviewTunnel}
onClose={() => setCodePreviewTunnel(null)}
title={codePreviewTunnel?.name ?? "GRE"}
description={exportContext?.mock ? "Моковый снимок · данные не получены с RouterOS" : `Снимок на момент открытия · ${exportContext?.at ? formatReceipt(exportContext.at) : "время получения неизвестно"} · ${exportContext?.stale ? "актуальность не подтверждена" : "последний успешный опрос"}`}
description={(codePreviewTunnel?.ipsec && !codePreviewTunnel.ipsec.secret ? "Шаблон: ключ IPsec скрыт. Укажите ключ перед применением. " : "") + (exportContext?.mock ? "Моковый снимок · данные не получены с RouterOS" : `Снимок на момент открытия · ${exportContext?.at ? formatReceipt(exportContext.at) : "время получения неизвестно"} · ${exportContext?.stale ? "актуальность не подтверждена" : "последний успешный опрос"}`)}
formats={[
{
id: "rsc",
+1 -1
View File
@@ -29,7 +29,7 @@
"test:scheduler": "tsx src/services/scheduler-running.test.ts && tsx src/services/scheduler-recorded-run.test.ts && tsx src/routes/scheduler.test.ts",
"test:backups": "tsx src/services/backup-scheduler-run.test.ts && tsx src/services/backup-restore.test.ts && tsx src/services/backup-deletion.test.ts && tsx src/services/backup-dto.test.ts && tsx src/services/backup-storage-update.test.ts && tsx src/services/backup-job-execution.test.ts && tsx src/routes/backups-operations.test.ts && tsx src/services/s3-backup-client.test.ts",
"test:ipsec": "tsx src/services/ipsec-config.test.ts && tsx src/services/ipsec-state.test.ts && tsx src/routes/ipsec-history.test.ts && tsx src/services/ipsec-restore.test.ts && tsx src/services/ipsec-operation.test.ts && tsx src/routes/ipsec-mutation.test.ts && tsx src/routes/ipsec-nat.test.ts && tsx src/routes/ipsec-group.test.ts",
"test:live-maps": "tsx src/services/ospf-route-parse.test.ts && tsx src/services/vxlan-live.test.ts && tsx src/routes/vxlan.test.ts && tsx src/services/containers-live.test.ts && tsx src/services/firewall-poll.test.ts && tsx src/services/gre-catalog-poll.test.ts && tsx src/routes/filters-gre-catalog.test.ts && tsx src/routes/bgp-catalog.test.ts && tsx src/services/bgp-catalog-parse.test.ts && tsx src/routes/ospf-catalog.test.ts && tsx src/services/ospf-observed-values.test.ts",
"test:live-maps": "tsx src/services/gre-read.test.ts && tsx src/services/ospf-route-parse.test.ts && tsx src/services/vxlan-live.test.ts && tsx src/routes/vxlan.test.ts && tsx src/services/containers-live.test.ts && tsx src/services/firewall-poll.test.ts && tsx src/services/gre-catalog-poll.test.ts && tsx src/routes/filters-gre-catalog.test.ts && tsx src/routes/bgp-catalog.test.ts && tsx src/services/bgp-catalog-parse.test.ts && tsx src/routes/ospf-catalog.test.ts && tsx src/services/ospf-observed-values.test.ts",
"test": "node scripts/test.mjs",
"test:evolution": "tsx src/services/evobgp-catalog.test.ts && tsx src/routes/evobgp-catalog.test.ts && tsx src/routes/evobgp-settings.test.ts && tsx src/plugins/auth.evolution-settings.test.ts",
"test:netflow-purge": "tsx src/db/operation-lock.test.ts && tsx src/services/traffic-flow-purge-execution.test.ts && tsx src/routes/traffic-flow-purge.test.ts",
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import {createServer} from 'node:http'
import {randomBytes} from 'node:crypto'
const database=process.env.MMAPP_OWN_SQL_DB,url=new URL(process.env.DATABASE_URL)
assert(/^mm_accept_166_\d+$/.test(database??''));assert.equal(url.pathname,'/'+database);assert.equal(url.username,database)
process.env.AUTH_REQUIRED='true';process.env.AUTH_JWT_SECRET=randomBytes(32).toString('hex');process.env.AUTH_ISSUER='https://own209.invalid';process.env.AUTH_PORTAL_URL='https://own209.invalid'
const {pool,closePool}=await import('/app/backend/dist/db/index.js'),{initDatabase}=await import('/app/backend/dist/db/bootstrap.js'),{buildApp}=await import('/app/backend/dist/index.js')
assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database);assert(Object.values((await pool.query('SELECT rolsuper,rolcreatedb,rolcreaterole,rolbypassrls FROM pg_roles WHERE rolname=current_user')).rows[0]).every(v=>v===false))
const psk='own209-'+randomBytes(24).toString('base64url'),baseline=process.env.MMAPP_GRE_SECRET_EXPECT==='leak',cases=[];let app,serverId,nativeWrites=0
const native=createServer((req,res)=>{if(req.method!=='GET')nativeWrites++;res.setHeader('content-type','application/json');res.end(JSON.stringify(req.url.endsWith('/interface/gre')?[{'.id':'*1',name:'own209-encrypted','local-address':'192.0.2.1','remote-address':'192.0.2.2','ipsec-secret':psk,mtu:'1476',keepalive:'10s,3',disabled:'false',running:'true','allow-fast-path':'false'},{'.id':'*2',name:'own209-plain','remote-address':'192.0.2.3','ipsec-secret':'',disabled:'false',running:'true'}]:[]))});await new Promise(r=>native.listen(0,'127.0.0.1',r))
const report={date:'2026-10-13',block:'12.5.209',database,baseline,cases,scope:'Production compiled GRE/Filters GET and auth with own restricted PostgreSQL, local native HTTP fixture, synthetic nonempty PSK and isolated signed JWT',passed:false}
try{await initDatabase();serverId=Number((await pool.query("INSERT INTO servers(name,host,port,use_ssl,enabled,username,password) VALUES('own209-native','127.0.0.1',$1,false,true,'fixture','fixture') RETURNING id",[native.address().port])).rows[0].id);app=await buildApp({logger:false,startScheduler:false});await app.ready()
for(const role of ['reader','filter-writer','network-writer','network-admin','is-admin-only']){const permissions=['mm:network:read','mm:filters:read',...(role==='filter-writer'?['mm:filters:write']:role==='network-writer'?['mm:network:write']:role==='network-admin'?['mm:network:admin']:[])],token=app.jwt.sign({sub:'own209-'+role,apps:['mm'],permissions,is_admin:role==='is-admin-only',iss:process.env.AUTH_ISSUER},{expiresIn:'5m'});for(const path of ['/api/gre/tunnels','/api/filters/gre-tunnels']){const r=await app.inject({method:'GET',url:path+'?serverId='+serverId,headers:{authorization:'Bearer '+token}});assert.equal(r.statusCode,200);const rows=r.json().tunnels;assert.equal(rows.length,2);const encrypted=rows.find(t=>t.name==='own209-encrypted'),plain=rows.find(t=>t.name==='own209-plain');assert.equal(plain.ipsec,null);const direct=path==='/api/gre/tunnels',privileged=role==='network-writer'||role==='network-admin';if(direct){assert(encrypted.ipsec);assert.equal(encrypted.ipsec.secret,baseline||privileged?psk:'')}else if(baseline)assert.equal(encrypted.ipsec,null);else{assert(encrypted.ipsec);assert.equal(encrypted.ipsec.secret,'')}const exposed=r.body.includes(psk);assert.equal(exposed,direct&&(baseline||privileged));cases.push({role,path,status:200,pskExposed:exposed,configuredStatePresent:!!encrypted.ipsec,plainStateNull:true})}}
for(const path of ['/api/gre/tunnels','/api/filters/gre-tunnels']){assert.equal((await app.inject({method:'GET',url:path})).statusCode,401);cases.push({role:'unauthenticated',path,status:401})}
const revisions=(await pool.query("SELECT payload FROM config_revisions WHERE server_id=$1 AND section='gre'",[serverId])).rows;assert(revisions.length>0);assert(revisions.some(r=>JSON.stringify(r.payload).includes(psk)));assert.equal(nativeWrites,0);report.internalSnapshotRetainsPsk=true;report.nativeWrites=0;report.passed=true
}catch(e){report.errorName=e.name;fs.writeFileSync('/app/backend/acceptance/gre209-private.log',String(e.stack??e),{mode:0o600})}finally{if(app)await app.close();native.closeAllConnections();await new Promise(r=>native.close(r));await closePool();fs.writeFileSync('/app/backend/acceptance/gre209-'+(baseline?'baseline':'fixed')+'-result.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report))}
if(!report.passed)process.exitCode=1
+4 -2
View File
@@ -20,6 +20,7 @@ import {
type ServerRow = typeof servers.$inferSelect
interface RosGre {
"ipsec-secret"?: string
".id"?: string
name?: string
"local-address"?: string
@@ -51,7 +52,7 @@ interface LiveGreTunnel {
localInnerIp: string
remoteInnerIp: string
poolId: string
ipsec: null
ipsec: { secret: string } | null
mtu: number
keepaliveInterval: number
keepaliveRetries: number
@@ -238,7 +239,8 @@ async function fetchServerFilters(server: ServerRow) {
localInnerIp: inner.localInnerIp,
remoteInnerIp: inner.remoteInnerIp,
poolId: "live",
ipsec: null,
// This routing catalog carries security state, never key material.
ipsec: g["ipsec-secret"]?.trim() ? { secret: "" } : null,
mtu: Number.parseInt(g.mtu ?? "1476", 10) || 1476,
keepaliveInterval: keepalive.interval,
keepaliveRetries: keepalive.retries,
+5 -1
View File
@@ -1,5 +1,6 @@
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { z } from "zod"
import { greReadForUser } from "../services/gre-read.js"
import { MikrotikClient, MikrotikError } from "../services/mikrotik.js"
import { getEnabledServerById } from "../services/wireguard-live.js"
import {
@@ -111,7 +112,10 @@ const greRoutes: FastifyPluginAsyncZod = async (app) => {
const sid = parseDbServerId(q.serverId)
if (q.serverId !== undefined && sid === null) return reply.status(400).send({ error: "Некорректный serverId GRE" })
const result = await listGreTunnels({ serverId: sid !== null ? String(sid) : undefined })
return reply.send(result)
return reply.send({
...result,
tunnels: result.tunnels.map(tunnel => greReadForUser(tunnel, req.authUser)),
})
})
app.post("/gre/tunnels", async (req, reply) => {
+9
View File
@@ -0,0 +1,9 @@
import assert from 'node:assert/strict'
import {greReadForUser} from './gre-read.js'
import type {AuthUser} from '../lib/permissions.js'
const tunnel={id:'own209',ipsec:{secret:'own209-PSK',encAlg:'aes-256'},enabled:true}
for(const permission of ['mm:network:read','mm:filters:write','mm:network:write','mm:network:admin']){const user={id:'own209',email:'',name:'',apps:['mm'],permissions:[permission],isAdmin:false} satisfies AuthUser;const read=greReadForUser(tunnel,user);assert.equal(read.ipsec?.secret,permission==='mm:network:read'||permission==='mm:filters:write'?'':'own209-PSK');assert.equal(read.ipsec?.encAlg,'aes-256');assert.equal(tunnel.ipsec.secret,'own209-PSK')}
assert.equal(greReadForUser(tunnel),tunnel)
assert.equal(greReadForUser({ipsec:null},{id:'own209',email:'',name:'',apps:['mm'],permissions:['mm:network:read'],isAdmin:true}).ipsec,null)
assert.equal(greReadForUser(tunnel,{id:'own209',email:'',name:'',apps:['mm'],permissions:['mm:network:read'],isAdmin:true}).ipsec.secret,'')
console.log('GRE read projection: PSK hidden, configured state and algorithms preserved, source unchanged, writer/legacy retained')
+7
View File
@@ -0,0 +1,7 @@
import {hasPermission,type AuthUser} from "../lib/permissions.js"
/** Project HTTP data without changing the secret-bearing native snapshot. */
export function greReadForUser<T extends {ipsec:{secret:string}|null}>(tunnel:T,user?:AuthUser):T {
if (!user || hasPermission(user.permissions,"mm:network:write") || !tunnel.ipsec) return tunnel
return {...tunnel,ipsec:{...tunnel.ipsec,secret:""}}
}
+18 -3
View File
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 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,8 +696,6 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
## 12.5.86 — BFD/OSPF: раздельные наблюдения реакции и маршрута (2026-10-09)
@@ -2524,3 +2522,20 @@ Backend/frontend ux-native208 развёрнуты;470/527 source files и1044 c
Протоколы: [baseline](ux-ui/acceptance/wireguard208-baseline-result.json), [fixed SQL11](ux-ui/acceptance/wireguard208-fixed-result.json), [profile](ux-ui/acceptance/native208-profile-result.json), [E2E53](ux-ui/acceptance/native208-e2e-2026-10-13.json), [live UI20](ux-ui/acceptance/native208-live-ui-2026-10-13.json), [native privacy/cleanup2](ux-ui/acceptance/wireguard208-native-live-2026-10-13.json), [compiled](ux-ui/acceptance/compiled208-result.json), [deployment](ux-ui/acceptance/native208-deployment-2026-10-13.json).
Следующий209: GRE mapper содержит ipsecSecret иipsec.secret, проверить оба публичных GET иограничить выдачу секретов reader, сохранив crypto-state/public metadata иwriter editing.12.5 открыта.
## 12.5.209 — доступ к GRE PSK и достоверность каталога IPsec (2026-10-13)
В production baseline читатель получал PSK через GET /gre/tunnels. Каталог /filters/gre-tunnels ключ не раскрывал, но всегда возвращал ipsec:null, скрывая сам факт настройки IPsec. Основной GET теперь копирует публичный DTO и очищает secret при отсутствии mm:network:write/admin; алгоритмы и наличие объекта IPsec сохраняются. Internal snapshot и право редактирования для writer/admin не меняются. is_admin без permission не даёт доступ к ключу. Legacy AUTH_REQUIRED=false сохранён. Общий каталог Filters возвращает только признак IPsec и пустой secret для всех ролей.
Экспорт скрытого PSK явно помечен как шаблон, содержит <IPSEC_PSK> и указание заменить ключ перед применением. Пустой ipsec-secret не подставляется как готовая конфигурация. Установленные ReUI компоненты и тема не изменялись.
Проверки: baseline и исправленный production backend — по 12 уникальных SQL/API-кейсов на собственных ограниченных PostgreSQL DB/role, с синтетическим PSK и настоящим auth middleware. Snapshot сохраняет исходный PSK; native writes 0. Оба комплекта DB/role/container удалены. Локальный test:live-maps, frontend TypeScript, backend build и ESLint прошли. Четыре E2E: скрытый ключ/шаблон на 1440 и 320px, прежние изменение/очистка PSK writer. Полную регрессию 192 не повторяли.
Backend/frontend ux-gre209 развёрнуты; 473/527 source files и 1048 compiled artifacts совпали, backend healthy. Live: по одному собственному отключённому GRE с PSK на каждом CHR, реальные Portal admin/reader JWT; reader не получает ключ, admin сохраняет его, каталог обеих ролей показывает IPsec без ключа. Четыре UI-кейса reader/admin × 1440/320, без API mocks и без UI mutations. Оба интерфейса и временный Portal user удалены; отсутствие интерфейсов проверено. Пароли и исходные объекты не менялись. Это проверка privacy/configured state, не шифрованного data plane.
Первоначальная сборка имела неверную ссылку на базовый Docker image; исправлена до развёртывания. Финальный frontend собран с mock=false и live default. Два первых native create отказали: опущенный keepalive превращался в 0, который RouterOS отклоняет. Cleanup прошёл; проверка завершена с явным 10s,3. Ошибка значения по умолчанию остаётся отдельным пунктом 210. Повторные проверки не складываются в число уникальных кейсов.
Протоколы: [baseline](ux-ui/acceptance/gre209-baseline-result.json), [fixed SQL/API](ux-ui/acceptance/gre209-fixed-result.json), [профиль](ux-ui/acceptance/gre209-profile-2026-10-13.json), [E2E](ux-ui/acceptance/gre209-e2e-2026-10-13.json), [native/UI/cleanup](ux-ui/acceptance/gre209-native-live-2026-10-13.json), [source](ux-ui/acceptance/source209-result.json), [compiled](ux-ui/acceptance/compiled209-result.json), [deployment](ux-ui/acceptance/gre209-deployment-2026-10-13.json).
Следующий блок 210: исправить создание GRE с опущенным keepalive, проверить фактический RouterOS ответ и прежнее редактирование. Этап 12.5 открыт; внешние ограничения в актуальной матрице сохраняются.
+1 -5
View File
@@ -1,13 +1,11 @@
# План реализации UX/UI по пятичасовым окнам
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-13. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
## Актуальная точка продолжения — 2026-10-13
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
По поручению2026-10-09 работать укрупнёнными разделами, последовательно на текущей модели: карта возможностей → исправления → профильные тесты → build/deploy → собственные реальные сценарии → cleanup → один локальный коммит без push. Проверять квоту после завершённого раздела; незавершённые проверки явно переносить в журнал.
@@ -371,6 +369,4 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
+17 -1
View File
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -38,6 +38,22 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–196 и ограничения ниже. Runtime backend/frontend ux-peak196. |
## 12.5.209 — доступ к GRE PSK и достоверность каталога IPsec (2026-10-13)
В production baseline читатель получал PSK через GET /gre/tunnels. Каталог /filters/gre-tunnels ключ не раскрывал, но всегда возвращал ipsec:null, скрывая сам факт настройки IPsec. Основной GET теперь копирует публичный DTO и очищает secret при отсутствии mm:network:write/admin; алгоритмы и наличие объекта IPsec сохраняются. Internal snapshot и право редактирования для writer/admin не меняются. is_admin без permission не даёт доступ к ключу. Legacy AUTH_REQUIRED=false сохранён. Общий каталог Filters возвращает только признак IPsec и пустой secret для всех ролей.
Экспорт скрытого PSK явно помечен как шаблон, содержит <IPSEC_PSK> и указание заменить ключ перед применением. Пустой ipsec-secret не подставляется как готовая конфигурация. Установленные ReUI компоненты и тема не изменялись.
Проверки: baseline и исправленный production backend — по 12 уникальных SQL/API-кейсов на собственных ограниченных PostgreSQL DB/role, с синтетическим PSK и настоящим auth middleware. Snapshot сохраняет исходный PSK; native writes 0. Оба комплекта DB/role/container удалены. Локальный test:live-maps, frontend TypeScript, backend build и ESLint прошли. Четыре E2E: скрытый ключ/шаблон на 1440 и 320px, прежние изменение/очистка PSK writer. Полную регрессию 192 не повторяли.
Backend/frontend ux-gre209 развёрнуты; 473/527 source files и 1048 compiled artifacts совпали, backend healthy. Live: по одному собственному отключённому GRE с PSK на каждом CHR, реальные Portal admin/reader JWT; reader не получает ключ, admin сохраняет его, каталог обеих ролей показывает IPsec без ключа. Четыре UI-кейса reader/admin × 1440/320, без API mocks и без UI mutations. Оба интерфейса и временный Portal user удалены; отсутствие интерфейсов проверено. Пароли и исходные объекты не менялись. Это проверка privacy/configured state, не шифрованного data plane.
Первоначальная сборка имела неверную ссылку на базовый Docker image; исправлена до развёртывания. Финальный frontend собран с mock=false и live default. Два первых native create отказали: опущенный keepalive превращался в 0, который RouterOS отклоняет. Cleanup прошёл; проверка завершена с явным 10s,3. Ошибка значения по умолчанию остаётся отдельным пунктом 210. Повторные проверки не складываются в число уникальных кейсов.
Протоколы: [baseline](ux-ui/acceptance/gre209-baseline-result.json), [fixed SQL/API](ux-ui/acceptance/gre209-fixed-result.json), [профиль](ux-ui/acceptance/gre209-profile-2026-10-13.json), [E2E](ux-ui/acceptance/gre209-e2e-2026-10-13.json), [native/UI/cleanup](ux-ui/acceptance/gre209-native-live-2026-10-13.json), [source](ux-ui/acceptance/source209-result.json), [compiled](ux-ui/acceptance/compiled209-result.json), [deployment](ux-ui/acceptance/gre209-deployment-2026-10-13.json).
Следующий блок 210: исправить создание GRE с опущенным keepalive, проверить фактический RouterOS ответ и прежнее редактирование. Этап 12.5 открыт; внешние ограничения в актуальной матрице сохраняются.
## 12.5.208 — приватные ключи WireGuard и режим просмотра сетевых разделов (2026-10-13)
Старый production backend выдавал синтетический непустой приватный ключ reader при GET WireGuard с includePrivateKey=1/true. Baseline подтверждён actual compiled auth/mapper на собственной ограниченной PostgreSQL БД и локальном RouterOS HTTP fixture. Теперь явный запрос приватного ключа требует mm:network:write/admin, иначе403 до любых native requests. Обычный GET200 сохраняет public key и не содержит private key. is_admin без необходимого permission не заменяет право. Writer/admin сохраняют прежнее действие, legacy AUTH_REQUIRED=false не менялся.
+21 -4
View File
@@ -1,10 +1,10 @@
# Оставшаяся внешняя приёмка — 2026-10-13
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 12.5 открыт.
Это актуальная матрица после 12.5.208. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Это актуальная матрица после 12.5.209. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend/frontend `ux-native208`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend/frontend `ux-gre209`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -26,7 +26,7 @@
## Следующая последовательность — 2026-10-13
1. **209 — GRE secret DTOs:** /gre/tunnels и/filters/gre-tunnels, синтетические ключи/own DB, роли исохранение crypto-state; не менятьnative config.
1. **210 — GRE keepalive:** исправить значение по умолчанию создания, которое RouterOS отклоняет; проверить отсутствие неожиданных изменений при PATCH.
2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания.
3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
@@ -1440,3 +1440,20 @@ Backend/frontend ux-native208 развёрнуты;470/527 source files и1044 c
Протоколы: [baseline](ux-ui/acceptance/wireguard208-baseline-result.json), [fixed SQL11](ux-ui/acceptance/wireguard208-fixed-result.json), [profile](ux-ui/acceptance/native208-profile-result.json), [E2E53](ux-ui/acceptance/native208-e2e-2026-10-13.json), [live UI20](ux-ui/acceptance/native208-live-ui-2026-10-13.json), [native privacy/cleanup2](ux-ui/acceptance/wireguard208-native-live-2026-10-13.json), [compiled](ux-ui/acceptance/compiled208-result.json), [deployment](ux-ui/acceptance/native208-deployment-2026-10-13.json).
Следующий209: GRE mapper содержит ipsecSecret иipsec.secret, проверить оба публичных GET иограничить выдачу секретов reader, сохранив crypto-state/public metadata иwriter editing.12.5 открыта.
## 12.5.209 — доступ к GRE PSK и достоверность каталога IPsec (2026-10-13)
В production baseline читатель получал PSK через GET /gre/tunnels. Каталог /filters/gre-tunnels ключ не раскрывал, но всегда возвращал ipsec:null, скрывая сам факт настройки IPsec. Основной GET теперь копирует публичный DTO и очищает secret при отсутствии mm:network:write/admin; алгоритмы и наличие объекта IPsec сохраняются. Internal snapshot и право редактирования для writer/admin не меняются. is_admin без permission не даёт доступ к ключу. Legacy AUTH_REQUIRED=false сохранён. Общий каталог Filters возвращает только признак IPsec и пустой secret для всех ролей.
Экспорт скрытого PSK явно помечен как шаблон, содержит <IPSEC_PSK> и указание заменить ключ перед применением. Пустой ipsec-secret не подставляется как готовая конфигурация. Установленные ReUI компоненты и тема не изменялись.
Проверки: baseline и исправленный production backend — по 12 уникальных SQL/API-кейсов на собственных ограниченных PostgreSQL DB/role, с синтетическим PSK и настоящим auth middleware. Snapshot сохраняет исходный PSK; native writes 0. Оба комплекта DB/role/container удалены. Локальный test:live-maps, frontend TypeScript, backend build и ESLint прошли. Четыре E2E: скрытый ключ/шаблон на 1440 и 320px, прежние изменение/очистка PSK writer. Полную регрессию 192 не повторяли.
Backend/frontend ux-gre209 развёрнуты; 473/527 source files и 1048 compiled artifacts совпали, backend healthy. Live: по одному собственному отключённому GRE с PSK на каждом CHR, реальные Portal admin/reader JWT; reader не получает ключ, admin сохраняет его, каталог обеих ролей показывает IPsec без ключа. Четыре UI-кейса reader/admin × 1440/320, без API mocks и без UI mutations. Оба интерфейса и временный Portal user удалены; отсутствие интерфейсов проверено. Пароли и исходные объекты не менялись. Это проверка privacy/configured state, не шифрованного data plane.
Первоначальная сборка имела неверную ссылку на базовый Docker image; исправлена до развёртывания. Финальный frontend собран с mock=false и live default. Два первых native create отказали: опущенный keepalive превращался в 0, который RouterOS отклоняет. Cleanup прошёл; проверка завершена с явным 10s,3. Ошибка значения по умолчанию остаётся отдельным пунктом 210. Повторные проверки не складываются в число уникальных кейсов.
Протоколы: [baseline](ux-ui/acceptance/gre209-baseline-result.json), [fixed SQL/API](ux-ui/acceptance/gre209-fixed-result.json), [профиль](ux-ui/acceptance/gre209-profile-2026-10-13.json), [E2E](ux-ui/acceptance/gre209-e2e-2026-10-13.json), [native/UI/cleanup](ux-ui/acceptance/gre209-native-live-2026-10-13.json), [source](ux-ui/acceptance/source209-result.json), [compiled](ux-ui/acceptance/compiled209-result.json), [deployment](ux-ui/acceptance/gre209-deployment-2026-10-13.json).
Следующий блок 210: исправить создание GRE с опущенным keepalive, проверить фактический RouterOS ответ и прежнее редактирование. Этап 12.5 открыт; внешние ограничения в актуальной матрице сохраняются.
+18 -1
View File
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -3829,3 +3829,20 @@ Backend/frontend ux-native208 развёрнуты;470/527 source files и1044 c
Протоколы: [baseline](ux-ui/acceptance/wireguard208-baseline-result.json), [fixed SQL11](ux-ui/acceptance/wireguard208-fixed-result.json), [profile](ux-ui/acceptance/native208-profile-result.json), [E2E53](ux-ui/acceptance/native208-e2e-2026-10-13.json), [live UI20](ux-ui/acceptance/native208-live-ui-2026-10-13.json), [native privacy/cleanup2](ux-ui/acceptance/wireguard208-native-live-2026-10-13.json), [compiled](ux-ui/acceptance/compiled208-result.json), [deployment](ux-ui/acceptance/native208-deployment-2026-10-13.json).
Следующий209: GRE mapper содержит ipsecSecret иipsec.secret, проверить оба публичных GET иограничить выдачу секретов reader, сохранив crypto-state/public metadata иwriter editing.12.5 открыта.
## 12.5.209 — доступ к GRE PSK и достоверность каталога IPsec (2026-10-13)
В production baseline читатель получал PSK через GET /gre/tunnels. Каталог /filters/gre-tunnels ключ не раскрывал, но всегда возвращал ipsec:null, скрывая сам факт настройки IPsec. Основной GET теперь копирует публичный DTO и очищает secret при отсутствии mm:network:write/admin; алгоритмы и наличие объекта IPsec сохраняются. Internal snapshot и право редактирования для writer/admin не меняются. is_admin без permission не даёт доступ к ключу. Legacy AUTH_REQUIRED=false сохранён. Общий каталог Filters возвращает только признак IPsec и пустой secret для всех ролей.
Экспорт скрытого PSK явно помечен как шаблон, содержит <IPSEC_PSK> и указание заменить ключ перед применением. Пустой ipsec-secret не подставляется как готовая конфигурация. Установленные ReUI компоненты и тема не изменялись.
Проверки: baseline и исправленный production backend — по 12 уникальных SQL/API-кейсов на собственных ограниченных PostgreSQL DB/role, с синтетическим PSK и настоящим auth middleware. Snapshot сохраняет исходный PSK; native writes 0. Оба комплекта DB/role/container удалены. Локальный test:live-maps, frontend TypeScript, backend build и ESLint прошли. Четыре E2E: скрытый ключ/шаблон на 1440 и 320px, прежние изменение/очистка PSK writer. Полную регрессию 192 не повторяли.
Backend/frontend ux-gre209 развёрнуты; 473/527 source files и 1048 compiled artifacts совпали, backend healthy. Live: по одному собственному отключённому GRE с PSK на каждом CHR, реальные Portal admin/reader JWT; reader не получает ключ, admin сохраняет его, каталог обеих ролей показывает IPsec без ключа. Четыре UI-кейса reader/admin × 1440/320, без API mocks и без UI mutations. Оба интерфейса и временный Portal user удалены; отсутствие интерфейсов проверено. Пароли и исходные объекты не менялись. Это проверка privacy/configured state, не шифрованного data plane.
Первоначальная сборка имела неверную ссылку на базовый Docker image; исправлена до развёртывания. Финальный frontend собран с mock=false и live default. Два первых native create отказали: опущенный keepalive превращался в 0, который RouterOS отклоняет. Cleanup прошёл; проверка завершена с явным 10s,3. Ошибка значения по умолчанию остаётся отдельным пунктом 210. Повторные проверки не складываются в число уникальных кейсов.
Протоколы: [baseline](ux-ui/acceptance/gre209-baseline-result.json), [fixed SQL/API](ux-ui/acceptance/gre209-fixed-result.json), [профиль](ux-ui/acceptance/gre209-profile-2026-10-13.json), [E2E](ux-ui/acceptance/gre209-e2e-2026-10-13.json), [native/UI/cleanup](ux-ui/acceptance/gre209-native-live-2026-10-13.json), [source](ux-ui/acceptance/source209-result.json), [compiled](ux-ui/acceptance/compiled209-result.json), [deployment](ux-ui/acceptance/gre209-deployment-2026-10-13.json).
Следующий блок 210: исправить создание GRE с опущенным keepalive, проверить фактический RouterOS ответ и прежнее редактирование. Этап 12.5 открыт; внешние ограничения в актуальной матрице сохраняются.
+18 -3
View File
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Актуальное продолжение 2026-10-13: блок 209 завершён. GRE PSK скрыт от читателя; каталог Filters сохраняет признак настроенного IPsec без ключа. Пройдены 12 SQL/API-проверок, профиль live-maps, 4 E2E и 4 live UI-сценария. Два собственных отключённых GRE на CHR созданы и удалены. Backend/frontend: ux-gre209, 473/527 исходных файлов и 1048 compiled artifacts совпали. Следующий блок 210 — keepalive при создании GRE без явных параметров. Этап 12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -708,8 +708,6 @@ flowchart LR
## 12.5.77 — основной блок IPsec (2026-10-09)
Актуальное продолжение 2026-10-13:208 закрывает WireGuard private-key GET дляreader иread-only операции пятисетевыхразделов; baseline/fixed SQL11, WireGuard profile56,53 unique E2E,20 live UI и2 own native интерфейса passed/cleaned. Backend/frontend ux-native208, source470/527 иcompiled1044 matched. Следующий209 — секреты GRE вGET /gre/tunnels и/filters/gre-tunnels.12.5 открыта.
Подробный объём, протоколы, схема и ограничения: [12.5.77 в журнале](ux-ui-progress.md).
12.5.87 (2026-10-09): OSPF optimizer native/API и4 browser checks passed, собственные объекты очищены, baseline7 коллекций×2 сохранён. Подробности/runtime происхождение — в журнале.
@@ -2255,3 +2253,20 @@ Backend/frontend ux-native208 развёрнуты;470/527 source files и1044 c
Протоколы: [baseline](ux-ui/acceptance/wireguard208-baseline-result.json), [fixed SQL11](ux-ui/acceptance/wireguard208-fixed-result.json), [profile](ux-ui/acceptance/native208-profile-result.json), [E2E53](ux-ui/acceptance/native208-e2e-2026-10-13.json), [live UI20](ux-ui/acceptance/native208-live-ui-2026-10-13.json), [native privacy/cleanup2](ux-ui/acceptance/wireguard208-native-live-2026-10-13.json), [compiled](ux-ui/acceptance/compiled208-result.json), [deployment](ux-ui/acceptance/native208-deployment-2026-10-13.json).
Следующий209: GRE mapper содержит ipsecSecret иipsec.secret, проверить оба публичных GET иограничить выдачу секретов reader, сохранив crypto-state/public metadata иwriter editing.12.5 открыта.
## 12.5.209 — доступ к GRE PSK и достоверность каталога IPsec (2026-10-13)
В production baseline читатель получал PSK через GET /gre/tunnels. Каталог /filters/gre-tunnels ключ не раскрывал, но всегда возвращал ipsec:null, скрывая сам факт настройки IPsec. Основной GET теперь копирует публичный DTO и очищает secret при отсутствии mm:network:write/admin; алгоритмы и наличие объекта IPsec сохраняются. Internal snapshot и право редактирования для writer/admin не меняются. is_admin без permission не даёт доступ к ключу. Legacy AUTH_REQUIRED=false сохранён. Общий каталог Filters возвращает только признак IPsec и пустой secret для всех ролей.
Экспорт скрытого PSK явно помечен как шаблон, содержит <IPSEC_PSK> и указание заменить ключ перед применением. Пустой ipsec-secret не подставляется как готовая конфигурация. Установленные ReUI компоненты и тема не изменялись.
Проверки: baseline и исправленный production backend — по 12 уникальных SQL/API-кейсов на собственных ограниченных PostgreSQL DB/role, с синтетическим PSK и настоящим auth middleware. Snapshot сохраняет исходный PSK; native writes 0. Оба комплекта DB/role/container удалены. Локальный test:live-maps, frontend TypeScript, backend build и ESLint прошли. Четыре E2E: скрытый ключ/шаблон на 1440 и 320px, прежние изменение/очистка PSK writer. Полную регрессию 192 не повторяли.
Backend/frontend ux-gre209 развёрнуты; 473/527 source files и 1048 compiled artifacts совпали, backend healthy. Live: по одному собственному отключённому GRE с PSK на каждом CHR, реальные Portal admin/reader JWT; reader не получает ключ, admin сохраняет его, каталог обеих ролей показывает IPsec без ключа. Четыре UI-кейса reader/admin × 1440/320, без API mocks и без UI mutations. Оба интерфейса и временный Portal user удалены; отсутствие интерфейсов проверено. Пароли и исходные объекты не менялись. Это проверка privacy/configured state, не шифрованного data plane.
Первоначальная сборка имела неверную ссылку на базовый Docker image; исправлена до развёртывания. Финальный frontend собран с mock=false и live default. Два первых native create отказали: опущенный keepalive превращался в 0, который RouterOS отклоняет. Cleanup прошёл; проверка завершена с явным 10s,3. Ошибка значения по умолчанию остаётся отдельным пунктом 210. Повторные проверки не складываются в число уникальных кейсов.
Протоколы: [baseline](ux-ui/acceptance/gre209-baseline-result.json), [fixed SQL/API](ux-ui/acceptance/gre209-fixed-result.json), [профиль](ux-ui/acceptance/gre209-profile-2026-10-13.json), [E2E](ux-ui/acceptance/gre209-e2e-2026-10-13.json), [native/UI/cleanup](ux-ui/acceptance/gre209-native-live-2026-10-13.json), [source](ux-ui/acceptance/source209-result.json), [compiled](ux-ui/acceptance/compiled209-result.json), [deployment](ux-ui/acceptance/gre209-deployment-2026-10-13.json).
Следующий блок 210: исправить создание GRE с опущенным keepalive, проверить фактический RouterOS ответ и прежнее редактирование. Этап 12.5 открыт; внешние ограничения в актуальной матрице сохраняются.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,103 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"database": "mm_accept_166_1791897731540",
"baseline": true,
"cases": [
{
"role": "reader",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "reader",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": false,
"plainStateNull": true
},
{
"role": "filter-writer",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "filter-writer",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": false,
"plainStateNull": true
},
{
"role": "network-writer",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-writer",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": false,
"plainStateNull": true
},
{
"role": "network-admin",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-admin",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": false,
"plainStateNull": true
},
{
"role": "is-admin-only",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "is-admin-only",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": false,
"plainStateNull": true
},
{
"role": "unauthenticated",
"path": "/api/gre/tunnels",
"status": 401
},
{
"role": "unauthenticated",
"path": "/api/filters/gre-tunnels",
"status": 401
}
],
"scope": "Production compiled GRE/Filters GET and auth with own restricted PostgreSQL, local native HTTP fixture, synthetic nonempty PSK and isolated signed JWT",
"passed": true,
"internalSnapshotRetainsPsk": true,
"nativeWrites": 0,
"databaseAndRoleRemoved": true
}
@@ -0,0 +1,35 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"runtime": "ux-gre209",
"services": {
"backend": {
"image": "mmapp-test/backend:ux-gre209",
"imageId": "sha256:5324663c72cc559354d09538aaed115bd3dc08f653d53e553223898c2ed06baf",
"running": true,
"health": "healthy"
},
"frontend": {
"image": "mmapp-test/frontend:ux-gre209",
"imageId": "sha256:30d7d3886c0b531bd8119337d156e80f7348f0dfe12e75db632ee59483310b28",
"running": true,
"health": null
}
},
"rollbackTags": {
"MMAPP_IMAGE_TAG": "ux-native208",
"BACKEND_IMAGE_TAG": "ux-native208"
},
"chrPasswordsChanged": false,
"source": [
{
"kind": "backend",
"matched": 473
},
{
"kind": "frontend",
"matched": 527
}
],
"compiledMatched": 1048
}
@@ -0,0 +1,15 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"stats": {
"startTime": "2026-10-13T13:29:15.534Z",
"duration": 13684.177,
"expected": 4,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"scope": "Masked GRE PSK export on 1440/320 and existing PSK edit/clear API fixtures",
"apiMocked": true,
"uniqueCases": 4
}
@@ -0,0 +1,103 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"database": "mm_accept_166_1791898793107",
"baseline": false,
"cases": [
{
"role": "reader",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "reader",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "filter-writer",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "filter-writer",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-writer",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-writer",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-admin",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": true,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "network-admin",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "is-admin-only",
"path": "/api/gre/tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "is-admin-only",
"path": "/api/filters/gre-tunnels",
"status": 200,
"pskExposed": false,
"configuredStatePresent": true,
"plainStateNull": true
},
{
"role": "unauthenticated",
"path": "/api/gre/tunnels",
"status": 401
},
{
"role": "unauthenticated",
"path": "/api/filters/gre-tunnels",
"status": 401
}
],
"scope": "Production compiled GRE/Filters GET and auth with own restricted PostgreSQL, local native HTTP fixture, synthetic nonempty PSK and isolated signed JWT",
"passed": true,
"internalSnapshotRetainsPsk": true,
"nativeWrites": 0,
"databaseAndRoleRemoved": true
}
@@ -0,0 +1,69 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"runtime": "ux-gre209",
"cases": [
{
"host": "194.226.121.150",
"createdDisabled": true,
"adminSecretRetained": true,
"readerSecretRedacted": true,
"catalogConfiguredStateRetained": true,
"catalogKeyAbsent": true,
"dataPlaneTest": false
},
{
"host": "104.165.244.7",
"createdDisabled": true,
"adminSecretRetained": true,
"readerSecretRedacted": true,
"catalogConfiguredStateRetained": true,
"catalogKeyAbsent": true,
"dataPlaneTest": false
}
],
"uiCases": [
{
"width": 1440,
"role": "reader",
"actualNativeData": true,
"templateLabel": true,
"keyMaterialPublished": false,
"mutationRequests": 0,
"pageErrors": 0
},
{
"width": 1440,
"role": "admin",
"actualNativeData": true,
"templateLabel": false,
"keyMaterialPublished": false,
"mutationRequests": 0,
"pageErrors": 0
},
{
"width": 320,
"role": "reader",
"actualNativeData": true,
"templateLabel": true,
"keyMaterialPublished": false,
"mutationRequests": 0,
"pageErrors": 0
},
{
"width": 320,
"role": "admin",
"actualNativeData": true,
"templateLabel": false,
"keyMaterialPublished": false,
"mutationRequests": 0,
"pageErrors": 0
}
],
"apiMocks": false,
"realPortalJWT": true,
"privateMaterialPublished": false,
"cleanupCompleted": true,
"scope": "Disabled owned native GRE interfaces and PSK read policy; no encryption/data-plane claim",
"ownPortalUserRemoved": true
}
@@ -0,0 +1,11 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"command": "npm --prefix backend run test:live-maps",
"passed": true,
"location": "local development dependencies",
"externalNativeDevices": false,
"frontendTypecheckPassed": true,
"backendBuildPassed": true,
"eslintErrors": 0
}
@@ -0,0 +1,14 @@
{
"date": "2026-10-13",
"block": "12.5.209",
"kinds": [
{
"kind": "backend",
"matched": 473
},
{
"kind": "frontend",
"matched": 527
}
]
}
+5
View File
@@ -19,3 +19,8 @@ assert.ok(noInner.includes('local-address="192.0.2.10"'))
assert.ok(noInner.includes("keepalive=0 dscp=12"))
assert.ok(!noInner.includes("ipsec-secret="))
console.log("gre-export: escaped strings, observed GRE/PSK only, optional IP and no duplicate IPsec passed")
const masked = generateGreRsc({ ...tunnel, ipsec: { secret: '' } }, {})
assert.ok(masked.includes('ipsec-secret="<IPSEC_PSK>"'))
assert.ok(masked.includes('# Шаблон: ключ IPsec не раскрыт.'))
assert.ok(!masked.includes('ipsec-secret=""'))
console.log('gre-export: hidden configured PSK produces an explicitly labelled template, never an empty live key')
+4 -1
View File
@@ -17,7 +17,10 @@ export function generateGreRsc(t: GreTunnel, servers: Record<string, Server>): s
"comment=" + quoteRosString(t.comment),
]
if (t.localAddress && t.localAddress !== "0.0.0.0") args.push("local-address=" + quoteRosString(t.localAddress))
if (t.ipsec) args.push("ipsec-secret=" + quoteRosString(t.ipsec.secret))
if (t.ipsec) {
if (!t.ipsec.secret) lines.push("# Шаблон: ключ IPsec не раскрыт. Перед применением замените <IPSEC_PSK> своим ключом.")
args.push("ipsec-secret=" + quoteRosString(t.ipsec.secret || "<IPSEC_PSK>"))
}
lines.push("/interface/gre/add " + args.join(" "))
if (t.localInnerIp) lines.push("/ip/address/add address=" + quoteRosString(t.localInnerIp) + " interface=" + quoteRosString(t.name))
if (t.ipsec) lines.push("# Peer/policy создаёт RouterOS по ipsec-secret; ручные объекты IPsec не экспортируются")
+15
View File
@@ -0,0 +1,15 @@
import {test,expect} from '@playwright/test'
for(const width of [1440,320])test(`GRE: скрытый PSK сохраняет состояние IPsec и помечает экспорт ${width}`,async({page})=>{
let writes=0
const token=[Buffer.from('{}').toString('base64url'),Buffer.from(JSON.stringify({sub:'own209',apps:['mm'],permissions:['mm:dashboard:read','mm:servers:read','mm:network:read'],exp:Math.floor(Date.now()/1000)+600})).toString('base64url'),'fixture'].join('.')
await page.addInitScript(t=>localStorage.setItem('mmapp_token',t),token)
const tunnel={id:'*1',serverId:'1',name:'own209-gre',localAddress:'192.0.2.1',remoteAddress:'192.0.2.2',localInnerIp:'10.209.0.1/30',remoteInnerIp:'10.209.0.2/30',poolId:'live',ipsec:{secret:''},mtu:1476,keepaliveInterval:10,keepaliveRetries:3,dscp:'inherit',clampTcpMss:true,allowFastPath:false,comment:'',enabled:true,status:'up'}
await page.route('**/health',r=>r.fulfill({json:{ok:true}}))
await page.route('**/api/**',r=>{if(r.request().method()!=='GET')writes++;const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==='/api/auth/config'?{required:true}:p==='/api/servers'?[{id:1,name:'Own209 router',host:'192.0.2.1',enabled:true,country:'RU'}]:p==='/api/gre/tunnels'?{tunnels:[tunnel]}:{}})})
await page.setViewportSize({width,height:1000});await page.goto('/gre?server=1')
const row=page.getByRole('row').filter({hasText:'own209-gre'});await expect(row).toContainText('IPsec');await expect(row).not.toContainText('Открытый')
await expect(page.getByRole('button',{name:'Добавить туннель',exact:true})).toBeDisabled()
await page.getByRole('button',{name:'Просмотр кода: own209-gre',exact:true}).click()
const sheet=page.getByRole('dialog');await expect(sheet).toContainText('Шаблон: ключ IPsec скрыт');await expect(sheet.locator('pre')).toContainText('<IPSEC_PSK>');await expect(sheet.locator('pre')).not.toContainText('ipsec-secret=""')
expect(writes).toBe(0);expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true)
})