feat(ipsec): управление IKEv2/IPsec VPN и клиентами из одного окна
Docker images / prepare-release (push) Successful in 15s
Docker images / backend-test (push) Successful in 2m32s
Docker images / frontend-image (push) Successful in 4m19s
Docker images / updater-image (push) Successful in 50s
Docker images / backend-image (push) Successful in 2m40s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 8s

- мастер инициализации сервера: CA и серверный сертификаты, peer/profile/proposal, пул, mode-config, policy-template, managed NAT masquerade
- клиенты по сертификату (RSA) и PSK: статический IP или из пула, онлайн-статус по active-peers
- скачивание .p12 и strongSwan .sswan с инструкцией, перекачка с новой passphrase
- история изменений (config_revisions, секция ipsec) и restore только managed-объектов
- привязка IPsec-клиентов к пользователям приложения по Common Name
- страница /ipsec с KPI и вкладками Клиенты/Сервер/CLI, сайдбар, command palette
This commit is contained in:
Denozordec
2026-09-12 20:20:56 +07:00
parent 564aae21f0
commit 7755d77340
35 changed files with 4154 additions and 13 deletions
+1
View File
@@ -4,6 +4,7 @@ export * from "./events.js"
export * from "./certificates.js"
export * from "./backups.js"
export * from "./wireguard.js"
export * from "./ipsec.js"
export * from "./users.js"
export * from "./traffic-flow.js"
export * from "./geoip.js"
+191
View File
@@ -0,0 +1,191 @@
import { z } from "zod"
export const ipsecAuthMethodSchema = z.enum(["certificate", "pre-shared-key"])
/** Клиент IKEv2 — /ip/ipsec/identity (+ опциональный персональный mode-config). */
export const ipsecClientDtoSchema = z.object({
id: z.string().min(1),
rosId: z.string().min(1),
serverId: z.string().min(1),
serverName: z.string(),
/** Управляемое имя клиента (из managed-комментария identity). */
name: z.string(),
authMethod: ipsecAuthMethodSchema,
certificateName: z.string().optional(),
commonName: z.string().optional(),
remoteId: z.string().optional(),
/** Статический IP клиента (персональный mode-config), undefined — из общего пула. */
staticIp: z.string().optional(),
modeConfigName: z.string().optional(),
peerName: z.string().optional(),
online: z.boolean(),
activeAddress: z.string().optional(),
activeSince: z.string().optional(),
disabled: z.boolean(),
comment: z.string().optional(),
/** Создан менеджером (managed-комментарий). */
managed: z.boolean(),
})
/** Слушатель IKEv2 — /ip/ipsec/peer (passive). */
export const ipsecPeerDtoSchema = z.object({
id: z.string().min(1),
rosId: z.string().min(1),
serverId: z.string().min(1),
serverName: z.string(),
name: z.string(),
address: z.string().optional(),
exchangeMode: z.string().optional(),
passive: z.boolean().optional(),
certificate: z.string().optional(),
profile: z.string().optional(),
disabled: z.boolean(),
comment: z.string().optional(),
managed: z.boolean(),
})
export const ipsecModeConfigDtoSchema = z.object({
id: z.string().min(1),
rosId: z.string().min(1),
serverId: z.string().min(1),
name: z.string(),
/** Общий пул (shared) или персональный статический адрес (per-user). */
addressPool: z.string().optional(),
address: z.string().optional(),
splitDns: z.string().optional(),
staticDns: z.string().optional(),
comment: z.string().optional(),
managed: z.boolean(),
})
export const ipsecPoolDtoSchema = z.object({
id: z.string().min(1),
rosId: z.string().min(1),
serverId: z.string().min(1),
name: z.string(),
ranges: z.string(),
comment: z.string().optional(),
managed: z.boolean(),
})
export const ipsecCertInfoDtoSchema = z.object({
name: z.string(),
commonName: z.string().optional(),
keySize: z.string().optional(),
fingerprint: z.string().optional(),
expiresAt: z.string().optional(),
trusted: z.boolean().optional(),
hasPrivateKey: z.boolean().optional(),
role: z.enum(["ca", "server", "client", "other"]).optional(),
managed: z.boolean(),
})
/** Сводка IKEv2-сервера на одном роутере. */
export const ipsecServerSummaryDtoSchema = z.object({
serverId: z.string().min(1),
serverName: z.string(),
serverCountry: z.string().optional(),
/** Managed-набор (CA + серверный серт + peer + mode-config) инициализирован. */
initialized: z.boolean(),
serverEndpoint: z.string().optional(),
peer: ipsecPeerDtoSchema.optional(),
pool: ipsecPoolDtoSchema.optional(),
sharedModeConfig: ipsecModeConfigDtoSchema.optional(),
caCert: ipsecCertInfoDtoSchema.optional(),
serverCert: ipsecCertInfoDtoSchema.optional(),
natRuleManaged: z.boolean(),
clientsTotal: z.number().int().nonnegative(),
clientsOnline: z.number().int().nonnegative(),
certs: z.array(ipsecCertInfoDtoSchema).optional(),
})
export const ipsecListResponseSchema = z.object({
servers: z.array(ipsecServerSummaryDtoSchema),
clients: z.array(ipsecClientDtoSchema),
failures: z
.array(
z.object({
serverId: z.string(),
serverName: z.string().optional(),
error: z.string(),
}),
)
.optional(),
})
export const ipsecInitRequestSchema = z.object({
serverId: z.union([z.string(), z.number()]),
/** Домен или IP для CN/SAN серверного сертификата (к нему подключаются клиенты). */
serverEndpoint: z.string().min(1),
/** Подсеть пула клиентов; из неё pool ranges .2.254. */
poolCidr: z.string().regex(/^[0-9a-fA-F.:/]+$/).default("10.77.0.0/24"),
dns: z.string().optional(),
caDaysValid: z.number().int().positive().default(3650),
serverDaysValid: z.number().int().positive().default(3650),
clientDaysValid: z.number().int().positive().default(1825),
/** Managed srcnat masquerade, чтобы у клиентов был интернет. */
createNatRule: z.boolean().default(true),
})
export const ipsecUserCreateRequestSchema = z.object({
serverId: z.union([z.string(), z.number()]),
/** Отображаемое имя клиента; из него slug для серта/CN. */
name: z.string().min(1).max(64),
authMethod: ipsecAuthMethodSchema.default("certificate"),
psk: z.string().min(8).optional(),
remoteId: z.string().optional(),
/** Конкретный IP клиента; без — выдаётся из пула. */
staticIp: z.string().optional(),
/** Пароль на экспортируемый .p12. */
passphrase: z.string().min(4).optional(),
daysValid: z.number().int().positive().optional(),
})
export const ipsecUserPatchSchema = z.object({
name: z.string().min(1).max(64).optional(),
/** null — вернуть выдачу из пула. */
staticIp: z.string().nullable().optional(),
psk: z.string().min(8).optional(),
remoteId: z.string().optional(),
disabled: z.boolean().optional(),
})
export const ipsecCertExportRequestSchema = z.object({
serverId: z.union([z.string(), z.number()]),
clientId: z.string().min(1),
passphrase: z.string().min(4),
})
/** Бандл для авторизации клиента: .p12 (+ strongSwan .sswan + инструкция). */
export const ipsecCertBundleSchema = z.object({
user: z.string(),
serverEndpoint: z.string().optional(),
filename: z.string(),
contentB64: z.string(),
mime: z.string().default("application/x-pkcs12"),
passphrase: z.string().optional(),
sswanFilename: z.string().optional(),
sswanContent: z.string().optional(),
instructions: z.string().optional(),
})
export const ipsecUserCreatedSchema = z.object({
client: ipsecClientDtoSchema,
/** Одноразовый бандл сертификата (для cert-клиентов). */
bundle: ipsecCertBundleSchema.optional(),
})
export type IpsecAuthMethod = z.infer<typeof ipsecAuthMethodSchema>
export type IpsecClientDto = z.infer<typeof ipsecClientDtoSchema>
export type IpsecPeerDto = z.infer<typeof ipsecPeerDtoSchema>
export type IpsecModeConfigDto = z.infer<typeof ipsecModeConfigDtoSchema>
export type IpsecPoolDto = z.infer<typeof ipsecPoolDtoSchema>
export type IpsecCertInfoDto = z.infer<typeof ipsecCertInfoDtoSchema>
export type IpsecServerSummaryDto = z.infer<typeof ipsecServerSummaryDtoSchema>
export type IpsecListResponse = z.infer<typeof ipsecListResponseSchema>
export type IpsecInitRequest = z.infer<typeof ipsecInitRequestSchema>
export type IpsecUserCreateRequest = z.infer<typeof ipsecUserCreateRequestSchema>
export type IpsecUserPatch = z.infer<typeof ipsecUserPatchSchema>
export type IpsecCertExportRequest = z.infer<typeof ipsecCertExportRequestSchema>
export type IpsecCertBundle = z.infer<typeof ipsecCertBundleSchema>
export type IpsecUserCreated = z.infer<typeof ipsecUserCreatedSchema>
+1 -1
View File
@@ -2,7 +2,7 @@ import { z } from "zod"
export const appUserRoleSchema = z.enum(["admin", "operator", "viewer"])
export const permLevelSchema = z.enum(["none", "read", "write"])
export const interfaceTypeSchema = z.enum(["ether", "gre", "wg", "other"])
export const interfaceTypeSchema = z.enum(["ether", "gre", "wg", "ipsec", "other"])
export const sectionPermSchema = z.object({
section: z.string().min(1),