Adding gateway resources

This commit is contained in:
Owen
2026-09-24 09:49:08 -04:00
parent 1dace9c9f7
commit a17b870ed2
12 changed files with 247 additions and 45 deletions
+1 -1
View File
@@ -492,7 +492,7 @@ export const siteResources = pgTable(
name: varchar("name").notNull(),
ssl: boolean("ssl").notNull().default(false),
mode: varchar("mode")
.$type<"host" | "cidr" | "http" | "ssh" | "inference">()
.$type<"host" | "cidr" | "http" | "ssh" | "inference" | "gateway">()
.notNull(), // "host" | "cidr" | "http"
scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
+1 -1
View File
@@ -513,7 +513,7 @@ export const siteResources = sqliteTable("siteResources", {
name: text("name").notNull(),
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
mode: text("mode")
.$type<"host" | "cidr" | "http" | "ssh" | "inference">()
.$type<"host" | "cidr" | "http" | "ssh" | "inference" | "gateway">()
.notNull(), // "host" | "cidr" | "http"
scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
@@ -17,7 +17,6 @@ import {
performDeleteSiteResources,
runSiteResourceDeleteSideEffects
} from "@server/lib/deleteSiteResource";
import logger from "@server/logger";
export const MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE = 250;
@@ -53,7 +53,7 @@ const createSiteResourceSchema = z
name: z.string().min(1).max(255),
niceId: z.string().optional(),
// protocol: z.enum(["tcp", "udp"]).optional(),
mode: z.enum(["host", "cidr", "http", "ssh", "inference"]),
mode: z.enum(["host", "cidr", "http", "ssh", "inference", "gateway"]),
ssl: z.boolean().optional(), // only used for http mode
scheme: z.enum(["http", "https"]).optional(),
siteIds: z.array(z.int()).optional(),
@@ -165,10 +165,11 @@ const createSiteResourceSchema = z
)
.refine(
(data) => {
// destination is only optional for ssh mode with native authDaemonMode or inference
// destination is only optional for ssh mode with native authDaemonMode, inference, or gateway
if (
(data.mode === "ssh" && data.authDaemonMode === "native") ||
data.mode == "inference"
data.mode == "inference" ||
data.mode == "gateway"
) {
return true;
}
@@ -179,7 +180,7 @@ const createSiteResourceSchema = z
},
{
message:
"Destination is required unless mode is ssh with authDaemonMode native or inference"
"Destination is required unless mode is ssh with authDaemonMode native, inference, or gateway"
}
)
.refine(
@@ -447,14 +448,18 @@ export async function createSiteResource(
);
}
// gateway resources always route the whole subnet with everything open
const effectiveDestination =
mode === "gateway" ? "0.0.0.0/0" : destination;
// Only check if destination is an IP address
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
.safeParse(effectiveDestination).success;
if (
isIp &&
(isIpInCidr(destination!, org.subnet) ||
isIpInCidr(destination!, org.utilitySubnet))
(isIpInCidr(effectiveDestination!, org.subnet) ||
isIpInCidr(effectiveDestination!, org.utilitySubnet))
) {
return next(
createHttpError(
@@ -584,6 +589,32 @@ export async function createSiteResource(
tcpPortRangeStringAdjusted = destinationPort
? destinationPort.toString()
: "22";
} else if (mode === "gateway") {
tcpPortRangeStringAdjusted = "*";
}
let udpPortRangeStringAdjusted = udpPortRangeString;
if (mode === "gateway") {
udpPortRangeStringAdjusted = "*";
} else if (
mode === "http" ||
mode === "ssh" ||
mode === "inference"
) {
udpPortRangeStringAdjusted = "";
}
// default to true for http/ssh/inference, false otherwise;
// gateway always allows icmp
let disableIcmpAdjusted = disableIcmp ?? false;
if (mode === "gateway") {
disableIcmpAdjusted = false;
} else if (
mode === "http" ||
mode === "ssh" ||
mode === "inference"
) {
disableIcmpAdjusted = true;
}
// Create the site resource
@@ -594,21 +625,14 @@ export async function createSiteResource(
mode,
ssl,
networkId: network ? network.networkId : null,
destination: destination, // the ssh can be null
destination: effectiveDestination, // the ssh can be null
scheme,
destinationPort,
alias: alias ? alias.trim() : null,
aliasAddress,
tcpPortRangeString: tcpPortRangeStringAdjusted,
udpPortRangeString:
mode == "http" || mode == "ssh" || mode == "inference"
? ""
: udpPortRangeString,
disableIcmp:
disableIcmp ||
(mode == "http" || mode == "ssh" || mode == "inference"
? true
: false), // default to true for http resources, otherwise false
udpPortRangeString: udpPortRangeStringAdjusted,
disableIcmp: disableIcmpAdjusted,
domainId,
subdomain: finalSubdomain,
fullDomain,
@@ -51,7 +51,9 @@ const updateSiteResourceSchema = z
)
.optional(),
// mode: z.enum(["host", "cidr", "port"]).optional(),
mode: z.enum(["host", "cidr", "http", "ssh", "inference"]).optional(),
mode: z
.enum(["host", "cidr", "http", "ssh", "inference", "gateway"])
.optional(),
ssl: z.boolean().optional(),
scheme: z.enum(["http", "https"]).nullish(),
destinationPort: z.int().positive().nullish(),
@@ -158,10 +160,11 @@ const updateSiteResourceSchema = z
if (data.mode === undefined && data.destination === undefined) {
return true;
}
// destination is only optional for ssh mode with native authDaemonMode or inference
// destination is only optional for ssh mode with native authDaemonMode, inference, or gateway
if (
(data.mode === "ssh" && data.authDaemonMode === "native") ||
data.mode == "inference"
data.mode == "inference" ||
data.mode == "gateway"
) {
return true;
}
@@ -172,7 +175,7 @@ const updateSiteResourceSchema = z
},
{
message:
"Destination is required unless mode is ssh with authDaemonMode native or inference"
"Destination is required unless mode is ssh with authDaemonMode native, inference, or gateway"
}
)
.refine(
@@ -409,14 +412,18 @@ export async function updateSiteResource(
}
}
// gateway resources always route the whole subnet with everything open
const effectiveDestination =
mode === "gateway" ? "0.0.0.0/0" : destination;
// Only check if destination is an IP address
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
.safeParse(effectiveDestination).success;
if (
isIp &&
(isIpInCidr(destination!, org.subnet) ||
isIpInCidr(destination!, org.utilitySubnet))
(isIpInCidr(effectiveDestination!, org.subnet) ||
isIpInCidr(effectiveDestination!, org.utilitySubnet))
) {
return next(
createHttpError(
@@ -542,6 +549,34 @@ export async function updateSiteResource(
tcpPortRangeStringAdjusted = destinationPort
? destinationPort.toString()
: "22";
} else if (mode === "gateway") {
tcpPortRangeStringAdjusted = "*";
}
// undefined means "leave unchanged" (partial update); only
// adjusted when the mode is explicitly being changed
let udpPortRangeStringAdjusted = udpPortRangeString;
if (mode === "gateway") {
udpPortRangeStringAdjusted = "*";
} else if (
mode === "http" ||
mode === "ssh" ||
mode === "inference"
) {
udpPortRangeStringAdjusted = "";
}
let disableIcmpAdjusted = disableIcmp;
if (mode === "gateway") {
disableIcmpAdjusted = false;
} else if (
mode === "http" ||
mode === "ssh" ||
mode === "inference"
) {
disableIcmpAdjusted = true;
} else if (mode !== undefined) {
disableIcmpAdjusted = disableIcmp ?? false;
}
[updatedSiteResource] = await trx
@@ -552,7 +587,8 @@ export async function updateSiteResource(
mode: mode,
scheme,
ssl,
destination: destination,
destination:
mode === "gateway" ? effectiveDestination : destination,
destinationPort: destinationPort,
enabled: enabled,
alias:
@@ -562,19 +598,8 @@ export async function updateSiteResource(
: null
: undefined,
tcpPortRangeString: tcpPortRangeStringAdjusted,
udpPortRangeString:
mode == "http" || mode == "ssh" || mode == "inference"
? ""
: udpPortRangeString,
disableIcmp:
mode !== undefined
? disableIcmp ||
(mode == "http" ||
mode == "ssh" ||
mode == "inference"
? true
: false)
: disableIcmp,
udpPortRangeString: udpPortRangeStringAdjusted,
disableIcmp: disableIcmpAdjusted,
domainId,
subdomain: finalSubdomain,
fullDomain,