Fix tunnels
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user