Fix tunnels
This commit is contained in:
@@ -62,17 +62,26 @@ router.get("/db/host/internal", async (req: Request, res: Response) => {
|
|||||||
.select()
|
.select()
|
||||||
.from(sshData)
|
.from(sshData)
|
||||||
.where(
|
.where(
|
||||||
or(
|
and(
|
||||||
isNotNull(sshData.autostartPassword),
|
eq(sshData.enableTunnel, true),
|
||||||
isNotNull(sshData.autostartKey),
|
isNotNull(sshData.tunnelConnections),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = autostartHosts.map((host) => {
|
const result = autostartHosts
|
||||||
|
.map((host) => {
|
||||||
const tunnelConnections = host.tunnelConnections
|
const tunnelConnections = host.tunnelConnections
|
||||||
? JSON.parse(host.tunnelConnections)
|
? JSON.parse(host.tunnelConnections)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const hasAutoStartTunnels = tunnelConnections.some(
|
||||||
|
(tunnel: any) => tunnel.autoStart,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAutoStartTunnels) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: host.id,
|
id: host.id,
|
||||||
userId: host.userId,
|
userId: host.userId,
|
||||||
@@ -87,16 +96,19 @@ router.get("/db/host/internal", async (req: Request, res: Response) => {
|
|||||||
autostartKey: host.autostartKey,
|
autostartKey: host.autostartKey,
|
||||||
autostartKeyPassword: host.autostartKeyPassword,
|
autostartKeyPassword: host.autostartKeyPassword,
|
||||||
authType: host.authType,
|
authType: host.authType,
|
||||||
|
keyType: host.keyType,
|
||||||
|
credentialId: host.credentialId,
|
||||||
enableTunnel: true,
|
enableTunnel: true,
|
||||||
tunnelConnections: tunnelConnections.filter(
|
tunnelConnections: tunnelConnections.filter(
|
||||||
(tunnel: any) => tunnel.autoStart,
|
(tunnel: any) => tunnel.autoStart,
|
||||||
),
|
),
|
||||||
pin: false,
|
pin: !!host.pin,
|
||||||
enableTerminal: false,
|
enableTerminal: !!host.enableTerminal,
|
||||||
enableFileManager: false,
|
enableFileManager: !!host.enableFileManager,
|
||||||
tags: ["autostart"],
|
tags: ["autostart"],
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { CONNECTION_STATES } from "../../types/index.js";
|
|||||||
import { tunnelLogger } from "../utils/logger.js";
|
import { tunnelLogger } from "../utils/logger.js";
|
||||||
import { SystemCrypto } from "../utils/system-crypto.js";
|
import { SystemCrypto } from "../utils/system-crypto.js";
|
||||||
import { SimpleDBOps } from "../utils/simple-db-ops.js";
|
import { SimpleDBOps } from "../utils/simple-db-ops.js";
|
||||||
|
import { DataCrypto } from "../utils/data-crypto.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(
|
app.use(
|
||||||
@@ -491,6 +492,8 @@ async function connectSSHTunnel(
|
|||||||
|
|
||||||
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
|
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
|
||||||
try {
|
try {
|
||||||
|
const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.sourceUserId);
|
||||||
|
if (userDataKey) {
|
||||||
const credentials = await SimpleDBOps.select(
|
const credentials = await SimpleDBOps.select(
|
||||||
getDb()
|
getDb()
|
||||||
.select()
|
.select()
|
||||||
@@ -515,11 +518,8 @@ async function connectSSHTunnel(
|
|||||||
authMethod: credential.authType,
|
authMethod: credential.authType,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
tunnelLogger.warn("No source credentials found in database", {
|
}
|
||||||
operation: "tunnel_connect",
|
} else {
|
||||||
tunnelName,
|
|
||||||
credentialId: tunnelConfig.sourceCredentialId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tunnelLogger.warn("Failed to resolve source credentials from database", {
|
tunnelLogger.warn("Failed to resolve source credentials from database", {
|
||||||
@@ -569,6 +569,8 @@ async function connectSSHTunnel(
|
|||||||
|
|
||||||
if (tunnelConfig.endpointCredentialId && tunnelConfig.endpointUserId) {
|
if (tunnelConfig.endpointCredentialId && tunnelConfig.endpointUserId) {
|
||||||
try {
|
try {
|
||||||
|
const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.endpointUserId);
|
||||||
|
if (userDataKey) {
|
||||||
const credentials = await SimpleDBOps.select(
|
const credentials = await SimpleDBOps.select(
|
||||||
getDb()
|
getDb()
|
||||||
.select()
|
.select()
|
||||||
@@ -599,6 +601,13 @@ async function connectSSHTunnel(
|
|||||||
credentialId: tunnelConfig.endpointCredentialId,
|
credentialId: tunnelConfig.endpointCredentialId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tunnelLogger.warn("User data key not available for endpoint credentials, using tunnel config credentials", {
|
||||||
|
operation: "tunnel_connect",
|
||||||
|
tunnelName,
|
||||||
|
credentialId: tunnelConfig.endpointCredentialId,
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tunnelLogger.warn(
|
tunnelLogger.warn(
|
||||||
`Failed to resolve endpoint credentials for tunnel ${tunnelName}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
`Failed to resolve endpoint credentials for tunnel ${tunnelName}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||||
@@ -710,9 +719,9 @@ async function connectSSHTunnel(
|
|||||||
resolvedEndpointCredentials.sshKey
|
resolvedEndpointCredentials.sshKey
|
||||||
) {
|
) {
|
||||||
const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
const keyFilePath = `/tmp/tunnel_key_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
||||||
tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && exec -a "${tunnelMarker}" ssh -i ${keyFilePath} -v -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} && rm -f ${keyFilePath}`;
|
tunnelCmd = `echo '${resolvedEndpointCredentials.sshKey}' > ${keyFilePath} && chmod 600 ${keyFilePath} && exec -a "${tunnelMarker}" ssh -i ${keyFilePath} -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP} && rm -f ${keyFilePath}`;
|
||||||
} else {
|
} else {
|
||||||
tunnelCmd = `exec -a "${tunnelMarker}" sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -v -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}`;
|
tunnelCmd = `exec -a "${tunnelMarker}" sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.exec(tunnelCmd, (err, stream) => {
|
conn.exec(tunnelCmd, (err, stream) => {
|
||||||
@@ -999,6 +1008,8 @@ async function killRemoteTunnelByMarker(
|
|||||||
|
|
||||||
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
|
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
|
||||||
try {
|
try {
|
||||||
|
const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.sourceUserId);
|
||||||
|
if (userDataKey) {
|
||||||
const credentials = await SimpleDBOps.select(
|
const credentials = await SimpleDBOps.select(
|
||||||
getDb()
|
getDb()
|
||||||
.select()
|
.select()
|
||||||
@@ -1023,6 +1034,8 @@ async function killRemoteTunnelByMarker(
|
|||||||
authMethod: credential.authType,
|
authMethod: credential.authType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tunnelLogger.warn("Failed to resolve source credentials for cleanup", {
|
tunnelLogger.warn("Failed to resolve source credentials for cleanup", {
|
||||||
tunnelName,
|
tunnelName,
|
||||||
@@ -1418,18 +1431,6 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
|||||||
const hasEndpointKey =
|
const hasEndpointKey =
|
||||||
tunnelConnection.endpointKey || endpointHost.autostartKey;
|
tunnelConnection.endpointKey || endpointHost.autostartKey;
|
||||||
|
|
||||||
if (!hasSourcePassword && !hasSourceKey) {
|
|
||||||
tunnelLogger.warn(
|
|
||||||
`Tunnel '${tunnelConfig.name}' may fail: source host '${host.name || `${host.username}@${host.ip}`}' has no plaintext credentials. Enable autostart for this host to use unattended tunneling.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasEndpointPassword && !hasEndpointKey) {
|
|
||||||
tunnelLogger.warn(
|
|
||||||
`Tunnel '${tunnelConfig.name}' may fail: endpoint host '${endpointHost.name || `${endpointHost.username}@${endpointHost.ip}`}' has no plaintext credentials. Consider enabling autostart for this host or configuring credentials in tunnel connection.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
autoStartTunnels.push(tunnelConfig);
|
autoStartTunnels.push(tunnelConfig);
|
||||||
} else {
|
} else {
|
||||||
tunnelLogger.error(
|
tunnelLogger.error(
|
||||||
|
|||||||
Reference in New Issue
Block a user