diff --git a/messages/en-US.json b/messages/en-US.json
index bfb07ef74..1cfee968e 100644
--- a/messages/en-US.json
+++ b/messages/en-US.json
@@ -307,6 +307,8 @@
"privateResourceTypeCidrDescription": "Expose a CIDR range on the site network to connected clients",
"privateResourceTypeHttpDescription": "Access an HTTP or HTTPS service through a domain",
"privateResourceTypeSshDescription": "Access an SSH server from connected clients",
+ "privateResourceTypeGatewayDescription": "Send all internet traffic to exit through the site network.",
+ "resourceTypeGatewayDescription": "Send all internet traffic to exit through the site network.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS Settings",
"resourceHTTPSSettingsDescription": "Configure how the resource will be accessed over HTTPS",
@@ -2927,6 +2929,7 @@
"editInternalResourceDialogModePort": "Port",
"editInternalResourceDialogModeHost": "Host",
"editInternalResourceDialogModeCidr": "CIDR",
+ "editInternalResourceDialogModeGateway": "Exit Node",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeInference": "AI Gateway",
@@ -2938,6 +2941,7 @@
"editInternalResourceDialogDestinationHostDescription": "The IP address or hostname of the resource on the site's network.",
"editInternalResourceDialogDestinationIPDescription": "The IP or hostname address of the resource on the site's network.",
"editInternalResourceDialogDestinationCidrDescription": "The CIDR range of the resource on the site's network.",
+ "editInternalResourceDialogDestinationGatewayDescription": "The sites to uses as exit nodes for this resource",
"editInternalResourceDialogAlias": "Alias",
"editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.",
"createInternalResourceDialogNoSitesAvailable": "No Sites Available",
@@ -2952,6 +2956,7 @@
"privateResourceNetworkAccessDescription": "Control TCP/UDP port access and whether ICMP ping is allowed for this resource.",
"hostSettings": "Host",
"cidrSettings": "CIDR",
+ "gatewaySettings": "Exit Node",
"createInternalResourceDialogResourceProperties": "Resource Properties",
"createInternalResourceDialogName": "Name",
"createInternalResourceDialogSite": "Site",
@@ -2990,6 +2995,7 @@
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"createInternalResourceDialogModeInference": "AI Gateway",
+ "createInternalResourceDialogModeGateway": "Exit Node",
"scheme": "Scheme",
"createInternalResourceDialogScheme": "Scheme",
"createInternalResourceDialogEnableSsl": "Enable TLS",
diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts
index 63ea3b23a..a4210ab8e 100644
--- a/server/db/pg/schema/schema.ts
+++ b/server/db/pg/schema/schema.ts
@@ -493,7 +493,7 @@ export const siteResources = pgTable(
ssl: boolean("ssl").notNull().default(false),
mode: varchar("mode")
.$type<"host" | "cidr" | "http" | "ssh" | "inference" | "gateway">()
- .notNull(), // "host" | "cidr" | "http"
+ .notNull(),
scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts
index cdd06ea20..a2c2339aa 100644
--- a/server/db/sqlite/schema/schema.ts
+++ b/server/db/sqlite/schema/schema.ts
@@ -514,7 +514,7 @@ export const siteResources = sqliteTable("siteResources", {
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
mode: text("mode")
.$type<"host" | "cidr" | "http" | "ssh" | "inference" | "gateway">()
- .notNull(), // "host" | "cidr" | "http"
+ .notNull(),
scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
diff --git a/src/components/PrivateResourceInfoBox.tsx b/src/components/PrivateResourceInfoBox.tsx
index 48f7fdd06..bec193bae 100644
--- a/src/components/PrivateResourceInfoBox.tsx
+++ b/src/components/PrivateResourceInfoBox.tsx
@@ -108,15 +108,19 @@ export function PrivateResourceInfoSections({
tcpPortRangeString: siteResource.tcpPortRangeString ?? "*",
udpPortRangeString: siteResource.udpPortRangeString ?? "*"
});
+ const showAccess = siteResource.mode !== "gateway";
const showAlias =
siteResource.mode !== "cidr" &&
siteResource.mode !== "http" &&
- siteResource.mode !== "inference";
+ siteResource.mode !== "inference" &&
+ siteResource.mode !== "gateway";
const showDestination =
!(
siteResource.mode === "ssh" &&
siteResource.authDaemonMode === "native"
- ) && siteResource.mode !== "inference";
+ ) &&
+ siteResource.mode !== "inference" &&
+ siteResource.mode !== "gateway";
const showCertificate = !!(
(siteResource.mode === "http" || siteResource.mode === "inference") &&
siteResource.ssl &&
@@ -129,7 +133,8 @@ export function PrivateResourceInfoSections({
siteResource.mode !== "inference";
const numSections =
- 2 +
+ 1 +
+ (showAccess ? 1 : 0) +
(showDestination ? 1 : 0) +
(showAlias ? 1 : 0) +
(showCertificate ? 1 : 0) +
@@ -144,17 +149,19 @@ export function PrivateResourceInfoSections({
-
- {t("access")}
-
-
-
-
+ {showAccess ? (
+
+ {t("access")}
+
+
+
+
+ ) : null}
{showDestination ? (
diff --git a/src/lib/privateResourceForm.ts b/src/lib/privateResourceForm.ts
index ad678bb73..4b8464540 100644
--- a/src/lib/privateResourceForm.ts
+++ b/src/lib/privateResourceForm.ts
@@ -421,7 +421,14 @@ export function createCreateFormSchema(t: TranslateFn) {
.min(1, t("createInternalResourceDialogNameRequired"))
.max(255, t("createInternalResourceDialogNameMaxLength")),
siteIds: z.array(z.number().int().positive()).optional(),
- mode: z.enum(["host", "cidr", "http", "ssh", "inference"]),
+ mode: z.enum([
+ "host",
+ "cidr",
+ "http",
+ "ssh",
+ "inference",
+ "gateway"
+ ]),
destination: z.string().nullish(),
alias: z.string().nullish(),
destinationPort: z
@@ -468,6 +475,7 @@ export function createCreateFormSchema(t: TranslateFn) {
if (
data.mode !== "ssh" &&
data.mode !== "inference" &&
+ data.mode !== "gateway" &&
(!trimmedDestination || trimmedDestination.length < 1)
) {
ctx.addIssue({