mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-19 17:19:58 +02:00
🚧 wip: traefik config for redirect
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
domains,
|
||||
exitNodes,
|
||||
loginPage,
|
||||
redirects,
|
||||
SiteResource,
|
||||
targetHealthCheck
|
||||
} from "@server/db";
|
||||
@@ -84,6 +85,10 @@ import {
|
||||
buildBrowserGatewayConfig
|
||||
} from "@server/lib/traefik/browserGateway";
|
||||
import { buildSiteResourceAliasCertPlaceholders } from "@server/lib/traefik/siteResourceAlias";
|
||||
import {
|
||||
buildRedirectConfig,
|
||||
RedirectRouteRow
|
||||
} from "@server/lib/traefik/redirect";
|
||||
|
||||
const redirectHttpsMiddlewareName = "redirect-to-https";
|
||||
const redirectToRootMiddlewareName = "redirect-to-root";
|
||||
@@ -395,6 +400,81 @@ export async function getTraefikConfig(
|
||||
)
|
||||
);
|
||||
|
||||
// Redirects have no targets/sites, so like inference resources they are
|
||||
// queried separately and emitted on every exit node. A redirect listens
|
||||
// either on a resource's fullDomain or on subdomain.baseDomain of a
|
||||
// domain; the domain join resolves to whichever one applies.
|
||||
const redirectRows = await db
|
||||
.select({
|
||||
redirectId: redirects.redirectId,
|
||||
subdomain: redirects.subdomain,
|
||||
matchPath: redirects.matchPath,
|
||||
pathMatchType: redirects.pathMatchType,
|
||||
priority: redirects.priority,
|
||||
// Resource (when attached to one)
|
||||
resourceId: resources.resourceId,
|
||||
resourceFullDomain: resources.fullDomain,
|
||||
resourceSubdomain: resources.subdomain,
|
||||
resourceSsl: resources.ssl,
|
||||
resourceWildcard: resources.wildcard,
|
||||
// Domain (the redirect's own, or the resource's)
|
||||
baseDomain: domains.baseDomain,
|
||||
domainCertResolver: domains.certResolver,
|
||||
preferWildcardCert: domains.preferWildcardCert,
|
||||
domainNamespaceId: domainNamespaces.domainNamespaceId
|
||||
})
|
||||
.from(redirects)
|
||||
.leftJoin(resources, eq(resources.resourceId, redirects.resourceId))
|
||||
.leftJoin(
|
||||
domains,
|
||||
eq(
|
||||
domains.domainId,
|
||||
sql`coalesce(${redirects.domainId}, ${resources.domainId})`
|
||||
)
|
||||
)
|
||||
.leftJoin(domainNamespaces, eq(domainNamespaces.domainId, domains.domainId))
|
||||
.where(
|
||||
and(
|
||||
eq(redirects.enabled, true),
|
||||
or(isNull(redirects.resourceId), eq(resources.enabled, true))
|
||||
)
|
||||
)
|
||||
.orderBy(desc(redirects.priority), redirects.redirectId); // stable ordering
|
||||
|
||||
const redirectRoutes: RedirectRouteRow[] = [];
|
||||
for (const row of redirectRows) {
|
||||
if (filterOutNamespaceDomains && row.domainNamespaceId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const attachedToResource = row.resourceId !== null;
|
||||
const fullDomain = attachedToResource
|
||||
? row.resourceFullDomain
|
||||
: [row.subdomain, row.baseDomain].filter(Boolean).join(".");
|
||||
if (!fullDomain) {
|
||||
logger.debug(
|
||||
`Redirect ${row.redirectId} has no host to listen on, skipping Traefik config`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
redirectRoutes.push({
|
||||
redirectId: row.redirectId,
|
||||
fullDomain,
|
||||
hasSubdomain: attachedToResource
|
||||
? !!row.resourceSubdomain
|
||||
: !!row.subdomain,
|
||||
wildcard: row.resourceWildcard,
|
||||
// Domain-attached redirects always get a certificate on creation
|
||||
ssl: attachedToResource ? !!row.resourceSsl : true,
|
||||
matchPath: row.matchPath,
|
||||
pathMatchType: row.pathMatchType,
|
||||
priority: row.priority,
|
||||
domainCertResolver: row.domainCertResolver,
|
||||
preferWildcardCert: row.preferWildcardCert
|
||||
});
|
||||
}
|
||||
|
||||
let validCerts: CertificateResult[] = [];
|
||||
if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
|
||||
// create a list of all domains to get certs for
|
||||
@@ -427,6 +507,12 @@ export async function getTraefikConfig(
|
||||
domains.add(sr.fullDomain);
|
||||
}
|
||||
}
|
||||
// Include redirect hosts
|
||||
for (const redirect of redirectRoutes) {
|
||||
if (redirect.ssl) {
|
||||
domains.add(redirect.fullDomain);
|
||||
}
|
||||
}
|
||||
// get the valid certs for these domains
|
||||
validCerts = await getValidCertificatesForDomains(domains, true); // we are caching here because this is called often
|
||||
// logger.debug(`Valid certs for domains: ${JSON.stringify(validCerts)}`);
|
||||
@@ -779,6 +865,34 @@ export async function getTraefikConfig(
|
||||
}
|
||||
}
|
||||
|
||||
buildRedirectConfig({
|
||||
config_output,
|
||||
redirects: redirectRoutes,
|
||||
badgerMiddlewareName,
|
||||
redirectHttpsMiddlewareName,
|
||||
resolveTls: (redirect) => {
|
||||
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
|
||||
return buildWildcardTls({
|
||||
fullDomain: redirect.fullDomain,
|
||||
hasSubdomain: redirect.hasSubdomain,
|
||||
domainCertResolver: redirect.domainCertResolver,
|
||||
preferWildcardCert:
|
||||
redirect.preferWildcardCert || redirect.wildcard
|
||||
});
|
||||
}
|
||||
const matchingCert = validCerts.find(
|
||||
(cert) => cert.queriedDomain === redirect.fullDomain
|
||||
);
|
||||
if (!matchingCert) {
|
||||
logger.debug(
|
||||
`No matching certificate found for redirect domain: ${redirect.fullDomain}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
if (browserGatewayUiUrl) {
|
||||
buildBrowserGatewayConfig({
|
||||
config_output,
|
||||
|
||||
Reference in New Issue
Block a user