fix: use jump host SOCKS proxy settings (#1116)

This commit is contained in:
ZacharyZcR
2026-07-28 02:21:21 +08:00
committed by GitHub
parent 5c709d38d6
commit 291ccf2549
9 changed files with 65 additions and 85 deletions
-1
View File
@@ -994,7 +994,6 @@ export function registerDockerSshRoutes(app: express.Express): void {
const jumpClient = await createJumpHostChain( const jumpClient = await createJumpHostChain(
host.jumpHosts as Array<{ hostId: number }>, host.jumpHosts as Array<{ hostId: number }>,
userId, userId,
proxyConfig,
); );
if (!jumpClient) { if (!jumpClient) {
+2 -10
View File
@@ -289,11 +289,7 @@ async function startDedicatedTransferConnect(
const hasJumpHosts = jumpHosts && jumpHosts.length > 0; const hasJumpHosts = jumpHosts && jumpHosts.length > 0;
if (hasJumpHosts) { if (hasJumpHosts) {
const jumpClient = await createJumpHostChain( const jumpClient = await createJumpHostChain(jumpHosts, userId);
jumpHosts,
userId,
proxyConfig,
);
if (!jumpClient) { if (!jumpClient) {
throw new Error("Failed to connect through jump hosts for transfer"); throw new Error("Failed to connect through jump hosts for transfer");
} }
@@ -1616,11 +1612,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
`Connecting via ${resolvedJumpHosts.length} jump host(s)`, `Connecting via ${resolvedJumpHosts.length} jump host(s)`,
), ),
); );
const jumpClient = await createJumpHostChain( const jumpClient = await createJumpHostChain(resolvedJumpHosts, userId);
resolvedJumpHosts,
userId,
proxyConfig,
);
if (!jumpClient) { if (!jumpClient) {
fileLogger.error("Failed to establish jump host chain", { fileLogger.error("Failed to establish jump host chain", {
+4 -36
View File
@@ -7,14 +7,13 @@ import { PermissionManager } from "../../utils/permission-manager.js";
import net from "net"; import net from "net";
import crypto from "crypto"; import crypto from "crypto";
import path from "path"; import path from "path";
import type { AuthenticatedRequest, ProxyNode } from "../../../types/index.js"; import type { AuthenticatedRequest } from "../../../types/index.js";
import { import {
createCurrentHostResolutionRepository, createCurrentHostResolutionRepository,
createCurrentSettingsRepository, createCurrentSettingsRepository,
} from "../../database/repositories/factory.js"; } from "../../database/repositories/factory.js";
import { resolveGuacdOptions } from "../../utils/guacd-config.js"; import { resolveGuacdOptions } from "../../utils/guacd-config.js";
import { createJumpHostChain } from "../jump-host-chain.js"; import { createJumpHostChain } from "../jump-host-chain.js";
import type { SOCKS5Config } from "../../utils/socks5-helper.js";
import { waitForGuacdOpen } from "./guacamole-server.js"; import { waitForGuacdOpen } from "./guacamole-server.js";
import { resolveJumpTunnelEndpoint } from "./jump-tunnel-endpoint.js"; import { resolveJumpTunnelEndpoint } from "./jump-tunnel-endpoint.js";
@@ -499,40 +498,9 @@ router.post(
perConnectionGuacdHost || resolveGuacdOptions(guacdUrl).host; perConnectionGuacdHost || resolveGuacdOptions(guacdUrl).host;
const tunnelEndpoint = resolveJumpTunnelEndpoint(guacdHost); const tunnelEndpoint = resolveJumpTunnelEndpoint(guacdHost);
let socks5ProxyChain: ProxyNode[] = []; // The chain dials the first hop through that hop's own SOCKS5
if (hostRecord.socks5ProxyChain) { // settings; the target host's proxy config does not apply to it.
try { const jumpClient = await createJumpHostChain(jumpHosts, userId);
socks5ProxyChain =
typeof hostRecord.socks5ProxyChain === "string"
? JSON.parse(hostRecord.socks5ProxyChain as string)
: (hostRecord.socks5ProxyChain as ProxyNode[]);
} catch {
socks5ProxyChain = [];
}
}
const proxyConfig: SOCKS5Config | null =
hostRecord.useSocks5 &&
(hostRecord.socks5Host || socks5ProxyChain.length > 0)
? {
useSocks5: hostRecord.useSocks5 as boolean,
socks5Host: hostRecord.socks5Host as string | undefined,
socks5Port: hostRecord.socks5Port as number | undefined,
socks5Username: hostRecord.socks5Username as
| string
| undefined,
socks5Password: hostRecord.socks5Password as
| string
| undefined,
socks5ProxyChain,
}
: null;
const jumpClient = await createJumpHostChain(
jumpHosts,
userId,
proxyConfig,
);
if (!jumpClient) { if (!jumpClient) {
guacLogger.error( guacLogger.error(
+2 -9
View File
@@ -1,10 +1,7 @@
import { Client as SSHClient } from "ssh2"; import { Client as SSHClient } from "ssh2";
import { createCurrentHostResolutionRepository } from "../database/repositories/factory.js"; import { createCurrentHostResolutionRepository } from "../database/repositories/factory.js";
import { fileLogger } from "../utils/logger.js"; import { fileLogger } from "../utils/logger.js";
import { import { createSocks5Connection } from "../utils/socks5-helper.js";
createSocks5Connection,
type SOCKS5Config,
} from "../utils/socks5-helper.js";
import { SSH_ALGORITHMS } from "../utils/ssh-algorithms.js"; import { SSH_ALGORITHMS } from "../utils/ssh-algorithms.js";
import { SSHHostKeyVerifier } from "./host-key-verifier.js"; import { SSHHostKeyVerifier } from "./host-key-verifier.js";
import { getJumpHostSocks5Config } from "./jump-host-proxy.js"; import { getJumpHostSocks5Config } from "./jump-host-proxy.js";
@@ -106,7 +103,6 @@ async function resolveJumpHost(
export async function createJumpHostChain( export async function createJumpHostChain(
jumpHosts: Array<{ hostId: number }>, jumpHosts: Array<{ hostId: number }>,
userId: string, userId: string,
socks5Config?: SOCKS5Config | null,
): Promise<SSHClient | null> { ): Promise<SSHClient | null> {
if (!jumpHosts || jumpHosts.length === 0) { if (!jumpHosts || jumpHosts.length === 0) {
return null; return null;
@@ -138,10 +134,7 @@ export async function createJumpHostChain(
} }
} }
const firstHopSocks5Config = getJumpHostSocks5Config( const firstHopSocks5Config = getJumpHostSocks5Config(jumpHostConfigs[0]);
jumpHostConfigs[0],
socks5Config,
);
let proxySocket: import("net").Socket | null = null; let proxySocket: import("net").Socket | null = null;
if (firstHopSocks5Config?.useSocks5) { if (firstHopSocks5Config?.useSocks5) {
const firstHop = jumpHostConfigs[0]!; const firstHop = jumpHostConfigs[0]!;
+53
View File
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { getJumpHostSocks5Config } from "./jump-host-proxy.js";
describe("getJumpHostSocks5Config", () => {
it("uses the first jump host proxy settings", () => {
expect(
getJumpHostSocks5Config({
useSocks5: true,
socks5Host: "proxy.internal",
socks5Port: 1080,
socks5Username: "user",
socks5Password: "secret",
}),
).toEqual({
useSocks5: true,
socks5Host: "proxy.internal",
socks5Port: 1080,
socks5Username: "user",
socks5Password: "secret",
socks5ProxyChain: [],
});
});
it("does not use destination proxy settings for the first jump host", () => {
expect(getJumpHostSocks5Config({ useSocks5: false })).toBeNull();
});
it("accepts a serialized proxy chain from the first jump host", () => {
const chain = [
{
id: "proxy-1",
name: "Proxy 1",
host: "proxy.internal",
port: 1080,
type: "socks5" as const,
},
];
expect(
getJumpHostSocks5Config({
useSocks5: true,
socks5ProxyChain: JSON.stringify(chain),
}),
).toEqual({
useSocks5: true,
socks5Host: undefined,
socks5Port: undefined,
socks5Username: undefined,
socks5Password: undefined,
socks5ProxyChain: chain,
});
});
});
+2 -3
View File
@@ -29,15 +29,14 @@ function parseProxyChain(value: JumpHostProxyConfig["socks5ProxyChain"]) {
export function getJumpHostSocks5Config( export function getJumpHostSocks5Config(
firstHop: JumpHostProxyConfig | null | undefined, firstHop: JumpHostProxyConfig | null | undefined,
fallbackConfig?: SOCKS5Config | null,
): SOCKS5Config | null { ): SOCKS5Config | null {
if (!firstHop?.useSocks5) { if (!firstHop?.useSocks5) {
return fallbackConfig ?? null; return null;
} }
const socks5ProxyChain = parseProxyChain(firstHop.socks5ProxyChain); const socks5ProxyChain = parseProxyChain(firstHop.socks5ProxyChain);
if (!firstHop.socks5Host && socks5ProxyChain.length === 0) { if (!firstHop.socks5Host && socks5ProxyChain.length === 0) {
return fallbackConfig ?? null; return null;
} }
return { return {
+1 -20
View File
@@ -464,24 +464,9 @@ class PollingManager {
let isOnline: boolean; let isOnline: boolean;
if (refreshedHost.jumpHosts && refreshedHost.jumpHosts.length > 0) { if (refreshedHost.jumpHosts && refreshedHost.jumpHosts.length > 0) {
const proxyConfig: SOCKS5Config | null =
refreshedHost.useSocks5 &&
(refreshedHost.socks5Host ||
(refreshedHost.socks5ProxyChain &&
refreshedHost.socks5ProxyChain.length > 0))
? {
useSocks5: true,
socks5Host: refreshedHost.socks5Host,
socks5Port: refreshedHost.socks5Port,
socks5Username: refreshedHost.socks5Username,
socks5Password: refreshedHost.socks5Password,
socks5ProxyChain: refreshedHost.socks5ProxyChain,
}
: null;
const jumpClient = await createJumpHostChain( const jumpClient = await createJumpHostChain(
refreshedHost.jumpHosts, refreshedHost.jumpHosts,
userId, userId,
proxyConfig,
); );
isOnline = jumpClient isOnline = jumpClient
? await tcpPingThroughJumpHost( ? await tcpPingThroughJumpHost(
@@ -1325,11 +1310,7 @@ function createSshFactory(host: SSHHostWithCredentials): () => Promise<Client> {
let jumpClient: Client | null = null; let jumpClient: Client | null = null;
if (hasJumpHosts) { if (hasJumpHosts) {
jumpClient = await createJumpHostChain( jumpClient = await createJumpHostChain(host.jumpHosts!, host.userId!);
host.jumpHosts!,
host.userId!,
proxyConfig,
);
if (!jumpClient) { if (!jumpClient) {
throw new Error("Failed to establish jump host chain"); throw new Error("Failed to establish jump host chain");
-1
View File
@@ -3000,7 +3000,6 @@ wss.on("connection", async (ws: WebSocket, req) => {
const jumpClient = await createJumpHostChain( const jumpClient = await createJumpHostChain(
hostConfig.jumpHosts!, hostConfig.jumpHosts!,
hostConfig.userId!, hostConfig.userId!,
proxyConfig,
); );
if (!jumpClient) { if (!jumpClient) {
+1 -5
View File
@@ -145,11 +145,7 @@ export function connectToHost(host: SSHHost): () => Promise<Client> {
let jumpClient: Client | null = null; let jumpClient: Client | null = null;
if (host.jumpHosts && host.jumpHosts.length > 0 && host.userId) { if (host.jumpHosts && host.jumpHosts.length > 0 && host.userId) {
jumpClient = await createJumpHostChain( jumpClient = await createJumpHostChain(host.jumpHosts, host.userId);
host.jumpHosts,
host.userId,
proxyConfig,
);
if (!jumpClient) { if (!jumpClient) {
throw new Error("Failed to establish jump host chain"); throw new Error("Failed to establish jump host chain");
} }