diff --git a/server/routers/newt/buildConfiguration.ts b/server/routers/newt/buildConfiguration.ts index bd8bfc18..e349f24e 100644 --- a/server/routers/newt/buildConfiguration.ts +++ b/server/routers/newt/buildConfiguration.ts @@ -1,3 +1,10 @@ +import { clients, clientSiteResourcesAssociationsCache, clientSitesAssociationsCache, db, ExitNode, resources, Site, siteResources, targetHealthCheck, targets } from "@server/db"; +import logger from "@server/logger"; +import { initPeerAddHandshake, updatePeer } from "../olm/peers"; +import { eq, and } from "drizzle-orm"; +import config from "@server/lib/config"; +import { generateSubnetProxyTargets, SubnetProxyTarget } from "@server/lib/ip"; + export async function buildClientConfigurationForNewtClient( site: Site, exitNode?: ExitNode diff --git a/server/routers/newt/handleGetConfigMessage.ts b/server/routers/newt/handleGetConfigMessage.ts index 30d55c73..801c8b65 100644 --- a/server/routers/newt/handleGetConfigMessage.ts +++ b/server/routers/newt/handleGetConfigMessage.ts @@ -2,20 +2,10 @@ import { z } from "zod"; import { MessageHandler } from "@server/routers/ws"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; -import { - db, - ExitNode, - exitNodes, - siteResources, - clientSiteResourcesAssociationsCache, - Site -} from "@server/db"; -import { clients, clientSitesAssociationsCache, Newt, sites } from "@server/db"; +import { db, ExitNode, exitNodes, Newt, sites } from "@server/db"; import { eq } from "drizzle-orm"; -import { initPeerAddHandshake, updatePeer } from "../olm/peers"; import { sendToExitNode } from "#dynamic/lib/exitNodes"; -import { generateSubnetProxyTargets, SubnetProxyTarget } from "@server/lib/ip"; -import config from "@server/lib/config"; +import { buildClientConfigurationForNewtClient } from "./buildConfiguration"; const inputSchema = z.object({ publicKey: z.string(), @@ -136,20 +126,13 @@ export const handleGetConfigMessage: MessageHandler = async (context) => { exitNode ); - // Build the configuration response - const configResponse = { - ipAddress: site.address, - peers, - targets - }; - - logger.debug("Sending config: ", configResponse); - return { message: { type: "newt/wg/receive-config", data: { - ...configResponse + ipAddress: site.address, + peers, + targets } }, broadcast: false, diff --git a/server/routers/newt/handleNewtPingMessage.ts b/server/routers/newt/handleNewtPingMessage.ts index 4a0102e0..8840c47e 100644 --- a/server/routers/newt/handleNewtPingMessage.ts +++ b/server/routers/newt/handleNewtPingMessage.ts @@ -1,4 +1,4 @@ -import { db } from "@server/db"; +import { db, sites } from "@server/db"; import { disconnectClient } from "#dynamic/routers/ws"; import { getClientConfigVersion, MessageHandler } from "@server/routers/ws"; import { clients, Newt } from "@server/db"; @@ -9,6 +9,7 @@ import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; import { sendTerminateClient } from "../client/terminate"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; +import { sendNewtSyncMessage } from "./sync"; // Track if the offline checker interval is running // let offlineCheckerInterval: NodeJS.Timeout | null = null; @@ -102,7 +103,26 @@ export const handleNewtPingMessage: MessageHandler = async (context) => { const newt = c as Newt; if (!newt) { - logger.warn("Newt not found"); + logger.warn("Newt ping message: Newt not found"); + return; + } + + if (!newt.siteId) { + logger.warn("Newt ping message: has no site ID"); + return; + } + + // get the site + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, newt.siteId)) + .limit(1); + + if (!site) { + logger.warn( + `Newt ping message: site with ID ${newt.siteId} not found` + ); return; } @@ -114,7 +134,7 @@ export const handleNewtPingMessage: MessageHandler = async (context) => { `Newt ping with outdated config version: ${message.configVersion} (current: ${configVersion})` ); - // TODO: sync the client + await sendNewtSyncMessage(newt, site); } // try { diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 28f6e64a..9ffee919 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -18,6 +18,7 @@ import { } from "#dynamic/lib/exitNodes"; import { fetchContainers } from "./dockerSocket"; import { lockManager } from "#dynamic/lib/lock"; +import { buildTargetConfigurationForNewtClient } from "./buildConfiguration"; export type ExitNodePingResult = { exitNodeId: number; @@ -261,118 +262,6 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { }; }; -export async function buildTargetConfigurationForNewtClient(siteId: number) { - // Get all enabled targets with their resource protocol information - const allTargets = await db - .select({ - resourceId: targets.resourceId, - targetId: targets.targetId, - ip: targets.ip, - method: targets.method, - port: targets.port, - internalPort: targets.internalPort, - enabled: targets.enabled, - protocol: resources.protocol, - hcEnabled: targetHealthCheck.hcEnabled, - hcPath: targetHealthCheck.hcPath, - hcScheme: targetHealthCheck.hcScheme, - hcMode: targetHealthCheck.hcMode, - hcHostname: targetHealthCheck.hcHostname, - hcPort: targetHealthCheck.hcPort, - hcInterval: targetHealthCheck.hcInterval, - hcUnhealthyInterval: targetHealthCheck.hcUnhealthyInterval, - hcTimeout: targetHealthCheck.hcTimeout, - hcHeaders: targetHealthCheck.hcHeaders, - hcMethod: targetHealthCheck.hcMethod, - hcTlsServerName: targetHealthCheck.hcTlsServerName - }) - .from(targets) - .innerJoin(resources, eq(targets.resourceId, resources.resourceId)) - .leftJoin( - targetHealthCheck, - eq(targets.targetId, targetHealthCheck.targetId) - ) - .where(and(eq(targets.siteId, siteId), eq(targets.enabled, true))); - - const { tcpTargets, udpTargets } = allTargets.reduce( - (acc, target) => { - // Filter out invalid targets - if (!target.internalPort || !target.ip || !target.port) { - return acc; - } - - // Format target into string - const formattedTarget = `${target.internalPort}:${target.ip}:${target.port}`; - - // Add to the appropriate protocol array - if (target.protocol === "tcp") { - acc.tcpTargets.push(formattedTarget); - } else { - acc.udpTargets.push(formattedTarget); - } - - return acc; - }, - { tcpTargets: [] as string[], udpTargets: [] as string[] } - ); - - const healthCheckTargets = allTargets.map((target) => { - // make sure the stuff is defined - if ( - !target.hcPath || - !target.hcHostname || - !target.hcPort || - !target.hcInterval || - !target.hcMethod - ) { - logger.debug( - `Skipping target ${target.targetId} due to missing health check fields` - ); - return null; // Skip targets with missing health check fields - } - - // parse headers - const hcHeadersParse = target.hcHeaders - ? JSON.parse(target.hcHeaders) - : null; - const hcHeadersSend: { [key: string]: string } = {}; - if (hcHeadersParse) { - hcHeadersParse.forEach( - (header: { name: string; value: string }) => { - hcHeadersSend[header.name] = header.value; - } - ); - } - - return { - id: target.targetId, - hcEnabled: target.hcEnabled, - hcPath: target.hcPath, - hcScheme: target.hcScheme, - hcMode: target.hcMode, - hcHostname: target.hcHostname, - hcPort: target.hcPort, - hcInterval: target.hcInterval, // in seconds - hcUnhealthyInterval: target.hcUnhealthyInterval, // in seconds - hcTimeout: target.hcTimeout, // in seconds - hcHeaders: hcHeadersSend, - hcMethod: target.hcMethod, - hcTlsServerName: target.hcTlsServerName - }; - }); - - // Filter out any null values from health check targets - const validHealthCheckTargets = healthCheckTargets.filter( - (target) => target !== null - ); - - return { - validHealthCheckTargets, - tcpTargets, - udpTargets - }; -} - async function getUniqueSubnetForSite( exitNode: ExitNode, trx: Transaction | typeof db = db diff --git a/server/routers/newt/sync.ts b/server/routers/newt/sync.ts index 9a7ad21a..e6f465e5 100644 --- a/server/routers/newt/sync.ts +++ b/server/routers/newt/sync.ts @@ -1,22 +1,41 @@ -import { Client, Olm } from "@server/db"; -import { buildSiteConfigurationForOlmClient } from "./buildSiteConfigurationForOlmClient"; +import { ExitNode, exitNodes, Newt, Site, db } from "@server/db"; +import { eq } from "drizzle-orm"; import { sendToClient } from "#dynamic/routers/ws"; import logger from "@server/logger"; +import { + buildClientConfigurationForNewtClient, + buildTargetConfigurationForNewtClient +} from "./buildConfiguration"; -export async function sendOlmSyncMessage(olm: Olm, client: Client) { - // NOTE: WE ARE HARDCODING THE RELAY PARAMETER TO FALSE HERE BUT IN THE REGISTER MESSAGE ITS DEFINED BY THE CLIENT - const siteConfigurations = await buildSiteConfigurationForOlmClient( - client, - client.pubKey, - false +export async function sendNewtSyncMessage(newt: Newt, site: Site) { + const { tcpTargets, udpTargets, validHealthCheckTargets } = + await buildTargetConfigurationForNewtClient(site.siteId); + + let exitNode: ExitNode | undefined; + if (site.exitNodeId) { + [exitNode] = await db + .select() + .from(exitNodes) + .where(eq(exitNodes.exitNodeId, site.exitNodeId)) + .limit(1); + } + const { peers, targets } = await buildClientConfigurationForNewtClient( + site, + exitNode ); - await sendToClient(olm.olmId, { - type: "olm/sync", + await sendToClient(newt.newtId, { + type: "newt/sync", data: { - sites: siteConfigurations + proxyTargets: { + udp: udpTargets, + tcp: tcpTargets + }, + healthCheckTargets: validHealthCheckTargets, + peers: peers, + clientTargets: targets } }).catch((error) => { - logger.warn(`Error sending olm sync message:`, error); + logger.warn(`Error sending newt sync message:`, error); }); } diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index 5c7d0a75..5dfa9520 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -24,7 +24,7 @@ import { validateSessionToken } from "@server/auth/sessions/app"; import config from "@server/lib/config"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; -import { buildSiteConfigurationForOlmClient } from "./buildSiteConfigurationForOlmClient"; +import { buildSiteConfigurationForOlmClient } from "./buildConfiguration"; export const handleOlmRegisterMessage: MessageHandler = async (context) => { logger.info("Handling register olm message!"); diff --git a/server/routers/olm/sync.ts b/server/routers/olm/sync.ts index 9a7ad21a..a7db4d04 100644 --- a/server/routers/olm/sync.ts +++ b/server/routers/olm/sync.ts @@ -1,5 +1,5 @@ import { Client, Olm } from "@server/db"; -import { buildSiteConfigurationForOlmClient } from "./buildSiteConfigurationForOlmClient"; +import { buildSiteConfigurationForOlmClient } from "./buildConfiguration"; import { sendToClient } from "#dynamic/routers/ws"; import logger from "@server/logger";