From f33be1434b689c9125746d1ad7de133a3b900afa Mon Sep 17 00:00:00 2001 From: Varun Narravula Date: Wed, 3 Dec 2025 15:16:43 -0800 Subject: [PATCH 1/2] feat(schema): add TLS server name column to target healthcheck tables --- server/db/pg/schema/schema.ts | 3 ++- server/db/sqlite/schema/schema.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index ffbe820c..627c094b 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -175,7 +175,8 @@ export const targetHealthCheck = pgTable("targetHealthCheck", { hcFollowRedirects: boolean("hcFollowRedirects").default(true), hcMethod: varchar("hcMethod").default("GET"), hcStatus: integer("hcStatus"), // http code - hcHealth: text("hcHealth").default("unknown") // "unknown", "healthy", "unhealthy" + hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy" + hcTlsServerName: text("hcTlsServerName"), }); export const exitNodes = pgTable("exitNodes", { diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 13453d2e..e2c0c3b6 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -195,7 +195,8 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", { }).default(true), hcMethod: text("hcMethod").default("GET"), hcStatus: integer("hcStatus"), // http code - hcHealth: text("hcHealth").default("unknown") // "unknown", "healthy", "unhealthy" + hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy" + hcTlsServerName: text("hcTlsServerName"), }); export const exitNodes = sqliteTable("exitNodes", { From ca9273c9eaded167d52f3c8f70bd968a6b015842 Mon Sep 17 00:00:00 2001 From: Varun Narravula Date: Wed, 3 Dec 2025 15:40:55 -0800 Subject: [PATCH 2/2] feat(healthcheck): add SNI input field to target healthcheck config --- messages/en-US.json | 2 + .../routers/newt/handleNewtRegisterMessage.ts | 6 ++- server/routers/newt/targets.ts | 3 +- server/routers/target/createTarget.ts | 4 +- server/routers/target/listTargets.ts | 1 + server/routers/target/updateTarget.ts | 4 +- .../resources/[niceId]/proxy/page.tsx | 11 ++++-- .../settings/resources/create/page.tsx | 12 ++++-- src/components/HealthCheckDialog.tsx | 38 ++++++++++++++++++- 9 files changed, 68 insertions(+), 13 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 4ddb1f7d..f0494f0a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -526,6 +526,8 @@ "targetCreatedDescription": "Target has been created successfully", "targetErrorCreate": "Failed to create target", "targetErrorCreateDescription": "An error occurred while creating the target", + "tlsServerName": "TLS Server Name", + "tlsServerNameDescription": "The TLS server name to use for SNI", "save": "Save", "proxyAdditional": "Additional Proxy Settings", "proxyAdditionalDescription": "Configure how your resource handles proxy settings", diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 372f3677..813e3ef5 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -272,7 +272,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { hcUnhealthyInterval: targetHealthCheck.hcUnhealthyInterval, hcTimeout: targetHealthCheck.hcTimeout, hcHeaders: targetHealthCheck.hcHeaders, - hcMethod: targetHealthCheck.hcMethod + hcMethod: targetHealthCheck.hcMethod, + hcTlsServerName: targetHealthCheck.hcTlsServerName, }) .from(targets) .innerJoin(resources, eq(targets.resourceId, resources.resourceId)) @@ -344,7 +345,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { hcUnhealthyInterval: target.hcUnhealthyInterval, // in seconds hcTimeout: target.hcTimeout, // in seconds hcHeaders: hcHeadersSend, - hcMethod: target.hcMethod + hcMethod: target.hcMethod, + hcTlsServerName: target.hcTlsServerName, }; }); diff --git a/server/routers/newt/targets.ts b/server/routers/newt/targets.ts index 97e4030d..32145fcb 100644 --- a/server/routers/newt/targets.ts +++ b/server/routers/newt/targets.ts @@ -66,7 +66,8 @@ export async function addTargets( hcUnhealthyInterval: hc.hcUnhealthyInterval, // in seconds hcTimeout: hc.hcTimeout, // in seconds hcHeaders: hcHeadersSend, - hcMethod: hc.hcMethod + hcMethod: hc.hcMethod, + hcTlsServerName: hc.hcTlsServerName, }; }); diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 6cf29da3..2c09b5a6 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -48,6 +48,7 @@ const createTargetSchema = z.strictObject({ hcFollowRedirects: z.boolean().optional().nullable(), hcMethod: z.string().min(1).optional().nullable(), hcStatus: z.int().optional().nullable(), + hcTlsServerName: z.string().optional().nullable(), path: z.string().optional().nullable(), pathMatchType: z .enum(["exact", "prefix", "regex"]) @@ -247,7 +248,8 @@ export async function createTarget( hcFollowRedirects: targetData.hcFollowRedirects ?? null, hcMethod: targetData.hcMethod ?? null, hcStatus: targetData.hcStatus ?? null, - hcHealth: "unknown" + hcHealth: "unknown", + hcTlsServerName: targetData.hcTlsServerName ?? null }) .returning(); diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index e97d577d..356276cb 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -57,6 +57,7 @@ function queryTargets(resourceId: number) { hcMethod: targetHealthCheck.hcMethod, hcStatus: targetHealthCheck.hcStatus, hcHealth: targetHealthCheck.hcHealth, + hcTlsServerName: targetHealthCheck.hcTlsServerName, path: targets.path, pathMatchType: targets.pathMatchType, rewritePath: targets.rewritePath, diff --git a/server/routers/target/updateTarget.ts b/server/routers/target/updateTarget.ts index 1889154c..4a60e6cf 100644 --- a/server/routers/target/updateTarget.ts +++ b/server/routers/target/updateTarget.ts @@ -42,6 +42,7 @@ const updateTargetBodySchema = z.strictObject({ hcFollowRedirects: z.boolean().optional().nullable(), hcMethod: z.string().min(1).optional().nullable(), hcStatus: z.int().optional().nullable(), + hcTlsServerName: z.string().optional().nullable(), path: z.string().optional().nullable(), pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(), rewritePath: z.string().optional().nullable(), @@ -217,7 +218,8 @@ export async function updateTarget( hcHeaders: hcHeaders, hcFollowRedirects: parsedBody.data.hcFollowRedirects, hcMethod: parsedBody.data.hcMethod, - hcStatus: parsedBody.data.hcStatus + hcStatus: parsedBody.data.hcStatus, + hcTlsServerName: parsedBody.data.hcTlsServerName, }) .where(eq(targetHealthCheck.targetId, targetId)) .returning(); diff --git a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx index f7a5a559..c7faed5f 100644 --- a/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[niceId]/proxy/page.tsx @@ -464,6 +464,7 @@ export default function ReverseProxyTargets(props: { hcStatus: null, hcMode: null, hcUnhealthyInterval: null, + hcTlsServerName: null, siteType: sites.length > 0 ? sites[0].type : null, new: true, updated: false @@ -629,7 +630,8 @@ export default function ReverseProxyTargets(props: { hcHealth: "unknown", hcStatus: null, hcMode: null, - hcUnhealthyInterval: null + hcUnhealthyInterval: null, + hcTlsServerName: null, }; setTargets([...targets, newTarget]); @@ -729,7 +731,8 @@ export default function ReverseProxyTargets(props: { hcMethod: target.hcMethod || null, hcStatus: target.hcStatus || null, hcUnhealthyInterval: target.hcUnhealthyInterval || null, - hcMode: target.hcMode || null + hcMode: target.hcMode || null, + hcTlsServerName: target.hcTlsServerName, }; // Only include path-related fields for HTTP resources @@ -1822,7 +1825,9 @@ export default function ReverseProxyTargets(props: { hcMode: selectedTargetForHealthCheck.hcMode || "http", hcUnhealthyInterval: selectedTargetForHealthCheck.hcUnhealthyInterval || - 30 + 30, + hcTlsServerName: selectedTargetForHealthCheck.hcTlsServerName || + undefined, }} onChanges={async (config) => { console.log("here"); diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index c3655239..2cbeaf34 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -297,6 +297,7 @@ export default function Page() { hcStatus: null, hcMode: null, hcUnhealthyInterval: null, + hcTlsServerName: null, siteType: sites.length > 0 ? sites[0].type : null, new: true, updated: false @@ -454,7 +455,8 @@ export default function Page() { hcHealth: "unknown", hcStatus: null, hcMode: null, - hcUnhealthyInterval: null + hcUnhealthyInterval: null, + hcTlsServerName: null }; setTargets([...targets, newTarget]); @@ -576,7 +578,8 @@ export default function Page() { target.hcFollowRedirects || null, hcStatus: target.hcStatus || null, hcUnhealthyInterval: target.hcUnhealthyInterval || null, - hcMode: target.hcMode || null + hcMode: target.hcMode || null, + hcTlsServerName: target.hcTlsServerName }; // Only include path-related fields for HTTP resources @@ -1800,7 +1803,10 @@ export default function Page() { "http", hcUnhealthyInterval: selectedTargetForHealthCheck.hcUnhealthyInterval || - 30 + 30, + hcTlsServerName: + selectedTargetForHealthCheck.hcTlsServerName || + undefined }} onChanges={async (config) => { if (selectedTargetForHealthCheck) { diff --git a/src/components/HealthCheckDialog.tsx b/src/components/HealthCheckDialog.tsx index be5e5d45..2784aa23 100644 --- a/src/components/HealthCheckDialog.tsx +++ b/src/components/HealthCheckDialog.tsx @@ -51,6 +51,7 @@ type HealthCheckConfig = { hcFollowRedirects: boolean; hcMode: string; hcUnhealthyInterval: number; + hcTlsServerName: string; }; type HealthCheckDialogProps = { @@ -93,7 +94,8 @@ export default function HealthCheckDialog({ hcPort: z.number().positive().gt(0).lte(65535), hcFollowRedirects: z.boolean(), hcMode: z.string(), - hcUnhealthyInterval: z.int().positive().min(5) + hcUnhealthyInterval: z.int().positive().min(5), + hcTlsServerName: z.string() }); const form = useForm>({ @@ -129,7 +131,8 @@ export default function HealthCheckDialog({ hcPort: initialConfig?.hcPort, hcFollowRedirects: initialConfig?.hcFollowRedirects, hcMode: initialConfig?.hcMode, - hcUnhealthyInterval: initialConfig?.hcUnhealthyInterval + hcUnhealthyInterval: initialConfig?.hcUnhealthyInterval, + hcTlsServerName: initialConfig?.hcTlsServerName ?? "" }); }, [open]); @@ -531,6 +534,37 @@ export default function HealthCheckDialog({ )} /> + {/*TLS Server Name (SNI)*/} + ( + + + {t("tlsServerName")} + + + { + field.onChange(e); + handleFieldChange( + "hcTlsServerName", + e.target.value + ); + }} + /> + + + {t( + "tlsServerNameDescription" + )} + + + + )} + /> + {/* Custom Headers */}