diff --git a/apprelays.js b/apprelays.js index b274abfa..30466f73 100644 --- a/apprelays.js +++ b/apprelays.js @@ -338,7 +338,18 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { var connectionOptions = { sock: obj.ser } if (typeof obj.username == 'string') { connectionOptions.username = obj.username; delete obj.username; } if (typeof obj.password == 'string') { connectionOptions.password = obj.password; delete obj.password; } - obj.sshClient.connect(connectionOptions); + if (typeof obj.privateKey == 'string') { connectionOptions.privateKey = obj.privateKey; delete obj.privateKey; } + if (typeof obj.privateKeyPass == 'string') { connectionOptions.passphrase = obj.privateKeyPass; delete obj.privateKeyPass; } + try { + obj.sshClient.connect(connectionOptions); + } catch (ex) { + // Exception, this is generally because we did not provide proper credentials. Ask again. + obj.relayActive = false; + delete obj.sshClient; + delete obj.ser.forwardwrite; + obj.close(); + return; + } // We are all set, start receiving data ws._socket.resume(); @@ -372,6 +383,8 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { obj.termSize = msg; obj.username = msg.username; obj.password = msg.password; + obj.privateKey = msg.key; + obj.privateKeyPass = msg.keypass; startRelayConnection(); break; } @@ -472,10 +485,14 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u const changed = (node.ssh == null); // Check if credentials are the same - if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; + //if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; // TODO // Save the credentials - node.ssh = { u: obj.username, p: obj.password }; + if (obj.password != null) { + node.ssh = { u: obj.username, p: obj.password }; + } else if (obj.privateKey != null) { + node.ssh = { u: obj.username, k: obj.privateKey, kp: obj.privateKeyPass }; + } parent.parent.db.Set(node); // Event node change if needed @@ -538,7 +555,17 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u var connectionOptions = { sock: obj.ser } if (typeof obj.username == 'string') { connectionOptions.username = obj.username; } if (typeof obj.password == 'string') { connectionOptions.password = obj.password; } - obj.sshClient.connect(connectionOptions); + if (typeof obj.privateKey == 'string') { connectionOptions.privateKey = obj.privateKey; } + if (typeof obj.privateKeyPass == 'string') { connectionOptions.passphrase = obj.privateKeyPass; } + try { + obj.sshClient.connect(connectionOptions); + } catch (ex) { + // Exception, this is generally because we did not provide proper credentials. Ask again. + obj.relayActive = false; + delete obj.sshClient; + delete obj.ser.forwardwrite; + try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } + } // We are all set, start receiving data ws._socket.resume(); @@ -569,13 +596,15 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u switch (msg.action) { case 'sshauth': { // Verify inputs - if ((typeof msg.username != 'string') || (typeof msg.password != 'string')) break; + if ((typeof msg.username != 'string') || ((typeof msg.password != 'string') && (typeof msg.key != 'string'))) break; if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; obj.keep = msg.keep; // If true, keep store credentials on the server if the SSH tunnel connected succesfully. obj.termSize = msg; obj.username = msg.username; obj.password = msg.password; + obj.privateKey = msg.key; + obj.privateKeyPass = msg.keypass; // Create a mesh relay authentication cookie var cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; @@ -588,7 +617,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break; obj.termSize = msg; - if ((obj.username == null) || (obj.password == null)) return; + if ((obj.username == null) || ((obj.password == null) && (obj.privateKey == null))) return; // Create a mesh relay authentication cookie var cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; @@ -639,13 +668,18 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || (typeof node.ssh.p != 'string')) { + if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } } else { // Use our existing credentials obj.username = node.ssh.u; - obj.password = node.ssh.p; + if (typeof node.ssh.p == 'string') { + obj.password = node.ssh.p; + } else if (typeof node.ssh.k == 'string') { + obj.privateKey = node.ssh.k; + obj.privateKeyPass = node.ssh.kp; + } try { ws.send(JSON.stringify({ action: 'sshautoauth' })) } catch (ex) { } } }); @@ -722,8 +756,15 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user const node = nodes[0]; const changed = (node.ssh == null); + // Check if credentials are the same + //if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; // TODO + // Save the credentials - node.ssh = { u: obj.username, p: obj.password }; + if (obj.password != null) { + node.ssh = { u: obj.username, p: obj.password }; + } else if (obj.privateKey != null) { + node.ssh = { u: obj.username, k: obj.privateKey, kp: obj.privateKeyPass }; + } parent.parent.db.Set(node); // Event node change if needed @@ -781,7 +822,17 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user var connectionOptions = { sock: obj.ser } if (typeof obj.username == 'string') { connectionOptions.username = obj.username; } if (typeof obj.password == 'string') { connectionOptions.password = obj.password; } - obj.sshClient.connect(connectionOptions); + if (typeof obj.privateKey == 'string') { connectionOptions.privateKey = obj.privateKey; } + if (typeof obj.privateKeyPass == 'string') { connectionOptions.passphrase = obj.privateKeyPass; } + try { + obj.sshClient.connect(connectionOptions); + } catch (ex) { + // Exception, this is generally because we did not provide proper credentials. Ask again. + obj.relayActive = false; + delete obj.sshClient; + delete obj.ser.forwardwrite; + try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } + } // We are all set, start receiving data ws._socket.resume(); @@ -995,11 +1046,13 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user if (obj.sshClient != null) return; // Verify inputs - if ((typeof msg.username != 'string') || (typeof msg.password != 'string')) break; + if ((typeof msg.username != 'string') || ((typeof msg.password != 'string') && (typeof msg.key != 'string'))) break; obj.keep = (msg.keep === true); // If true, keep store credentials on the server if the SSH tunnel connected succesfully. obj.username = msg.username; obj.password = msg.password; + obj.privateKey = msg.key; + obj.privateKeyPass = msg.keypass; // Create a mesh relay authentication cookie var cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; @@ -1068,13 +1121,18 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || (typeof node.ssh.p != 'string')) { + if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } } else { // Use our existing credentials obj.username = node.ssh.u; - obj.password = node.ssh.p; + if (typeof node.ssh.p == 'string') { + obj.password = node.ssh.p; + } else if (typeof node.ssh.k == 'string') { + obj.privateKey = node.ssh.k; + obj.privateKeyPass = node.ssh.kp; + } // Create a mesh relay authentication cookie var cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport }; diff --git a/meshuser.js b/meshuser.js index 58d6c016..26221cab 100644 --- a/meshuser.js +++ b/meshuser.js @@ -717,7 +717,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (docs[i].pmt != null) { docs[i].pmt = 1; } // Remove SSH credentials if present - if (docs[i].ssh != null) { docs[i].ssh = 1; } + if (docs[i].ssh != null) { docs[i].ssh = (docs[i].ssh.k) ? 2 : 1; } // Remove RDP credentials if present if (docs[i].rdp != null) { docs[i].rdp = 1; } diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index c1ca62f5..7f429dfa 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -3439,10 +3439,10 @@ if ((node.ssh != null) || (node.rdp != null)) { var y = []; if ((meshrights & 4) != 0) { - if (node.ssh != null) { y.push('' + "SSH" + ' '); } + if (node.ssh != null) { y.push('' + ((node.ssh == 2) ? "SSH-Key" : "SSH") + ' '); } if (node.rdp != null) { y.push('' + "RDP" + ' '); } } else { - if (node.ssh != null) { y.push("SSH"); } + if (node.ssh != null) { y.push(((node.ssh == 2) ? "SSH-Key" : "SSH")); } if (node.rdp != null) { y.push("RDP"); } } x += addDeviceAttribute("Credentials", y.join(', ')); diff --git a/views/default.handlebars b/views/default.handlebars index 4d5d9ad2..e28a7700 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -6624,10 +6624,10 @@ if ((node.ssh != null) || (node.rdp != null)) { var y = []; if ((meshrights & 4) != 0) { - if (node.ssh != null) { y.push('' + "SSH" + ' '); } + if (node.ssh != null) { y.push('' + ((node.ssh == 2)?"SSH-Key":"SSH") + ' '); } if (node.rdp != null) { y.push('' + "RDP" + ' '); } } else { - if (node.ssh != null) { y.push("SSH"); } + if (node.ssh != null) { y.push(((node.ssh == 2)?"SSH-Key":"SSH")); } if (node.rdp != null) { y.push("RDP"); } } x += addDeviceAttribute("Credentials", y.join(', ')); @@ -8957,11 +8957,17 @@ switch (j.action) { case 'sshauth': { var x = ''; - x += addHtmlValue("Username", ''); - x += addHtmlValue("Password", ''); + x += addHtmlValue("Authentication", '') + x += addHtmlValue("Username", ''); + x += '
'; + x += addHtmlValue("Password", ''); + x += '
'; x += addHtmlValue('', ''); setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh'); - setTimeout(sshAuthKeyUp, 50); + setTimeout(sshAuthUpdate, 50); break; } case 'sshautoauth': { @@ -8977,12 +8983,35 @@ } } - function sshAuthKeyUp(e) { QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); } + function sshAuthUpdate(e) { + QV('d2passauth', Q('dp2authmethod').value == 1); + QV('d2keyauth', Q('dp2authmethod').value == 2); + if (Q('dp2authmethod').value == 1) { + QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0)); + } else { + QE('idx_dlgOkButton', false); + var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000); + if (ok == true) { + var reader = new FileReader(); + reader.onload = function (e) { + var validkey = ((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)); + QE('idx_dlgOkButton', validkey); + } + reader.readAsText(Q('dp2key').files[0]); + } + } + } function sshConnectEx(b) { if (b == 0) { if (terminal != null) { connectTerminal(); } // Disconnect } else { - terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); + if (Q('dp2authmethod').value == 1) { + terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); + } else { + var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked; + reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); } + reader.readAsText(Q('dp2key').files[0]); + } } } @@ -9312,11 +9341,17 @@ switch (data.action) { case 'sshauth': { var x = ''; - x += addHtmlValue("Username", ''); - x += addHtmlValue("Password", ''); + x += addHtmlValue("Authentication", '') + x += addHtmlValue("Username", ''); + x += '
'; + x += addHtmlValue("Password", ''); + x += '
'; x += addHtmlValue('', ''); setDialogMode(2, "Authentication", 11, p13sshConnectEx, x, 'ssh'); - setTimeout(sshAuthKeyUp, 50); + setTimeout(sshAuthUpdate, 50); return; } case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; } @@ -9367,7 +9402,13 @@ if (b == 0) { if (files != null) { connectFiles(); } // Disconnect } else { - files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked })); + if (Q('dp2authmethod').value == 1) { + files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked })); + } else { + var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked; + reader.onload = function (e) { files.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep })); } + reader.readAsText(Q('dp2key').files[0]); + } } } diff --git a/webserver.js b/webserver.js index 0d4cad5f..806d7918 100644 --- a/webserver.js +++ b/webserver.js @@ -7072,10 +7072,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.CloneSafeNode = function (node) { if (typeof node != 'object') { return node; } var r = node; - if ((r.pmt != null) || ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null)))) { + if ((r.pmt != null) || (r.ssh != null) || (r.rdp != null) || ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null)))) { r = Object.assign({}, r); // Shallow clone if (r.pmt != null) { r.pmt = 1; } - if (r.ssh != null) { r.ssh = 1; } + if (r.ssh != null) { r.ssh = (r.ssh.k != null) ? 2 : 1; } if (r.rdp != null) { r.rdp = 1; } if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) { r.intelamt = Object.assign({}, r.intelamt); // Shallow clone