mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-03 17:59:29 +02:00
@@ -121,25 +121,37 @@ export async function applyBlueprint({
|
||||
(hc) => hc.targetId === target.targetId
|
||||
);
|
||||
|
||||
if (["http", "tcp", "udp"].includes(target.mode)) {
|
||||
await addProxyTargets(
|
||||
site.newt.newtId,
|
||||
[target],
|
||||
matchingHealthcheck
|
||||
? [matchingHealthcheck]
|
||||
: [],
|
||||
result.proxyResource.mode === "udp"
|
||||
? "udp"
|
||||
: "tcp",
|
||||
site.newt.version
|
||||
);
|
||||
} else if (
|
||||
["ssh", "rdp", "vnc"].includes(target.mode)
|
||||
) {
|
||||
await sendBrowserGatewayTargets(
|
||||
site.newt.newtId,
|
||||
[target],
|
||||
site.newt.version
|
||||
// The DB writes for all resources have already committed
|
||||
// by this point, so a push failure for one target (e.g.
|
||||
// a newt rejecting a malformed health check) must not
|
||||
// abort pushing the rest, and must not mark the whole
|
||||
// blueprint as failed when the config was actually
|
||||
// persisted successfully.
|
||||
try {
|
||||
if (["http", "tcp", "udp"].includes(target.mode)) {
|
||||
await addProxyTargets(
|
||||
site.newt.newtId,
|
||||
[target],
|
||||
matchingHealthcheck
|
||||
? [matchingHealthcheck]
|
||||
: [],
|
||||
result.proxyResource.mode === "udp"
|
||||
? "udp"
|
||||
: "tcp",
|
||||
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)
|
||||
});
|
||||
|
||||
// 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({
|
||||
hostname: z.string(),
|
||||
hostname: healthCheckHostnameSchema,
|
||||
port: z.int().min(1).max(65535),
|
||||
enabled: z.boolean().optional().default(true),
|
||||
path: z.string().optional().default("/"),
|
||||
|
||||
Reference in New Issue
Block a user