feat: emit location rule routes in Caddy config generation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-03-28 11:13:17 +01:00
parent 55f4ba4e80
commit 23e186a22e

View File

@@ -50,7 +50,7 @@ import {
proxyHosts,
l4ProxyHosts
} from "./db/schema";
import { type GeoBlockMode, type WafHostConfig, type MtlsConfig, type RedirectRule, type RewriteConfig } from "./models/proxy-hosts";
import { type GeoBlockMode, type WafHostConfig, type MtlsConfig, type RedirectRule, type RewriteConfig, type LocationRule } from "./models/proxy-hosts";
import { buildClientAuthentication, groupMtlsDomainsByCaSet } from "./caddy-mtls";
import { buildWafHandler, resolveEffectiveWaf } from "./caddy-waf";
@@ -116,6 +116,7 @@ type ProxyHostMeta = {
waf?: WafHostConfig;
redirects?: RedirectRule[];
rewrite?: RewriteConfig;
location_rules?: { path: string; upstreams: string[] }[];
};
type L4Meta = {
@@ -622,6 +623,38 @@ type BuildProxyRoutesOptions = {
globalWaf?: WafSettings | null;
};
function buildLocationReverseProxy(
rule: { path: string; upstreams: string[] },
skipHttpsValidation: boolean,
preserveHostHeader: boolean
): { safePath: string; reverseProxyHandler: Record<string, unknown> } {
const parsedTargets = rule.upstreams.map(parseUpstreamTarget);
const hasHttps = parsedTargets.some((t) => t.scheme === "https");
// Sanitize path to prevent Caddy placeholder injection
const safePath = rule.path.replace(/\{[^}]*\}/g, "");
const reverseProxyHandler: Record<string, unknown> = {
handler: "reverse_proxy",
upstreams: parsedTargets.map((t) => ({ dial: t.dial })),
};
if (preserveHostHeader) {
reverseProxyHandler.headers = {
request: { set: { Host: ["{http.request.host}"] } },
};
}
if (hasHttps) {
reverseProxyHandler.transport = {
protocol: "http",
tls: skipHttpsValidation ? { insecure_skip_verify: true } : {},
};
}
return { safePath, reverseProxyHandler };
}
async function buildProxyRoutes(
rows: ProxyHostRow[],
accessAccounts: Map<number, AccessListEntryRow[]>,
@@ -1009,19 +1042,31 @@ async function buildProxyRoutes(
});
}
const locationRules = meta.location_rules ?? [];
for (const rule of locationRules) {
const { safePath, reverseProxyHandler: locationProxy } = buildLocationReverseProxy(
rule,
Boolean(row.skip_https_hostname_validation),
Boolean(row.preserve_host_header)
);
if (!safePath) continue;
hostRoutes.push({
match: [{ host: domainGroup, path: [safePath] }],
handle: [...handlers, locationProxy],
terminal: true,
});
}
const unprotectedHandlers: Record<string, unknown>[] = [...handlers, reverseProxyHandler];
hostRoutes.push({
match: [
{
host: domainGroup
}
],
match: [{ host: domainGroup }],
handle: unprotectedHandlers,
terminal: true
});
}
} else {
const locationRules = meta.location_rules ?? [];
for (const domainGroup of domainGroups) {
if (outpostRoute) {
const outpostMatches = (outpostRoute.match as Array<Record<string, unknown>> | undefined) ?? [];
@@ -1034,32 +1079,50 @@ async function buildProxyRoutes(
});
}
for (const rule of locationRules) {
const { safePath, reverseProxyHandler: locationProxy } = buildLocationReverseProxy(
rule,
Boolean(row.skip_https_hostname_validation),
Boolean(row.preserve_host_header)
);
if (!safePath) continue;
hostRoutes.push({
match: [{ host: domainGroup, path: [safePath] }],
handle: [...handlers, forwardAuthHandler, locationProxy],
terminal: true,
});
}
const routeHandlers: Record<string, unknown>[] = [...handlers, forwardAuthHandler, reverseProxyHandler];
const route: CaddyHttpRoute = {
match: [
{
host: domainGroup
}
],
match: [{ host: domainGroup }],
handle: routeHandlers,
terminal: true
};
hostRoutes.push(route);
}
}
} else {
const locationRules = meta.location_rules ?? [];
for (const domainGroup of domainGroups) {
for (const rule of locationRules) {
const { safePath, reverseProxyHandler: locationProxy } = buildLocationReverseProxy(
rule,
Boolean(row.skip_https_hostname_validation),
Boolean(row.preserve_host_header)
);
if (!safePath) continue;
hostRoutes.push({
match: [{ host: domainGroup, path: [safePath] }],
handle: [...handlers, locationProxy],
terminal: true,
});
}
const route: CaddyHttpRoute = {
match: [
{
host: domainGroup
}
],
match: [{ host: domainGroup }],
handle: [...handlers, reverseProxyHandler],
terminal: true
terminal: true,
};
hostRoutes.push(route);
}
}