94 lines
3.3 KiB
JavaScript
94 lines
3.3 KiB
JavaScript
// ---------------------------------------------------------------------------
|
|
// Outbound proxy support for the container backend scripts.
|
|
//
|
|
// Node's core http/https modules ignore proxy env vars, so requests that
|
|
// should go through a forward proxy are tunneled manually via HTTP CONNECT.
|
|
// Currently used for GitLab access (see GITLAB_PROXY below).
|
|
//
|
|
// Configure in .env / docker-compose.yml (no rebuild needed, container
|
|
// restart is enough):
|
|
// GITLAB_PROXY=http://127.0.0.1:8118 (updater run on the host)
|
|
// GITLAB_PROXY=http://host.docker.internal:8118 (proxy on the docker host)
|
|
// GITLAB_PROXY=http://10.0.0.5:8118 (proxy moved elsewhere)
|
|
// Empty/unset disables proxying (direct connection).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const http = require("http");
|
|
const tls = require("tls");
|
|
const { URL } = require("url");
|
|
|
|
const GITLAB_PROXY = process.env.GITLAB_PROXY || "";
|
|
const GITLAB_HOST = (() => {
|
|
try {
|
|
return new URL(process.env.GITLAB_URL || "https://gitlab.ixsol.wien").hostname;
|
|
} catch {
|
|
return "gitlab.ixsol.wien";
|
|
}
|
|
})();
|
|
|
|
// Returns the proxy URL to use for a given target URL, or null for direct.
|
|
function proxyFor(url) {
|
|
if (!GITLAB_PROXY) return null;
|
|
try {
|
|
if (new URL(url).hostname === GITLAB_HOST) return GITLAB_PROXY;
|
|
} catch {
|
|
// unparseable URL — no proxy
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Opens a CONNECT tunnel through the proxy and resolves to a socket usable
|
|
// as the connection for a request to targetUrl: a TLS socket for https
|
|
// targets, the raw tunnel socket otherwise.
|
|
function connectThroughProxy(proxyUrl, targetUrl, timeoutMs, tlsOptions = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const target = new URL(targetUrl);
|
|
const proxy = new URL(proxyUrl);
|
|
const port = target.port || (target.protocol === "https:" ? "443" : "80");
|
|
const req = http.request({
|
|
host: proxy.hostname,
|
|
port: proxy.port || 80,
|
|
method: "CONNECT",
|
|
path: `${target.hostname}:${port}`,
|
|
headers: { Host: `${target.hostname}:${port}` },
|
|
timeout: timeoutMs || 10000,
|
|
});
|
|
req.on("connect", (res, socket) => {
|
|
if (res.statusCode !== 200) {
|
|
socket.destroy();
|
|
reject(new Error(`Proxy CONNECT to ${target.hostname} failed: HTTP ${res.statusCode}`));
|
|
return;
|
|
}
|
|
resolve(
|
|
target.protocol === "https:"
|
|
? tls.connect({ socket, servername: target.hostname, ...tlsOptions })
|
|
: socket
|
|
);
|
|
});
|
|
req.on("timeout", () => {
|
|
req.destroy();
|
|
reject(new Error(`Proxy CONNECT to ${target.hostname} timed out`));
|
|
});
|
|
req.on("error", reject);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
// Request options for client.request() that route the request through the
|
|
// proxy: https via the CONNECT tunnel (caller passes the socket returned by
|
|
// connectThroughProxy), plain http via an absolute-form request URI.
|
|
function proxiedRequestOptions(proxyUrl, targetUrl, socket) {
|
|
const u = new URL(targetUrl);
|
|
if (u.protocol === "https:") {
|
|
return { agent: false, createConnection: () => socket };
|
|
}
|
|
const p = new URL(proxyUrl);
|
|
return {
|
|
host: p.hostname,
|
|
port: p.port || 80,
|
|
path: u.href,
|
|
headers: { Host: u.host },
|
|
};
|
|
}
|
|
|
|
module.exports = { proxyFor, connectThroughProxy, proxiedRequestOptions }; |