Standardize remote subnets build

This commit is contained in:
Owen
2025-11-17 10:22:10 -05:00
parent 7dcf4d5192
commit 1261ad3a00
6 changed files with 90 additions and 64 deletions

View File

@@ -1454,9 +1454,7 @@
"sitesFetchError": "An error occurred while fetching sites.",
"olmErrorFetchReleases": "An error occurred while fetching Olm releases.",
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
"remoteSubnets": "Remote Subnets",
"enterCidrRange": "Enter CIDR range",
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
"resourceEnableProxy": "Enable Public Proxy",
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
"externalProxyEnabled": "External Proxy Enabled",

View File

@@ -1,7 +1,8 @@
import { db } from "@server/db";
import { db, SiteResource } from "@server/db";
import { clients, orgs, sites } from "@server/db";
import { and, eq, isNotNull } from "drizzle-orm";
import config from "@server/lib/config";
import z from "zod";
interface IPRange {
start: bigint;
@@ -300,3 +301,28 @@ export async function getNextAvailableOrgSubnet(): Promise<string> {
return subnet;
}
export function generateRemoteSubnetsStr(allSiteResources: SiteResource[]) {
let remoteSubnets = allSiteResources
.filter((sr) => {
if (sr.mode === "cidr") return true;
if (sr.mode === "host") {
// check if its a valid IP using zod
const ipSchema = z.string().ip();
const parseResult = ipSchema.safeParse(sr.destination);
return parseResult.success;
}
return false;
})
.map((sr) => {
if (sr.mode === "cidr") return sr.destination;
if (sr.mode === "host") {
return `${sr.destination}/32`;
}
});
// remove duplicates
remoteSubnets = Array.from(new Set(remoteSubnets));
const remoteSubnetsStr =
remoteSubnets.length > 0 ? remoteSubnets.join(",") : null;
return remoteSubnetsStr;
}

View File

@@ -29,6 +29,8 @@ import {
} from "@server/routers/olm/peers";
import { sendToExitNode } from "#dynamic/lib/exitNodes";
import logger from "@server/logger";
import z from "zod";
import { generateRemoteSubnetsStr } from "@server/lib/ip";
export async function rebuildSiteClientAssociations(
siteResource: SiteResource,
@@ -331,14 +333,6 @@ async function handleMessagesForSiteClients(
.from(siteResources)
.where(eq(siteResources.siteId, site.siteId));
let remoteSubnets = allSiteResources
.filter((sr) => sr.mode == "cidr")
.map((sr) => sr.destination);
// remove duplicates
remoteSubnets = Array.from(new Set(remoteSubnets));
const remoteSubnetsStr =
remoteSubnets.length > 0 ? remoteSubnets.join(",") : null;
olmJobs.push(
olmAddPeer(
client.clientId,
@@ -351,7 +345,7 @@ async function handleMessagesForSiteClients(
publicKey: site.publicKey,
serverIP: site.address,
serverPort: site.listenPort,
remoteSubnets: remoteSubnetsStr
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
},
olm.olmId
)

View File

@@ -15,6 +15,7 @@ import { clients, clientSites, Newt, sites } from "@server/db";
import { eq, and, inArray } from "drizzle-orm";
import { updatePeer } from "../olm/peers";
import { sendToExitNode } from "#dynamic/lib/exitNodes";
import { generateRemoteSubnetsStr } from "@server/lib/ip";
const inputSchema = z.object({
publicKey: z.string(),
@@ -188,23 +189,13 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
.from(siteResources)
.where(eq(siteResources.siteId, site.siteId));
let remoteSubnets = allSiteResources
.filter((sr) => sr.mode == "cidr")
.map((sr) => sr.destination);
// remove duplicates
remoteSubnets = Array.from(new Set(remoteSubnets));
const remoteSubnetsStr =
remoteSubnets.length > 0
? remoteSubnets.join(",")
: null;
await updatePeer(client.clients.clientId, {
siteId: site.siteId,
endpoint: endpoint,
publicKey: site.publicKey,
serverIP: site.address,
serverPort: site.listenPort,
remoteSubnets: remoteSubnetsStr
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
});
} catch (error) {
logger.error(
@@ -231,46 +222,35 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
.from(siteResources)
.where(eq(siteResources.siteId, siteId));
const { tcpTargets, udpTargets } = allSiteResources.reduce(
(acc, resource) => {
// Only process port mode resources
if (resource.mode !== "port") {
return acc;
let targets: {
cidr: string;
portRange?: {
min: number;
max: number;
}[];
}[] = [];
for (const siteResource of allSiteResources) {
if (siteResource.mode == "host") {
// check if this is a valid ip
const ipSchema = z.string().ip();
if (ipSchema.safeParse(siteResource.destination).success) {
targets.push({
cidr: `${siteResource.destination}/32`
});
}
// Filter out invalid targets
if (
!resource.proxyPort ||
!resource.destination ||
!resource.destinationPort ||
!resource.protocol
) {
return acc;
}
// Format target into string
const formattedTarget = `${resource.proxyPort}:${resource.destination}:${resource.destinationPort}`;
// Add to the appropriate protocol array
if (resource.protocol === "tcp") {
acc.tcpTargets.push(formattedTarget);
} else {
acc.udpTargets.push(formattedTarget);
}
return acc;
},
{ tcpTargets: [] as string[], udpTargets: [] as string[] }
);
} else if (siteResource.mode == "cidr") {
targets.push({
cidr: siteResource.destination
});
}
}
// Build the configuration response
const configResponse = {
ipAddress: site.address,
peers: validPeers,
targets: {
udp: udpTargets,
tcp: tcpTargets
}
targets: targets
};
logger.debug("Sending config: ", configResponse);

View File

@@ -18,6 +18,7 @@ import { addPeer, deletePeer } from "../newt/peers";
import logger from "@server/logger";
import { listExitNodes } from "#dynamic/lib/exitNodes";
import { getNextAvailableClientSubnet } from "@server/lib/ip";
import { generateRemoteSubnetsStr } from "@server/lib/ip";
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
logger.info("Handling register olm message!");
@@ -238,11 +239,6 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
.from(siteResources)
.where(eq(siteResources.siteId, site.siteId));
let remoteSubnets = allSiteResources.filter((sr => sr.mode == "cidr")).map(sr => sr.destination);
// remove duplicates
remoteSubnets = Array.from(new Set(remoteSubnets));
const remoteSubnetsStr = remoteSubnets.length > 0 ? remoteSubnets.join(",") : null;
// Add the peer to the exit node for this site
if (clientSite.endpoint) {
logger.info(
@@ -280,7 +276,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
publicKey: site.publicKey,
serverIP: site.address,
serverPort: site.listenPort,
remoteSubnets: remoteSubnetsStr
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
});
}

View File

@@ -47,7 +47,39 @@ const createSiteResourceSchema = z
message:
"Protocol, proxy port, and destination port are required for port mode"
}
);
)
.refine(
(data) => {
if (data.mode === "host") {
// Check if it's a valid IP address using zod
const isValidIP = z.string().ip().safeParse(data.destination).success;
// Check if it's a valid domain (hostname pattern, TLD not required)
const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
const isValidDomain = domainRegex.test(data.destination);
return isValidIP || isValidDomain;
}
return true;
},
{
message:
"Destination must be a valid IP address or domain name for host mode"
}
)
.refine(
(data) => {
if (data.mode === "cidr") {
// Check if it's a valid CIDR
const isValidCIDR = z.string().cidr().safeParse(data.destination).success;
return isValidCIDR;
}
return true;
},
{
message: "Destination must be a valid CIDR notation for cidr mode"
}
);
export type CreateSiteResourceBody = z.infer<typeof createSiteResourceSchema>;
export type CreateSiteResourceResponse = SiteResource;