From 118120c9ce92e10a4e769712e4d4848ca5d75b20 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 23 Sep 2026 14:54:00 -0400 Subject: [PATCH] Create gateway resources in blueprints --- server/lib/blueprints/privateResources.ts | 52 ++++++++++++++++------- server/lib/blueprints/types.ts | 8 ++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index 085fed836..55904492b 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -259,6 +259,11 @@ export async function updatePrivateResources( } const isInference = resourceData.mode === "inference"; + const isGateway = resourceData.mode === "gateway"; + // gateway resources always route the whole subnet with everything open + const effectiveDestination = isGateway + ? "0.0.0.0/0" + : resourceData.destination; // Update existing resource const [updatedResource] = await trx @@ -268,23 +273,28 @@ export async function updatePrivateResources( mode: resourceData.mode, ssl: resourceSsl, scheme: resourceData.scheme, - destination: resourceData.destination, + destination: effectiveDestination, destinationPort: resourceData["destination-port"], enabled: resourceEnabled, alias: resourceData.alias || null, - disableIcmp: - resourceData["disable-icmp"] || - (resourceData.mode == "http" || isInference - ? true - : false), // default to true for http/inference resources, otherwise false + disableIcmp: isGateway + ? false // gateway always allows icmp + : resourceData["disable-icmp"] || + (resourceData.mode == "http" || isInference + ? true + : false), // default to true for http/inference resources, otherwise false tcpPortRangeString: resourceData.mode == "http" || isInference ? "443,80" - : resourceData["tcp-ports"], + : isGateway + ? "*" + : resourceData["tcp-ports"], udpPortRangeString: resourceData.mode == "http" || isInference ? "" - : resourceData["udp-ports"], + : isGateway + ? "*" + : resourceData["udp-ports"], fullDomain: resourceData["full-domain"] || null, subdomain: domainInfo ? domainInfo.subdomain : null, domainId: domainInfo ? domainInfo.domainId : null, @@ -529,6 +539,11 @@ export async function updatePrivateResources( } const isInference = resourceData.mode === "inference"; + const isGateway = resourceData.mode === "gateway"; + // gateway resources always route the whole subnet with everything open + const effectiveDestination = isGateway + ? "0.0.0.0/0" + : resourceData.destination; let domainInfo: | { subdomain: string | null; domainId: string } @@ -590,24 +605,29 @@ export async function updatePrivateResources( mode: resourceData.mode, ssl: resourceSsl, scheme: resourceData.scheme, - destination: resourceData.destination, + destination: effectiveDestination, destinationPort: resourceData["destination-port"], enabled: resourceEnabled, alias: resourceData.alias || null, aliasAddress: aliasAddress, - disableIcmp: - resourceData["disable-icmp"] || - (resourceData.mode == "http" || isInference - ? true - : false), // default to true for http/inference resources, otherwise false + disableIcmp: isGateway + ? false // gateway always allows icmp + : resourceData["disable-icmp"] || + (resourceData.mode == "http" || isInference + ? true + : false), // default to true for http/inference resources, otherwise false tcpPortRangeString: resourceData.mode == "http" || isInference ? "443,80" - : resourceData["tcp-ports"], + : isGateway + ? "*" + : resourceData["tcp-ports"], udpPortRangeString: resourceData.mode == "http" || isInference ? "" - : resourceData["udp-ports"], + : isGateway + ? "*" + : resourceData["udp-ports"], fullDomain: resourceData["full-domain"] || null, subdomain: domainInfo ? domainInfo.subdomain : null, domainId: domainInfo ? domainInfo.domainId : null, diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index ff3996417..a057eb745 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -612,7 +612,7 @@ export function isTargetsOnlyResource(resource: any): boolean { export const PrivateResourceSchema = z .object({ name: z.string().min(1).max(255), - mode: z.enum(["host", "cidr", "http", "ssh", "inference"]), + mode: z.enum(["host", "cidr", "http", "ssh", "inference", "gateway"]), site: z.string().optional(), // DEPRECATED IN FAVOR OF sites sites: z.array(z.string()).optional().default([]), // protocol: z.enum(["tcp", "udp"]).optional(), @@ -652,13 +652,15 @@ export const PrivateResourceSchema = z }) .refine( (data) => { - // destination is optional only for ssh+native or inference; required for everything else + // destination is optional only for ssh+native, inference, or gateway + // (gateway always routes the whole subnet, so destination is ignored); required for everything else const isNativeSSH = data.mode === "ssh" && (data["auth-daemon"] === undefined || data["auth-daemon"].mode === "native"); if ( data.mode !== "inference" && + data.mode !== "gateway" && !isNativeSSH && !data.destination ) { @@ -669,7 +671,7 @@ export const PrivateResourceSchema = z { path: ["destination"], message: - "destination is required unless mode is 'ssh' with auth-daemon mode 'native', or mode is 'inference'" + "destination is required unless mode is 'ssh' with auth-daemon mode 'native', 'inference', or 'gateway'" } ) .refine(