diff --git a/backend/src/Environment.ts b/backend/src/Environment.ts index 627a4ff..3756d8c 100644 --- a/backend/src/Environment.ts +++ b/backend/src/Environment.ts @@ -1,4 +1,4 @@ -import SSHConsole, { Console } from "./consoles/SSHConsole"; +import SSHConsole, { Console, JumpHost } from "./consoles/SSHConsole"; import FileHandler from "./filehandler/SSHFileHandler"; import { InstanceProvider, @@ -461,6 +461,11 @@ export default class Environment { return endpoint.IPAddress; } + async getJumphost(): Promise { + const endpoint = await this.makeSureInstanceExists(); + return endpoint.SSHJumpHost; + } + async makeSureInstanceExists(createIfMissing?: boolean): Promise { return new Promise((resolve, reject) => { this.persister diff --git a/backend/src/websocket/LanguageServerHandler.ts b/backend/src/websocket/LanguageServerHandler.ts index 87b9426..bd00ee1 100644 --- a/backend/src/websocket/LanguageServerHandler.ts +++ b/backend/src/websocket/LanguageServerHandler.ts @@ -1,5 +1,8 @@ import WebSocket from "ws"; import Environment from "../Environment"; +import { Client } from "ssh2"; +import { AddressInfo, createServer, Server } from "net"; +import fs from "fs"; export default function ( wsFromBrowser: WebSocket, @@ -12,9 +15,78 @@ export default function ( Promise.all([ envInstance.getLanguageServerPort(), envInstance.getIPAddress(), + envInstance.getJumphost(), ]) .then((result) => { - const [port, ipAddress] = result; + let [port, ipAddress] = result; + const jumpHost = result[2]; + if (jumpHost !== undefined) { + console.log( + "Establishing SSH lsp connection " + + ipAddress + + ":" + + port + + " via jump host " + + jumpHost.ipaddress + + ":" + + jumpHost.port, + ); + const sshJumpHostConnection = new Client(); + let srv: Server; + sshJumpHostConnection + .on("ready", () => { + srv = createServer((socket) => { + sshJumpHostConnection.forwardOut( + "127.0.0.1", + 0, + ipAddress, + port, + (err, stream) => { + if (err) { + console.log( + "Unable to forward lsp connection on jump host: " + err.message, + ); + sshJumpHostConnection.end(); + socket.end(); + srv.close(); + } else { + socket.pipe(stream); + } + }, + ); + }); + srv.listen(0, () => { + const srvIpAddress = (srv.address() as AddressInfo).address; + const srvPort = (srv.address() as AddressInfo).port; + ipAddress = srvIpAddress; + port = srvPort; + console.log("Forwarding lsp connection from " + srvIpAddress + ":" + srvPort + " over " + jumpHost.ipaddress + ":" + jumpHost.port + " to " + ipAddress + ":" + port); + }); + }) + .on("close", () => { + console.log("SSH jumphost lsp connection close"); + sshJumpHostConnection.end(); + srv && srv.close(); + }) + .on("error", (err) => { + console.log("SSH jumphost lsp connection error: " + err.message); + sshJumpHostConnection.end(); + srv && srv.close(); + }) + .connect({ + host: jumpHost.ipaddress, + port: jumpHost.port, + username: jumpHost.username, + password: jumpHost.password, + privateKey: jumpHost.privateKey + ? fs.readFileSync(jumpHost.privateKey) + : undefined, + //debug: (debug) => { + // console.log(debug) + //}, + readyTimeout: 1000, + }); + } const wsToLanguageServer = new WebSocket( "ws://" + ipAddress + ":" + port + "/" + language, ); @@ -49,9 +121,8 @@ export default function ( wsFromBrowser.on("close", () => { console.log("LanguageServer WebSocket closed..."); wsToLanguageServer.close(); - }); - }) - .catch((err) => { + }); + }).catch((err) => { console.log(err); wsFromBrowser.send( "Could not connect to environment language server, closing connection.", diff --git a/frontend/src/components/FileEditor.tsx b/frontend/src/components/FileEditor.tsx index 3ffefd1..02366a4 100644 --- a/frontend/src/components/FileEditor.tsx +++ b/frontend/src/components/FileEditor.tsx @@ -376,6 +376,8 @@ export default class FileEditor extends Component { Y.applyUpdate(doc, toUint8Array(data.content)); + // Check for double document, if environment was not undeployed + // Websocket provider this.collaborationProvider = new WebsocketProvider( `${window?.location?.protocol === "http:" || undefined ? "ws:" : "wss:"}//` +