mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-03 17:59:29 +02:00
@@ -2717,6 +2717,7 @@
|
|||||||
"healthScheme": "Method",
|
"healthScheme": "Method",
|
||||||
"healthSelectScheme": "Select Method",
|
"healthSelectScheme": "Select Method",
|
||||||
"healthCheckPortInvalid": "Port must be between 1 and 65535",
|
"healthCheckPortInvalid": "Port must be between 1 and 65535",
|
||||||
|
"healthCheckHostnameInvalid": "Hostname must not contain whitespace",
|
||||||
"healthCheckPath": "Path",
|
"healthCheckPath": "Path",
|
||||||
"healthHostname": "IP / Host",
|
"healthHostname": "IP / Host",
|
||||||
"healthPort": "Port",
|
"healthPort": "Port",
|
||||||
|
|||||||
@@ -121,25 +121,37 @@ export async function applyBlueprint({
|
|||||||
(hc) => hc.targetId === target.targetId
|
(hc) => hc.targetId === target.targetId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (["http", "tcp", "udp"].includes(target.mode)) {
|
// The DB writes for all resources have already committed
|
||||||
await addProxyTargets(
|
// by this point, so a push failure for one target (e.g.
|
||||||
site.newt.newtId,
|
// a newt rejecting a malformed health check) must not
|
||||||
[target],
|
// abort pushing the rest, and must not mark the whole
|
||||||
matchingHealthcheck
|
// blueprint as failed when the config was actually
|
||||||
? [matchingHealthcheck]
|
// persisted successfully.
|
||||||
: [],
|
try {
|
||||||
result.proxyResource.mode === "udp"
|
if (["http", "tcp", "udp"].includes(target.mode)) {
|
||||||
? "udp"
|
await addProxyTargets(
|
||||||
: "tcp",
|
site.newt.newtId,
|
||||||
site.newt.version
|
[target],
|
||||||
);
|
matchingHealthcheck
|
||||||
} else if (
|
? [matchingHealthcheck]
|
||||||
["ssh", "rdp", "vnc"].includes(target.mode)
|
: [],
|
||||||
) {
|
result.proxyResource.mode === "udp"
|
||||||
await sendBrowserGatewayTargets(
|
? "udp"
|
||||||
site.newt.newtId,
|
: "tcp",
|
||||||
[target],
|
site.newt.version
|
||||||
site.newt.version
|
);
|
||||||
|
} else if (
|
||||||
|
["ssh", "rdp", "vnc"].includes(target.mode)
|
||||||
|
) {
|
||||||
|
await sendBrowserGatewayTargets(
|
||||||
|
site.newt.newtId,
|
||||||
|
[target],
|
||||||
|
site.newt.version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to push target ${target.targetId} to newt on site ${site.sites.siteId}. Error: ${e}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,34 @@ export const SiteSchema = z.object({
|
|||||||
"docker-socket-enabled": z.boolean().optional().default(true)
|
"docker-socket-enabled": z.boolean().optional().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// A malformed hostname (e.g. stray whitespace) is silently accepted here but
|
||||||
|
// fails to parse as a URL when newt builds the health check request, which
|
||||||
|
// takes the target out of the routing pool and breaks the resource entirely
|
||||||
|
// (see #3677). Validate eagerly so blueprints reject it up front instead.
|
||||||
|
const healthCheckHostnameSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => !/\s/.test(val), {
|
||||||
|
message: "Hostname must not contain whitespace"
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => {
|
||||||
|
if (z.union([z.ipv4(), z.ipv6()]).safeParse(val).success) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const hostnameRegex =
|
||||||
|
/^(?:[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])?$/;
|
||||||
|
return hostnameRegex.test(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
"Hostname must be a valid IP address or hostname (no spaces or invalid characters)"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const TargetHealthCheckSchema = z.object({
|
export const TargetHealthCheckSchema = z.object({
|
||||||
hostname: z.string(),
|
hostname: healthCheckHostnameSchema,
|
||||||
port: z.int().min(1).max(65535),
|
port: z.int().min(1).max(65535),
|
||||||
enabled: z.boolean().optional().default(true),
|
enabled: z.boolean().optional().default(true),
|
||||||
path: z.string().optional().default("/"),
|
path: z.string().optional().default("/"),
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
|
|||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
hcScheme: z.string().optional(),
|
hcScheme: z.string().optional(),
|
||||||
hcHostname: z.string(),
|
|
||||||
hcPort: z
|
hcPort: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: t("healthCheckPortInvalid") })
|
.min(1, { message: t("healthCheckPortInvalid") })
|
||||||
@@ -184,6 +183,11 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
|
|||||||
{ message: t("healthCheckPortInvalid") }
|
{ message: t("healthCheckPortInvalid") }
|
||||||
),
|
),
|
||||||
hcFollowRedirects: z.boolean(),
|
hcFollowRedirects: z.boolean(),
|
||||||
|
hcHostname: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => !/\s/.test(val), {
|
||||||
|
message: t("healthCheckHostnameInvalid")
|
||||||
|
}),
|
||||||
hcMode: z.string(),
|
hcMode: z.string(),
|
||||||
hcUnhealthyInterval: z.int().positive().min(5),
|
hcUnhealthyInterval: z.int().positive().min(5),
|
||||||
hcTlsServerName: z.string(),
|
hcTlsServerName: z.string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user