Fix tunnels

This commit is contained in:
LukeGus
2025-09-30 23:54:27 -05:00
parent a7a62bad21
commit ccfe866b8f
2 changed files with 126 additions and 113 deletions

View File

@@ -62,41 +62,53 @@ 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
const tunnelConnections = host.tunnelConnections .map((host) => {
? JSON.parse(host.tunnelConnections) const tunnelConnections = host.tunnelConnections
: []; ? JSON.parse(host.tunnelConnections)
: [];
return { const hasAutoStartTunnels = tunnelConnections.some(
id: host.id,
userId: host.userId,
name: host.name || `autostart-${host.id}`,
ip: host.ip,
port: host.port,
username: host.username,
password: host.autostartPassword,
key: host.autostartKey,
keyPassword: host.autostartKeyPassword,
autostartPassword: host.autostartPassword,
autostartKey: host.autostartKey,
autostartKeyPassword: host.autostartKeyPassword,
authType: host.authType,
enableTunnel: true,
tunnelConnections: tunnelConnections.filter(
(tunnel: any) => tunnel.autoStart, (tunnel: any) => tunnel.autoStart,
), );
pin: false,
enableTerminal: false, if (!hasAutoStartTunnels) {
enableFileManager: false, return null;
tags: ["autostart"], }
};
}); return {
id: host.id,
userId: host.userId,
name: host.name || `autostart-${host.id}`,
ip: host.ip,
port: host.port,
username: host.username,
password: host.autostartPassword,
key: host.autostartKey,
keyPassword: host.autostartKeyPassword,
autostartPassword: host.autostartPassword,
autostartKey: host.autostartKey,
autostartKeyPassword: host.autostartKeyPassword,
authType: host.authType,
keyType: host.keyType,
credentialId: host.credentialId,
enableTunnel: true,
tunnelConnections: tunnelConnections.filter(
(tunnel: any) => tunnel.autoStart,
),
pin: !!host.pin,
enableTerminal: !!host.enableTerminal,
enableFileManager: !!host.enableFileManager,
tags: ["autostart"],
};
})
.filter(Boolean);
res.json(result); res.json(result);
} catch (err) { } catch (err) {

View File

@@ -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,35 +492,34 @@ async function connectSSHTunnel(
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) { if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
try { try {
const credentials = await SimpleDBOps.select( const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.sourceUserId);
getDb() if (userDataKey) {
.select() const credentials = await SimpleDBOps.select(
.from(sshCredentials) getDb()
.where( .select()
and( .from(sshCredentials)
eq(sshCredentials.id, tunnelConfig.sourceCredentialId), .where(
eq(sshCredentials.userId, tunnelConfig.sourceUserId), and(
eq(sshCredentials.id, tunnelConfig.sourceCredentialId),
eq(sshCredentials.userId, tunnelConfig.sourceUserId),
),
), ),
), "ssh_credentials",
"ssh_credentials", tunnelConfig.sourceUserId,
tunnelConfig.sourceUserId, );
);
if (credentials.length > 0) { if (credentials.length > 0) {
const credential = credentials[0]; const credential = credentials[0];
resolvedSourceCredentials = { resolvedSourceCredentials = {
password: credential.password, password: credential.password,
sshKey: credential.privateKey || credential.key, sshKey: credential.privateKey || credential.key,
keyPassword: credential.keyPassword, keyPassword: credential.keyPassword,
keyType: credential.keyType, keyType: credential.keyType,
authMethod: credential.authType, authMethod: credential.authType,
}; };
} else {
}
} else { } else {
tunnelLogger.warn("No source credentials found in database", {
operation: "tunnel_connect",
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,31 +569,40 @@ async function connectSSHTunnel(
if (tunnelConfig.endpointCredentialId && tunnelConfig.endpointUserId) { if (tunnelConfig.endpointCredentialId && tunnelConfig.endpointUserId) {
try { try {
const credentials = await SimpleDBOps.select( const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.endpointUserId);
getDb() if (userDataKey) {
.select() const credentials = await SimpleDBOps.select(
.from(sshCredentials) getDb()
.where( .select()
and( .from(sshCredentials)
eq(sshCredentials.id, tunnelConfig.endpointCredentialId), .where(
eq(sshCredentials.userId, tunnelConfig.endpointUserId), and(
eq(sshCredentials.id, tunnelConfig.endpointCredentialId),
eq(sshCredentials.userId, tunnelConfig.endpointUserId),
),
), ),
), "ssh_credentials",
"ssh_credentials", tunnelConfig.endpointUserId,
tunnelConfig.endpointUserId, );
);
if (credentials.length > 0) { if (credentials.length > 0) {
const credential = credentials[0]; const credential = credentials[0];
resolvedEndpointCredentials = { resolvedEndpointCredentials = {
password: credential.password, password: credential.password,
sshKey: credential.privateKey || credential.key, sshKey: credential.privateKey || credential.key,
keyPassword: credential.keyPassword, keyPassword: credential.keyPassword,
keyType: credential.keyType, keyType: credential.keyType,
authMethod: credential.authType, authMethod: credential.authType,
}; };
} else {
tunnelLogger.warn("No endpoint credentials found in database", {
operation: "tunnel_connect",
tunnelName,
credentialId: tunnelConfig.endpointCredentialId,
});
}
} else { } else {
tunnelLogger.warn("No endpoint credentials found in database", { tunnelLogger.warn("User data key not available for endpoint credentials, using tunnel config credentials", {
operation: "tunnel_connect", operation: "tunnel_connect",
tunnelName, tunnelName,
credentialId: tunnelConfig.endpointCredentialId, credentialId: tunnelConfig.endpointCredentialId,
@@ -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,29 +1008,33 @@ async function killRemoteTunnelByMarker(
if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) { if (tunnelConfig.sourceCredentialId && tunnelConfig.sourceUserId) {
try { try {
const credentials = await SimpleDBOps.select( const userDataKey = DataCrypto.getUserDataKey(tunnelConfig.sourceUserId);
getDb() if (userDataKey) {
.select() const credentials = await SimpleDBOps.select(
.from(sshCredentials) getDb()
.where( .select()
and( .from(sshCredentials)
eq(sshCredentials.id, tunnelConfig.sourceCredentialId), .where(
eq(sshCredentials.userId, tunnelConfig.sourceUserId), and(
eq(sshCredentials.id, tunnelConfig.sourceCredentialId),
eq(sshCredentials.userId, tunnelConfig.sourceUserId),
),
), ),
), "ssh_credentials",
"ssh_credentials", tunnelConfig.sourceUserId,
tunnelConfig.sourceUserId, );
);
if (credentials.length > 0) { if (credentials.length > 0) {
const credential = credentials[0]; const credential = credentials[0];
resolvedSourceCredentials = { resolvedSourceCredentials = {
password: credential.password, password: credential.password,
sshKey: credential.privateKey || credential.key, sshKey: credential.privateKey || credential.key,
keyPassword: credential.keyPassword, keyPassword: credential.keyPassword,
keyType: credential.keyType, keyType: credential.keyType,
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", {
@@ -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(