mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-03 01:06:39 +00:00
Standardize remote subnets build
This commit is contained in:
@@ -1454,9 +1454,7 @@
|
|||||||
"sitesFetchError": "An error occurred while fetching sites.",
|
"sitesFetchError": "An error occurred while fetching sites.",
|
||||||
"olmErrorFetchReleases": "An error occurred while fetching Olm releases.",
|
"olmErrorFetchReleases": "An error occurred while fetching Olm releases.",
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"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",
|
"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.",
|
"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",
|
"externalProxyEnabled": "External Proxy Enabled",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { db } from "@server/db";
|
import { db, SiteResource } from "@server/db";
|
||||||
import { clients, orgs, sites } from "@server/db";
|
import { clients, orgs, sites } from "@server/db";
|
||||||
import { and, eq, isNotNull } from "drizzle-orm";
|
import { and, eq, isNotNull } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
interface IPRange {
|
interface IPRange {
|
||||||
start: bigint;
|
start: bigint;
|
||||||
@@ -300,3 +301,28 @@ export async function getNextAvailableOrgSubnet(): Promise<string> {
|
|||||||
|
|
||||||
return subnet;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import {
|
|||||||
} from "@server/routers/olm/peers";
|
} from "@server/routers/olm/peers";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import z from "zod";
|
||||||
|
import { generateRemoteSubnetsStr } from "@server/lib/ip";
|
||||||
|
|
||||||
export async function rebuildSiteClientAssociations(
|
export async function rebuildSiteClientAssociations(
|
||||||
siteResource: SiteResource,
|
siteResource: SiteResource,
|
||||||
@@ -331,14 +333,6 @@ async function handleMessagesForSiteClients(
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, site.siteId));
|
.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(
|
olmJobs.push(
|
||||||
olmAddPeer(
|
olmAddPeer(
|
||||||
client.clientId,
|
client.clientId,
|
||||||
@@ -351,7 +345,7 @@ async function handleMessagesForSiteClients(
|
|||||||
publicKey: site.publicKey,
|
publicKey: site.publicKey,
|
||||||
serverIP: site.address,
|
serverIP: site.address,
|
||||||
serverPort: site.listenPort,
|
serverPort: site.listenPort,
|
||||||
remoteSubnets: remoteSubnetsStr
|
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
|
||||||
},
|
},
|
||||||
olm.olmId
|
olm.olmId
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { clients, clientSites, Newt, sites } from "@server/db";
|
|||||||
import { eq, and, inArray } from "drizzle-orm";
|
import { eq, and, inArray } from "drizzle-orm";
|
||||||
import { updatePeer } from "../olm/peers";
|
import { updatePeer } from "../olm/peers";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
|
import { generateRemoteSubnetsStr } from "@server/lib/ip";
|
||||||
|
|
||||||
const inputSchema = z.object({
|
const inputSchema = z.object({
|
||||||
publicKey: z.string(),
|
publicKey: z.string(),
|
||||||
@@ -188,23 +189,13 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, site.siteId));
|
.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, {
|
await updatePeer(client.clients.clientId, {
|
||||||
siteId: site.siteId,
|
siteId: site.siteId,
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
publicKey: site.publicKey,
|
publicKey: site.publicKey,
|
||||||
serverIP: site.address,
|
serverIP: site.address,
|
||||||
serverPort: site.listenPort,
|
serverPort: site.listenPort,
|
||||||
remoteSubnets: remoteSubnetsStr
|
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -231,46 +222,35 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, siteId));
|
.where(eq(siteResources.siteId, siteId));
|
||||||
|
|
||||||
const { tcpTargets, udpTargets } = allSiteResources.reduce(
|
let targets: {
|
||||||
(acc, resource) => {
|
cidr: string;
|
||||||
// Only process port mode resources
|
portRange?: {
|
||||||
if (resource.mode !== "port") {
|
min: number;
|
||||||
return acc;
|
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`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (siteResource.mode == "cidr") {
|
||||||
// Filter out invalid targets
|
targets.push({
|
||||||
if (
|
cidr: siteResource.destination
|
||||||
!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[] }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the configuration response
|
// Build the configuration response
|
||||||
const configResponse = {
|
const configResponse = {
|
||||||
ipAddress: site.address,
|
ipAddress: site.address,
|
||||||
peers: validPeers,
|
peers: validPeers,
|
||||||
targets: {
|
targets: targets
|
||||||
udp: udpTargets,
|
|
||||||
tcp: tcpTargets
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.debug("Sending config: ", configResponse);
|
logger.debug("Sending config: ", configResponse);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { addPeer, deletePeer } from "../newt/peers";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
|
import { generateRemoteSubnetsStr } from "@server/lib/ip";
|
||||||
|
|
||||||
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||||
logger.info("Handling register olm message!");
|
logger.info("Handling register olm message!");
|
||||||
@@ -238,11 +239,6 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.where(eq(siteResources.siteId, site.siteId));
|
.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
|
// Add the peer to the exit node for this site
|
||||||
if (clientSite.endpoint) {
|
if (clientSite.endpoint) {
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -280,7 +276,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
publicKey: site.publicKey,
|
publicKey: site.publicKey,
|
||||||
serverIP: site.address,
|
serverIP: site.address,
|
||||||
serverPort: site.listenPort,
|
serverPort: site.listenPort,
|
||||||
remoteSubnets: remoteSubnetsStr
|
remoteSubnets: generateRemoteSubnetsStr(allSiteResources)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,38 @@ const createSiteResourceSchema = z
|
|||||||
message:
|
message:
|
||||||
"Protocol, proxy port, and destination port are required for port mode"
|
"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 CreateSiteResourceBody = z.infer<typeof createSiteResourceSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user