feat(ipsec): add certificate export functionality by name
Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 2m41s
Docker images / frontend-image (push) Successful in 2m57s
Docker images / updater-image (push) Successful in 48s
Docker images / backend-image (push) Successful in 2m48s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 13s
Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 2m41s
Docker images / frontend-image (push) Successful in 2m57s
Docker images / updater-image (push) Successful in 48s
Docker images / backend-image (push) Successful in 2m48s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 13s
- Implemented a new backend route for exporting existing client certificates in .p12 format by name. - Enhanced the frontend to support exporting certificates directly from the IPsec server grid. - Updated the IPsec page to manage certificate states and handle exports effectively. - Introduced new utility functions for certificate handling and improved data structures to accommodate the changes. - Added tests to ensure the reliability of the new certificate export feature.
This commit is contained in:
@@ -40,6 +40,7 @@ import {
|
||||
deleteIpsecPeer,
|
||||
deleteIpsecServer,
|
||||
deleteIpsecUser,
|
||||
exportIpsecCertByName,
|
||||
exportIpsecUserCert,
|
||||
initIpsecServer,
|
||||
listIpsec,
|
||||
@@ -97,6 +98,7 @@ const MOCK_IPSEC_SERVERS: IpsecServerSummaryDto[] = [
|
||||
serverId: "srv2",
|
||||
serverName: "mt-spb-edge-01",
|
||||
initialized: true,
|
||||
ike2Ready: true,
|
||||
serverEndpoint: "vpn.example.com",
|
||||
pool: { id: "srv2:p1", rosId: "*P1", serverId: "srv2", name: "ipsec-vpn", ranges: "10.77.0.2-10.77.0.254", managed: true },
|
||||
sharedModeConfig: { id: "srv2:m1", rosId: "*M1", serverId: "srv2", name: "ipsec-vpn", addressPool: "ipsec-vpn", staticDns: "10.77.0.1", managed: true },
|
||||
@@ -181,6 +183,7 @@ export default function IpsecPage() {
|
||||
const [certOpen, setCertOpen] = useState(false)
|
||||
const [certBundle, setCertBundle] = useState<IpsecCertBundle | null>(null)
|
||||
const [certClient, setCertClient] = useState<IpsecClientDto | null>(null)
|
||||
const [certByName, setCertByName] = useState<{ serverId: string; name: string } | null>(null)
|
||||
const [peerOpen, setPeerOpen] = useState(false)
|
||||
const [editingPeer, setEditingPeer] = useState<{ serverId: string; peer: IpsecPeerDto } | null>(null)
|
||||
const [pendingDelete, setPendingDelete] = useState<PendingDelete | null>(null)
|
||||
@@ -318,7 +321,7 @@ export default function IpsecPage() {
|
||||
|
||||
/** Подсказка свободного IP из пула выбранного сервера. */
|
||||
const freeIpHint = useMemo(() => {
|
||||
const summary = scopedSummaries.find((s) => s.initialized && s.pool)
|
||||
const summary = scopedSummaries.find((s) => s.ike2Ready && s.pool)
|
||||
if (!summary?.pool) return undefined
|
||||
const taken = scopedClients.map((c) => c.staticIp).filter(Boolean) as string[]
|
||||
return findFreePoolIp(summary.pool.ranges, taken) ?? undefined
|
||||
@@ -388,6 +391,7 @@ export default function IpsecPage() {
|
||||
if (res.bundle) {
|
||||
setCertBundle(res.bundle)
|
||||
setCertClient(res.client ?? null)
|
||||
setCertByName(null)
|
||||
setCertOpen(true)
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -421,6 +425,7 @@ export default function IpsecPage() {
|
||||
|
||||
const openCert = async (client: IpsecClientDto) => {
|
||||
setCertClient(client)
|
||||
setCertByName(null)
|
||||
setCertBundle(null)
|
||||
setCertOpen(true)
|
||||
setCertBusy(true)
|
||||
@@ -438,11 +443,34 @@ export default function IpsecPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const reexportCert = async (passphrase: string) => {
|
||||
if (!certClient) return
|
||||
/** Экспорт .p12 существующего клиентского серта по имени (client1/anakondra и т.п.). */
|
||||
const openCertByName = async (serverId: string, cert: IpsecCertInfoDto) => {
|
||||
setCertByName({ serverId, name: cert.name })
|
||||
setCertClient(null)
|
||||
setCertBundle(null)
|
||||
setCertOpen(true)
|
||||
setCertBusy(true)
|
||||
try {
|
||||
const bundle = await exportIpsecUserCert(backendUrl, certClient.serverId, certClient.rosId, passphrase)
|
||||
const passphrase = `mm-${Math.random().toString(36).slice(2, 10)}`
|
||||
const bundle = await exportIpsecCertByName(backendUrl, serverId, cert.name, passphrase)
|
||||
setCertBundle(bundle)
|
||||
} catch (e) {
|
||||
toast.error("Ошибка экспорта сертификата", {
|
||||
description: e instanceof Error ? e.message : String(e),
|
||||
})
|
||||
setCertOpen(false)
|
||||
} finally {
|
||||
setCertBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const reexportCert = async (passphrase: string) => {
|
||||
if (!certClient && !certByName) return
|
||||
setCertBusy(true)
|
||||
try {
|
||||
const bundle = certByName
|
||||
? await exportIpsecCertByName(backendUrl, certByName.serverId, certByName.name, passphrase)
|
||||
: await exportIpsecUserCert(backendUrl, certClient!.serverId, certClient!.rosId, passphrase)
|
||||
setCertBundle(bundle)
|
||||
} catch (e) {
|
||||
toast.error("Ошибка экспорта", { description: e instanceof Error ? e.message : String(e) })
|
||||
@@ -718,6 +746,7 @@ export default function IpsecPage() {
|
||||
onEditPeer={isLive ? openEditPeer : undefined}
|
||||
onDeletePeer={isLive ? (serverId, peer) => setPendingDelete({ kind: "peer", serverId, peer }) : undefined}
|
||||
onDeleteCert={isLive ? (serverId, cert) => setPendingDelete({ kind: "cert", serverId, cert }) : undefined}
|
||||
onExportCert={isLive ? (serverId, cert) => void openCertByName(serverId, cert) : undefined}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -820,7 +849,7 @@ export default function IpsecPage() {
|
||||
|
||||
<IpsecCertSheet
|
||||
open={certOpen}
|
||||
onOpenChange={setCertOpen}
|
||||
onOpenChange={(v) => { setCertOpen(v); if (!v) setCertByName(null) }}
|
||||
bundle={certBundle}
|
||||
busy={certBusy}
|
||||
onReexport={reexportCert}
|
||||
|
||||
Reference in New Issue
Block a user