123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import { Request, Response } from "express";
- import db from "@server/db";
- import { and, eq } from "drizzle-orm";
- import logger from "@server/logger";
- import HttpCode from "@server/types/HttpCode";
- import config from "@server/lib/config";
- import { orgs, resources, sites, Target, targets } from "@server/db/schema";
- import { sql } from "drizzle-orm";
- export async function traefikConfigProvider(
- _: Request,
- res: Response
- ): Promise<any> {
- try {
- const allResources = await db
- .select({
- // Resource fields
- resourceId: resources.resourceId,
- subdomain: resources.subdomain,
- fullDomain: resources.fullDomain,
- ssl: resources.ssl,
- blockAccess: resources.blockAccess,
- sso: resources.sso,
- emailWhitelistEnabled: resources.emailWhitelistEnabled,
- http: resources.http,
- proxyPort: resources.proxyPort,
- protocol: resources.protocol,
- isBaseDomain: resources.isBaseDomain,
- // Site fields
- site: {
- siteId: sites.siteId,
- type: sites.type,
- subnet: sites.subnet
- },
- // Org fields
- org: {
- orgId: orgs.orgId,
- domain: orgs.domain
- },
- // Targets as a subquery
- targets: sql<string>`json_group_array(json_object(
- 'targetId', ${targets.targetId},
- 'ip', ${targets.ip},
- 'method', ${targets.method},
- 'port', ${targets.port},
- 'internalPort', ${targets.internalPort},
- 'enabled', ${targets.enabled}
- ))`.as("targets")
- })
- .from(resources)
- .innerJoin(sites, eq(sites.siteId, resources.siteId))
- .innerJoin(orgs, eq(resources.orgId, orgs.orgId))
- .leftJoin(
- targets,
- and(
- eq(targets.resourceId, resources.resourceId),
- eq(targets.enabled, true)
- )
- )
- .groupBy(resources.resourceId);
- if (!allResources.length) {
- return res.status(HttpCode.OK).json({});
- }
- const badgerMiddlewareName = "badger";
- const redirectHttpsMiddlewareName = "redirect-to-https";
- const config_output: any = {
- http: {
- middlewares: {
- [badgerMiddlewareName]: {
- plugin: {
- [badgerMiddlewareName]: {
- apiBaseUrl: new URL(
- "/api/v1",
- `http://${config.getRawConfig().server.internal_hostname}:${
- config.getRawConfig().server
- .internal_port
- }`
- ).href,
- userSessionCookieName:
- config.getRawConfig().server
- .session_cookie_name,
- accessTokenQueryParam:
- config.getRawConfig().server
- .resource_access_token_param,
- resourceSessionRequestParam:
- config.getRawConfig().server
- .resource_session_request_param
- }
- }
- },
- [redirectHttpsMiddlewareName]: {
- redirectScheme: {
- scheme: "https"
- }
- }
- }
- }
- };
- for (const resource of allResources) {
- const targets = JSON.parse(resource.targets);
- const site = resource.site;
- const org = resource.org;
- if (!org.domain) {
- continue;
- }
- const routerName = `${resource.resourceId}-router`;
- const serviceName = `${resource.resourceId}-service`;
- const fullDomain = `${resource.fullDomain}`;
- if (resource.http) {
- // HTTP configuration remains the same
- if (!resource.subdomain && !resource.isBaseDomain) {
- continue;
- }
- // add routers and services empty objects if they don't exist
- if (!config_output.http.routers) {
- config_output.http.routers = {};
- }
- if (!config_output.http.services) {
- config_output.http.services = {};
- }
- const domainParts = fullDomain.split(".");
- let wildCard;
- if (domainParts.length <= 2) {
- wildCard = `*.${domainParts.join(".")}`;
- } else {
- wildCard = `*.${domainParts.slice(1).join(".")}`;
- }
- const tls = {
- certResolver: config.getRawConfig().traefik.cert_resolver,
- ...(config.getRawConfig().traefik.prefer_wildcard_cert
- ? {
- domains: [
- {
- main: wildCard
- }
- ]
- }
- : {})
- };
- logger.debug(config.getRawConfig().traefik.prefer_wildcard_cert)
- const additionalMiddlewares =
- config.getRawConfig().traefik.additional_middlewares || [];
- config_output.http.routers![routerName] = {
- entryPoints: [
- resource.ssl
- ? config.getRawConfig().traefik.https_entrypoint
- : config.getRawConfig().traefik.http_entrypoint
- ],
- middlewares: [
- badgerMiddlewareName,
- ...additionalMiddlewares
- ],
- service: serviceName,
- rule: `Host(\`${fullDomain}\`)`,
- ...(resource.ssl ? { tls } : {})
- };
- if (resource.ssl) {
- config_output.http.routers![routerName + "-redirect"] = {
- entryPoints: [
- config.getRawConfig().traefik.http_entrypoint
- ],
- middlewares: [redirectHttpsMiddlewareName],
- service: serviceName,
- rule: `Host(\`${fullDomain}\`)`
- };
- }
- config_output.http.services![serviceName] = {
- loadBalancer: {
- servers: targets
- .filter((target: Target) => {
- if (!target.enabled) {
- return false;
- }
- if (
- site.type === "local" ||
- site.type === "wireguard"
- ) {
- if (
- !target.ip ||
- !target.port ||
- !target.method
- ) {
- return false;
- }
- } else if (site.type === "newt") {
- if (
- !target.internalPort ||
- !target.method
- ) {
- return false;
- }
- }
- return true;
- })
- .map((target: Target) => {
- if (
- site.type === "local" ||
- site.type === "wireguard"
- ) {
- return {
- url: `${target.method}://${target.ip}:${target.port}`
- };
- } else if (site.type === "newt") {
- const ip = site.subnet.split("/")[0];
- return {
- url: `${target.method}://${ip}:${target.internalPort}`
- };
- }
- })
- }
- };
- } else {
- // Non-HTTP (TCP/UDP) configuration
- const protocol = resource.protocol.toLowerCase();
- const port = resource.proxyPort;
- if (!port) {
- continue;
- }
- if (!config_output[protocol]) {
- config_output[protocol] = {
- routers: {},
- services: {}
- };
- }
- config_output[protocol].routers[routerName] = {
- entryPoints: [`${protocol}-${port}`],
- service: serviceName,
- ...(protocol === "tcp" ? { rule: "HostSNI(`*`)" } : {})
- };
- config_output[protocol].services[serviceName] = {
- loadBalancer: {
- servers: targets
- .filter((target: Target) => {
- if (!target.enabled) {
- return false;
- }
- if (
- site.type === "local" ||
- site.type === "wireguard"
- ) {
- if (!target.ip || !target.port) {
- return false;
- }
- } else if (site.type === "newt") {
- if (!target.internalPort) {
- return false;
- }
- }
- return true;
- })
- .map((target: Target) => {
- if (
- site.type === "local" ||
- site.type === "wireguard"
- ) {
- return {
- address: `${target.ip}:${target.port}`
- };
- } else if (site.type === "newt") {
- const ip = site.subnet.split("/")[0];
- return {
- address: `${ip}:${target.internalPort}`
- };
- }
- })
- }
- };
- }
- }
- return res.status(HttpCode.OK).json(config_output);
- } catch (e) {
- logger.error(`Failed to build Traefik config: ${e}`);
- return res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
- error: "Failed to build Traefik config"
- });
- }
- }
|