mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-07 13:08:03 +02:00
Basic target routing
This commit is contained in:
@@ -1,7 +1,15 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { AiProvider, db, exitNodes, sites, targets } from "@server/db";
|
import { AiProvider, db, exitNodes, sites, targets } from "@server/db";
|
||||||
|
import config from "@server/lib/config";
|
||||||
|
import { decrypt } from "@server/lib/crypto";
|
||||||
import { localCache } from "@server/lib/cache";
|
import { localCache } from "@server/lib/cache";
|
||||||
|
import {
|
||||||
|
AiProviderAuthType,
|
||||||
|
applyAiProviderAuthHeaders,
|
||||||
|
applyAiProviderCustomHeaders,
|
||||||
|
authTypeRequiresApiKey
|
||||||
|
} from "@server/lib/aiProviderDefaults";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
|
||||||
@@ -15,6 +23,12 @@ const PROVIDER_TARGETS_TTL_SEC = 7;
|
|||||||
// match gerbil's `pangolinDestHeader` constant.
|
// match gerbil's `pangolinDestHeader` constant.
|
||||||
const PANGOLIN_DEST_HEADER = "p-dest-header";
|
const PANGOLIN_DEST_HEADER = "p-dest-header";
|
||||||
|
|
||||||
|
// Header gerbil reads for the Host header value to send to the destination,
|
||||||
|
// when it should differ from PANGOLIN_DEST_HEADER (the target's configured
|
||||||
|
// ip rather than the WireGuard routing address). Must match gerbil's
|
||||||
|
// `pangolinHostHeader` constant.
|
||||||
|
const PANGOLIN_HOST_HEADER = "p-dest-host-header";
|
||||||
|
|
||||||
const SKIP_HEADERS = new Set([
|
const SKIP_HEADERS = new Set([
|
||||||
"p-host",
|
"p-host",
|
||||||
"host",
|
"host",
|
||||||
@@ -36,6 +50,10 @@ type ResolvedProviderTarget = {
|
|||||||
// gerbil as the destination to proxy the request to over the WireGuard
|
// gerbil as the destination to proxy the request to over the WireGuard
|
||||||
// tunnel.
|
// tunnel.
|
||||||
destination: string;
|
destination: string;
|
||||||
|
// The target's configured ip, passed to gerbil as the Host header to
|
||||||
|
// send to the destination (which may differ from the WireGuard routing
|
||||||
|
// address above, e.g. for vhost-based targets).
|
||||||
|
hostHeader: string;
|
||||||
// The target's site's exit node HTTP API base URL (gerbil's /router/*).
|
// The target's site's exit node HTTP API base URL (gerbil's /router/*).
|
||||||
gerbilBaseUrl: string;
|
gerbilBaseUrl: string;
|
||||||
};
|
};
|
||||||
@@ -46,6 +64,7 @@ async function fetchProviderTargets(
|
|||||||
const rows = await db
|
const rows = await db
|
||||||
.select({
|
.select({
|
||||||
targetId: targets.targetId,
|
targetId: targets.targetId,
|
||||||
|
ip: targets.ip,
|
||||||
internalPort: targets.internalPort,
|
internalPort: targets.internalPort,
|
||||||
port: targets.port,
|
port: targets.port,
|
||||||
method: targets.method,
|
method: targets.method,
|
||||||
@@ -72,6 +91,7 @@ async function fetchProviderTargets(
|
|||||||
resolved.push({
|
resolved.push({
|
||||||
targetId: row.targetId,
|
targetId: row.targetId,
|
||||||
destination: `${scheme}://${host}:${port}`,
|
destination: `${scheme}://${host}:${port}`,
|
||||||
|
hostHeader: row.ip,
|
||||||
gerbilBaseUrl: row.reachableAt
|
gerbilBaseUrl: row.reachableAt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,9 +137,10 @@ function pathFromRequest(req: Request): string {
|
|||||||
* Proxies an AI gateway request to one of a "custom" / "target" routing-mode
|
* Proxies an AI gateway request to one of a "custom" / "target" routing-mode
|
||||||
* provider's site targets, via that site's gerbil sidecar. Gerbil's
|
* provider's site targets, via that site's gerbil sidecar. Gerbil's
|
||||||
* /router/* endpoint forwards the request (untouched body, same path minus
|
* /router/* endpoint forwards the request (untouched body, same path minus
|
||||||
* the /router prefix, and all headers besides PANGOLIN_DEST_HEADER) over the
|
* the /router prefix, and all headers besides PANGOLIN_DEST_HEADER and
|
||||||
* WireGuard tunnel to the destination named in that header. Always writes a
|
* PANGOLIN_HOST_HEADER) over the WireGuard tunnel to the destination named
|
||||||
* response to `res`, including on failure.
|
* in PANGOLIN_DEST_HEADER, sending PANGOLIN_HOST_HEADER as the Host header.
|
||||||
|
* Always writes a response to `res`, including on failure.
|
||||||
*/
|
*/
|
||||||
export async function proxyAiGatewayToSiteTarget(
|
export async function proxyAiGatewayToSiteTarget(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -147,7 +168,30 @@ export async function proxyAiGatewayToSiteTarget(
|
|||||||
}
|
}
|
||||||
headers[key] = Array.isArray(value) ? value.join(", ") : value;
|
headers[key] = Array.isArray(value) ? value.join(", ") : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authType = provider.authType as AiProviderAuthType;
|
||||||
|
let apiKey: string | null = null;
|
||||||
|
if (authTypeRequiresApiKey(authType)) {
|
||||||
|
if (!provider.apiKey) {
|
||||||
|
res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
|
||||||
|
error: {
|
||||||
|
message: "AI provider has no API key configured"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const secret = config.getRawConfig().server.secret!;
|
||||||
|
apiKey = decrypt(provider.apiKey, secret);
|
||||||
|
}
|
||||||
|
applyAiProviderCustomHeaders(
|
||||||
|
headers,
|
||||||
|
provider.headers,
|
||||||
|
config.getRawConfig().server.secret!
|
||||||
|
);
|
||||||
|
applyAiProviderAuthHeaders(headers, authType, apiKey);
|
||||||
|
|
||||||
headers[PANGOLIN_DEST_HEADER] = target.destination;
|
headers[PANGOLIN_DEST_HEADER] = target.destination;
|
||||||
|
headers[PANGOLIN_HOST_HEADER] = target.hostHeader;
|
||||||
|
|
||||||
const body = JSON.stringify(req.body);
|
const body = JSON.stringify(req.body);
|
||||||
|
|
||||||
@@ -155,7 +199,10 @@ export async function proxyAiGatewayToSiteTarget(
|
|||||||
providerId: provider.providerId,
|
providerId: provider.providerId,
|
||||||
targetId: target.targetId,
|
targetId: target.targetId,
|
||||||
destination: target.destination,
|
destination: target.destination,
|
||||||
url: gerbilUrl
|
hostHeader: target.hostHeader,
|
||||||
|
url: gerbilUrl,
|
||||||
|
headers,
|
||||||
|
body: req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
let upstreamRes: globalThis.Response;
|
let upstreamRes: globalThis.Response;
|
||||||
|
|||||||
Reference in New Issue
Block a user