mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 10:21:34 +00:00
fix: use jump host SOCKS proxy settings (#1116)
This commit is contained in:
@@ -994,7 +994,6 @@ export function registerDockerSshRoutes(app: express.Express): void {
|
||||
const jumpClient = await createJumpHostChain(
|
||||
host.jumpHosts as Array<{ hostId: number }>,
|
||||
userId,
|
||||
proxyConfig,
|
||||
);
|
||||
|
||||
if (!jumpClient) {
|
||||
|
||||
@@ -289,11 +289,7 @@ async function startDedicatedTransferConnect(
|
||||
const hasJumpHosts = jumpHosts && jumpHosts.length > 0;
|
||||
|
||||
if (hasJumpHosts) {
|
||||
const jumpClient = await createJumpHostChain(
|
||||
jumpHosts,
|
||||
userId,
|
||||
proxyConfig,
|
||||
);
|
||||
const jumpClient = await createJumpHostChain(jumpHosts, userId);
|
||||
if (!jumpClient) {
|
||||
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)`,
|
||||
),
|
||||
);
|
||||
const jumpClient = await createJumpHostChain(
|
||||
resolvedJumpHosts,
|
||||
userId,
|
||||
proxyConfig,
|
||||
);
|
||||
const jumpClient = await createJumpHostChain(resolvedJumpHosts, userId);
|
||||
|
||||
if (!jumpClient) {
|
||||
fileLogger.error("Failed to establish jump host chain", {
|
||||
|
||||
@@ -7,14 +7,13 @@ import { PermissionManager } from "../../utils/permission-manager.js";
|
||||
import net from "net";
|
||||
import crypto from "crypto";
|
||||
import path from "path";
|
||||
import type { AuthenticatedRequest, ProxyNode } from "../../../types/index.js";
|
||||
import type { AuthenticatedRequest } from "../../../types/index.js";
|
||||
import {
|
||||
createCurrentHostResolutionRepository,
|
||||
createCurrentSettingsRepository,
|
||||
} from "../../database/repositories/factory.js";
|
||||
import { resolveGuacdOptions } from "../../utils/guacd-config.js";
|
||||
import { createJumpHostChain } from "../jump-host-chain.js";
|
||||
import type { SOCKS5Config } from "../../utils/socks5-helper.js";
|
||||
import { waitForGuacdOpen } from "./guacamole-server.js";
|
||||
import { resolveJumpTunnelEndpoint } from "./jump-tunnel-endpoint.js";
|
||||
|
||||
@@ -499,40 +498,9 @@ router.post(
|
||||
perConnectionGuacdHost || resolveGuacdOptions(guacdUrl).host;
|
||||
const tunnelEndpoint = resolveJumpTunnelEndpoint(guacdHost);
|
||||
|
||||
let socks5ProxyChain: ProxyNode[] = [];
|
||||
if (hostRecord.socks5ProxyChain) {
|
||||
try {
|
||||
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,
|
||||
);
|
||||
// The chain dials the first hop through that hop's own SOCKS5
|
||||
// settings; the target host's proxy config does not apply to it.
|
||||
const jumpClient = await createJumpHostChain(jumpHosts, userId);
|
||||
|
||||
if (!jumpClient) {
|
||||
guacLogger.error(
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Client as SSHClient } from "ssh2";
|
||||
import { createCurrentHostResolutionRepository } from "../database/repositories/factory.js";
|
||||
import { fileLogger } from "../utils/logger.js";
|
||||
import {
|
||||
createSocks5Connection,
|
||||
type SOCKS5Config,
|
||||
} from "../utils/socks5-helper.js";
|
||||
import { createSocks5Connection } from "../utils/socks5-helper.js";
|
||||
import { SSH_ALGORITHMS } from "../utils/ssh-algorithms.js";
|
||||
import { SSHHostKeyVerifier } from "./host-key-verifier.js";
|
||||
import { getJumpHostSocks5Config } from "./jump-host-proxy.js";
|
||||
@@ -106,7 +103,6 @@ async function resolveJumpHost(
|
||||
export async function createJumpHostChain(
|
||||
jumpHosts: Array<{ hostId: number }>,
|
||||
userId: string,
|
||||
socks5Config?: SOCKS5Config | null,
|
||||
): Promise<SSHClient | null> {
|
||||
if (!jumpHosts || jumpHosts.length === 0) {
|
||||
return null;
|
||||
@@ -138,10 +134,7 @@ export async function createJumpHostChain(
|
||||
}
|
||||
}
|
||||
|
||||
const firstHopSocks5Config = getJumpHostSocks5Config(
|
||||
jumpHostConfigs[0],
|
||||
socks5Config,
|
||||
);
|
||||
const firstHopSocks5Config = getJumpHostSocks5Config(jumpHostConfigs[0]);
|
||||
let proxySocket: import("net").Socket | null = null;
|
||||
if (firstHopSocks5Config?.useSocks5) {
|
||||
const firstHop = jumpHostConfigs[0]!;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -29,15 +29,14 @@ function parseProxyChain(value: JumpHostProxyConfig["socks5ProxyChain"]) {
|
||||
|
||||
export function getJumpHostSocks5Config(
|
||||
firstHop: JumpHostProxyConfig | null | undefined,
|
||||
fallbackConfig?: SOCKS5Config | null,
|
||||
): SOCKS5Config | null {
|
||||
if (!firstHop?.useSocks5) {
|
||||
return fallbackConfig ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const socks5ProxyChain = parseProxyChain(firstHop.socks5ProxyChain);
|
||||
if (!firstHop.socks5Host && socks5ProxyChain.length === 0) {
|
||||
return fallbackConfig ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -464,24 +464,9 @@ class PollingManager {
|
||||
|
||||
let isOnline: boolean;
|
||||
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(
|
||||
refreshedHost.jumpHosts,
|
||||
userId,
|
||||
proxyConfig,
|
||||
);
|
||||
isOnline = jumpClient
|
||||
? await tcpPingThroughJumpHost(
|
||||
@@ -1325,11 +1310,7 @@ function createSshFactory(host: SSHHostWithCredentials): () => Promise<Client> {
|
||||
|
||||
let jumpClient: Client | null = null;
|
||||
if (hasJumpHosts) {
|
||||
jumpClient = await createJumpHostChain(
|
||||
host.jumpHosts!,
|
||||
host.userId!,
|
||||
proxyConfig,
|
||||
);
|
||||
jumpClient = await createJumpHostChain(host.jumpHosts!, host.userId!);
|
||||
|
||||
if (!jumpClient) {
|
||||
throw new Error("Failed to establish jump host chain");
|
||||
|
||||
@@ -3000,7 +3000,6 @@ wss.on("connection", async (ws: WebSocket, req) => {
|
||||
const jumpClient = await createJumpHostChain(
|
||||
hostConfig.jumpHosts!,
|
||||
hostConfig.userId!,
|
||||
proxyConfig,
|
||||
);
|
||||
|
||||
if (!jumpClient) {
|
||||
|
||||
@@ -145,11 +145,7 @@ export function connectToHost(host: SSHHost): () => Promise<Client> {
|
||||
|
||||
let jumpClient: Client | null = null;
|
||||
if (host.jumpHosts && host.jumpHosts.length > 0 && host.userId) {
|
||||
jumpClient = await createJumpHostChain(
|
||||
host.jumpHosts,
|
||||
host.userId,
|
||||
proxyConfig,
|
||||
);
|
||||
jumpClient = await createJumpHostChain(host.jumpHosts, host.userId);
|
||||
if (!jumpClient) {
|
||||
throw new Error("Failed to establish jump host chain");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user