feat(vpn,target-profiles,migrations): add startup data migrations, support scoped VPN route allow entries, and rename target profile hosts to ips
This commit is contained in:
@@ -295,7 +295,12 @@ export class CertificateHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy route-based reprovisioning
|
||||
* Legacy route-based reprovisioning. Kept for backward compatibility with
|
||||
* older clients that send `reprovisionCertificate` typed-requests.
|
||||
*
|
||||
* Like reprovisionCertificateDomain, this triggers the full route apply
|
||||
* pipeline rather than smartProxy.provisionCertificate(routeName) — which
|
||||
* is a no-op when certProvisionFunction is set (Rust ACME disabled).
|
||||
*/
|
||||
private async reprovisionCertificateByRoute(routeName: string): Promise<{ success: boolean; message?: string }> {
|
||||
const dcRouter = this.opsServerRef.dcRouterRef;
|
||||
@@ -305,13 +310,19 @@ export class CertificateHandler {
|
||||
return { success: false, message: 'SmartProxy is not running' };
|
||||
}
|
||||
|
||||
// Clear event-based status for domains in this route so the
|
||||
// certificate-issued event can refresh them
|
||||
for (const [domain, entry] of dcRouter.certificateStatusMap) {
|
||||
if (entry.routeNames.includes(routeName)) {
|
||||
dcRouter.certificateStatusMap.delete(domain);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await smartProxy.provisionCertificate(routeName);
|
||||
// Clear event-based status for domains in this route
|
||||
for (const [domain, entry] of dcRouter.certificateStatusMap) {
|
||||
if (entry.routeNames.includes(routeName)) {
|
||||
dcRouter.certificateStatusMap.delete(domain);
|
||||
}
|
||||
if (dcRouter.routeConfigManager) {
|
||||
await dcRouter.routeConfigManager.applyRoutes();
|
||||
} else {
|
||||
await smartProxy.updateRoutes(smartProxy.routeManager.getRoutes());
|
||||
}
|
||||
return { success: true, message: `Certificate reprovisioning triggered for route '${routeName}'` };
|
||||
} catch (err: unknown) {
|
||||
@@ -320,7 +331,16 @@ export class CertificateHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain-based reprovisioning — clears backoff first, then triggers provision
|
||||
* Domain-based reprovisioning — clears backoff first, refreshes the smartacme
|
||||
* cert (when forceRenew is set), then re-applies routes so the running Rust
|
||||
* proxy actually picks up the new cert.
|
||||
*
|
||||
* Why applyRoutes (not smartProxy.provisionCertificate)?
|
||||
* smartProxy.provisionCertificate(routeName) routes through the Rust ACME
|
||||
* path, which is forcibly disabled whenever certProvisionFunction is set
|
||||
* (smart-proxy.ts:168-171). The only path that re-invokes
|
||||
* certProvisionFunction → bridge.loadCertificate is updateRoutes(), which
|
||||
* we trigger via routeConfigManager.applyRoutes().
|
||||
*/
|
||||
private async reprovisionCertificateDomain(domain: string, forceRenew?: boolean): Promise<{ success: boolean; message?: string }> {
|
||||
const dcRouter = this.opsServerRef.dcRouterRef;
|
||||
@@ -335,28 +355,37 @@ export class CertificateHandler {
|
||||
await dcRouter.certProvisionScheduler.clearBackoff(domain);
|
||||
}
|
||||
|
||||
// Find routes matching this domain — needed to provision through SmartProxy
|
||||
// Find routes matching this domain — fail early if none exist
|
||||
const routeNames = dcRouter.findRouteNamesForDomain(domain);
|
||||
if (routeNames.length === 0) {
|
||||
return { success: false, message: `No routes found for domain '${domain}'` };
|
||||
}
|
||||
|
||||
// If forceRenew, invalidate SmartAcme's cache so the next provision gets a fresh cert
|
||||
// If forceRenew, order a fresh cert from ACME now so it's already in
|
||||
// AcmeCertDoc by the time certProvisionFunction is invoked below.
|
||||
if (forceRenew && dcRouter.smartAcme) {
|
||||
try {
|
||||
await dcRouter.smartAcme.getCertificateForDomain(domain, { forceRenew: true });
|
||||
} catch {
|
||||
// Cache invalidation failed — proceed with provisioning anyway
|
||||
} catch (err: unknown) {
|
||||
return { success: false, message: `Failed to renew certificate for ${domain}: ${(err as Error).message}` };
|
||||
}
|
||||
}
|
||||
|
||||
// Clear status map entry so it gets refreshed by the certificate-issued event
|
||||
dcRouter.certificateStatusMap.delete(domain);
|
||||
|
||||
// Provision through SmartProxy — this triggers the full pipeline:
|
||||
// certProvisionFunction → bridge.loadCertificate → certificate-issued event → status map updated
|
||||
// Trigger the full route apply pipeline:
|
||||
// applyRoutes → updateRoutes → provisionCertificatesViaCallback →
|
||||
// certProvisionFunction(domain) → smartAcme.getCertificateForDomain →
|
||||
// bridge.loadCertificate → Rust hot-swaps `loaded_certs` →
|
||||
// certificate-issued event → certificateStatusMap updated
|
||||
try {
|
||||
await smartProxy.provisionCertificate(routeNames[0]);
|
||||
if (dcRouter.routeConfigManager) {
|
||||
await dcRouter.routeConfigManager.applyRoutes();
|
||||
} else {
|
||||
// Fallback when DB is disabled and there is no RouteConfigManager
|
||||
await smartProxy.updateRoutes(smartProxy.routeManager.getRoutes());
|
||||
}
|
||||
return { success: true, message: forceRenew ? `Certificate force-renewed for domain '${domain}'` : `Certificate reprovisioning triggered for domain '${domain}'` };
|
||||
} catch (err: unknown) {
|
||||
return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` };
|
||||
|
||||
@@ -31,7 +31,6 @@ export class VpnHandler {
|
||||
createdAt: c.createdAt,
|
||||
updatedAt: c.updatedAt,
|
||||
expiresAt: c.expiresAt,
|
||||
forceDestinationSmartproxy: c.forceDestinationSmartproxy ?? true,
|
||||
destinationAllowList: c.destinationAllowList,
|
||||
destinationBlockList: c.destinationBlockList,
|
||||
useHostIp: c.useHostIp,
|
||||
@@ -122,7 +121,6 @@ export class VpnHandler {
|
||||
clientId: dataArg.clientId,
|
||||
targetProfileIds: dataArg.targetProfileIds,
|
||||
description: dataArg.description,
|
||||
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
||||
destinationAllowList: dataArg.destinationAllowList,
|
||||
destinationBlockList: dataArg.destinationBlockList,
|
||||
useHostIp: dataArg.useHostIp,
|
||||
@@ -148,7 +146,6 @@ export class VpnHandler {
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
expiresAt: bundle.entry.expiresAt,
|
||||
forceDestinationSmartproxy: persistedClient?.forceDestinationSmartproxy ?? true,
|
||||
destinationAllowList: persistedClient?.destinationAllowList,
|
||||
destinationBlockList: persistedClient?.destinationBlockList,
|
||||
useHostIp: persistedClient?.useHostIp,
|
||||
@@ -180,7 +177,6 @@ export class VpnHandler {
|
||||
await manager.updateClient(dataArg.clientId, {
|
||||
description: dataArg.description,
|
||||
targetProfileIds: dataArg.targetProfileIds,
|
||||
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
||||
destinationAllowList: dataArg.destinationAllowList,
|
||||
destinationBlockList: dataArg.destinationBlockList,
|
||||
useHostIp: dataArg.useHostIp,
|
||||
|
||||
Reference in New Issue
Block a user