SSH remember credentials.

Ylian Saint-Hilaire committed May 21, 2021 at 13:43 UTC 2416545aa402bc3efce163894f79541b31804e83
4 files changed +106 -37
apprelays.js
+61 -5
@@ -165,6 +165,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
165
166 // Construct a SSH Relay object, called upon connection
167 module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
168 + console.log('CreateSshRelay');
169 const Net = require('net');
170 const WebSocket = require('ws');
171
@@ -293,6 +294,7 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
294 // When data is received from the web socket
295 // SSH default port is 22
296 ws.on('message', function (msg) {
297 + console.log('message', msg);
298 try {
299 if (typeof msg != 'string') return;
300 if (msg[0] == '{') {
@@ -400,6 +402,28 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
402 delete obj.ws;
403 };
404
405 + // Save SSH credentials into device
406 + function saveSshCredentials() {
407 + console.log('Save SSH credentials', obj.username, obj.password, obj.nodeid);
408 + parent.parent.db.Get(obj.nodeid, function (err, nodes) {
409 + if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
410 + const node = nodes[0];
411 + const changed = (node.ssh == null);
412 +
413 + // Save the credentials
414 + node.ssh = { u: obj.username, p: obj.password };
415 + parent.parent.db.Set(node);
416 +
417 + // Event node change if needed
418 + if (changed) {
419 + // Event the node change
420 + var event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: user._id, username: user.name, node: parent.CloneSafeNode(node), msg: "Changed SSH credentials" };
421 + if (parent.parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
422 + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event);
423 + }
424 + });
425 + }
426 +
427 // Start the looppback server
428 function startRelayConnection(authCookie) {
429 try {
@@ -423,6 +447,9 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
447 const Client = require('ssh2').Client;
448 obj.sshClient = new Client();
449 obj.sshClient.on('ready', function () { // Authentication was successful.
450 + // If requested, save the credentials
451 + if (obj.keep === true) saveSshCredentials();
452 +
453 obj.sshClient.shell(function (err, stream) { // Start a remote shell
454 if (err) { obj.close(); return; }
455 obj.sshShell = stream;
@@ -433,7 +460,8 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
460 obj.ws.send('c');
461 });
462 obj.sshClient.on('error', function (err) {
436 - if (err.level == 'client-authentication') { obj.ws.send(JSON.stringify({ action: 'autherror' })); }
463 + if (err.level == 'client-authentication') { try { obj.ws.send(JSON.stringify({ action: 'autherror' })); } catch (ex) { } }
464 + if (err.level == 'client-timeout') { try { obj.ws.send(JSON.stringify({ action: 'sessiontimeout' })); } catch (ex) { } }
465 obj.close();
466 });
467
@@ -442,8 +470,8 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
470
471 // Connect the SSH module to the serial tunnel
472 var connectionOptions = { sock: obj.ser }
445 - if (typeof obj.username == 'string') { connectionOptions.username = obj.username; delete obj.username; }
446 - if (typeof obj.password == 'string') { connectionOptions.password = obj.password; delete obj.password; }
473 + if (typeof obj.username == 'string') { connectionOptions.username = obj.username; }
474 + if (typeof obj.password == 'string') { connectionOptions.password = obj.password; }
475 obj.sshClient.connect(connectionOptions);
476
477 // We are all set, start receiving data
@@ -475,6 +503,7 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
503 if ((typeof msg.username != 'string') || (typeof msg.password != 'string')) break;
504 if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break;
505
506 + obj.keep = msg.keep; // If true, keep store credentials on the server if the SSH tunnel connected succesfully.
507 obj.termSize = msg;
508 obj.username = msg.username;
509 obj.password = msg.password;
@@ -485,6 +514,19 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
514 startRelayConnection(parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey));
515 break;
516 }
517 + case 'sshautoauth': {
518 + // Verify inputs
519 + if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break;
520 + obj.termSize = msg;
521 +
522 + if ((obj.username == null) || (obj.password == null)) return;
523 +
524 + // Create a mesh relay authentication cookie
525 + var cookieContent = { userid: user._id, domainid: user.domain, nodeid: obj.nodeid, tcpport: obj.tcpport };
526 + if (obj.mtype == 3) { cookieContent.lc = 1; } // This is a local device
527 + startRelayConnection(parent.parent.encodeCookie(cookieContent, parent.parent.loginCookieEncryptionKey));
528 + break;
529 + }
530 case 'resize': {
531 // Verify inputs
532 if ((typeof msg.rows != 'number') || (typeof msg.cols != 'number') || (typeof msg.height != 'number') || (typeof msg.width != 'number')) break;
@@ -531,8 +573,22 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u
573 // We are all set, start receiving data
574 ws._socket.resume();
575
534 - // Send a request for SSH authentication
535 - try { ws.send(JSON.stringify({ action:'sshauth' })) } catch (ex) { }
576 + // Check if we have SSH credentials for this device
577 + parent.parent.db.Get(obj.nodeid, function (err, nodes) {
578 + if ((err != null) || (nodes == null) || (nodes.length != 1)) return;
579 + const node = nodes[0];
580 +
581 + if ((node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || (typeof node.ssh.p != 'string')) {
582 + // Send a request for SSH authentication
583 + try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { }
584 + } else {
585 + // Use our existing credentials
586 + obj.username = node.ssh.u;
587 + obj.password = node.ssh.p;
588 + try { ws.send(JSON.stringify({ action: 'sshautoauth' })) } catch (ex) { }
589 + }
590 + });
591 +
592 });
593
594 return obj;
views/default-mobile.handlebars
+21 -11
@@ -3999,6 +3999,17 @@
3999 function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
4000 function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
4001
4002 + function p12setConsoleMsg(msg, timeout) {
4003 + if (msg) {
4004 + Q('p12TermConsoleMsg').innerHTML += msg;
4005 + QV('p12TermConsoleMsg', true);
4006 + if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
4007 + if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
4008 + } else {
4009 + p12clearConsoleMsg();
4010 + }
4011 + }
4012 +
4013 function onDesktopStateChange(xdesktop, state) {
4014 var xstate = state;
4015 if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
@@ -4567,9 +4578,17 @@
4578 var x = '';
4579 x += addHtmlValue("Username", '<input id=dp2user style=width:190px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
4580 x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:190px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
4581 + x += addHtmlValue('', '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>');
4582 setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh');
4583 setTimeout(sshAuthKeyUp, 50);
4584 }
4585 + case 'sshautoauth': {
4586 + terminal.socket.send(JSON.stringify({ action: 'sshautoauth', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
4587 + break;
4588 + }
4589 + case 'autherror': { p12setConsoleMsg("Authentication Error", 5000); break; }
4590 + case 'sessionerror': { p12setConsoleMsg("Session expired", 5000); break; }
4591 + case 'sessiontimeout': { p12setConsoleMsg("Session timeout", 5000); break; }
4592 }
4593 } else if (data[0] == '~') { xterm.writeUtf8(data.substring(1)); }
4594 }
@@ -4580,7 +4599,7 @@
4599 if (b == 0) {
4600 if (terminal != null) { connectTerminal(); } // Disconnect
4601 } else {
4583 - terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
4602 + 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 }));
4603 }
4604 }
4605
@@ -4648,16 +4667,7 @@
4667 terminal.onStateChanged = onTerminalStateChange;
4668 terminal.contype = 1;
4669 terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
4651 - terminal.onConsoleMessageChange = function (server, msg) {
4652 - if (terminal.consoleMessage) {
4653 - Q('p12TermConsoleMsg').innerHTML += formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs);
4654 - QV('p12TermConsoleMsg', true);
4655 - if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
4656 - if (terminal.consoleMessageTimeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, terminal.consoleMessageTimeout * 1000); }
4657 - } else {
4658 - p12clearConsoleMsg();
4659 - }
4660 - };
4670 + terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
4671 } else {
4672 terminal.Stop();
4673 terminal = null;
views/default.handlebars
+23 -21
@@ -7804,6 +7804,17 @@
7804 function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
7805 function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
7806
7807 + function p12setConsoleMsg(msg, timeout) {
7808 + if (msg) {
7809 + Q('p12TermConsoleMsg').innerHTML += msg;
7810 + QV('p12TermConsoleMsg', true);
7811 + if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
7812 + if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
7813 + } else {
7814 + p12clearConsoleMsg();
7815 + }
7816 + }
7817 +
7818 var webRtcDesktop = null;
7819 function webRtcDesktopReset() {
7820 if (webRtcDesktop == null) return;
@@ -8680,9 +8691,18 @@
8691 var x = '';
8692 x += addHtmlValue("Username", '<input id=dp2user style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
8693 x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthKeyUp(event) />');
8694 + x += addHtmlValue('', '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>');
8695 setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh');
8696 setTimeout(sshAuthKeyUp, 50);
8697 + break;
8698 + }
8699 + case 'sshautoauth': {
8700 + terminal.socket.send(JSON.stringify({ action: 'sshautoauth', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
8701 + break;
8702 }
8703 + case 'autherror': { p12setConsoleMsg("Authentication Error", 5000); break; }
8704 + case 'sessionerror': { p12setConsoleMsg("Session expired", 5000); break; }
8705 + case 'sessiontimeout': { p12setConsoleMsg("Session timeout", 5000); break; }
8706 }
8707 } else if (data[0] == '~') { xterm.writeUtf8(data.substring(1)); }
8708 }
@@ -8693,7 +8713,7 @@
8713 if (b == 0) {
8714 if (terminal != null) { connectTerminal(); } // Disconnect
8715 } else {
8696 - terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
8716 + 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 }));
8717 }
8718 }
8719
@@ -8796,16 +8816,7 @@
8816 terminal.onStateChanged = onTerminalStateChange;
8817 terminal.contype = 1;
8818 terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
8799 - terminal.onConsoleMessageChange = function (server, msg) {
8800 - if (terminal.consoleMessage) {
8801 - Q('p12TermConsoleMsg').innerHTML += formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs);
8802 - QV('p12TermConsoleMsg', true);
8803 - if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
8804 - if (terminal.consoleMessageTimeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, terminal.consoleMessageTimeout * 1000); }
8805 - } else {
8806 - p12clearConsoleMsg();
8807 - }
8808 - };
8819 + terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
8820 } else {
8821 QV('termarea3xdiv', false);
8822 QV('Term', true);
@@ -8819,16 +8830,7 @@
8830 terminal.m.lineFeed = ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) >= 0) ? '\r\n' : '\r'; // On windows, send \r\n, on Linux only \r
8831 terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
8832 terminal.onStateChanged = onTerminalStateChange;
8822 - terminal.onConsoleMessageChange = function () {
8823 - if (terminal.consoleMessage) {
8824 - Q('p12TermConsoleMsg').innerHTML += formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs);
8825 - QV('p12TermConsoleMsg', true);
8826 - if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
8827 - if (terminal.consoleMessageTimeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, terminal.consoleMessageTimeout * 1000); }
8828 - } else {
8829 - p12clearConsoleMsg();
8830 - }
8831 - }
8833 + terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
8834 terminal.Start(terminalNode._id);
8835 terminal.contype = 1;
8836 terminal.m.terminalEmulation = 0;
webserver.js
+1
@@ -6898,6 +6898,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
6898 if ((r.pmt != null) || ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null)))) {
6899 r = Object.assign({}, r); // Shallow clone
6900 if (r.pmt != null) { r.pmt = 1; }
6901 + if (r.ssh != null) { r.ssh = 1; }
6902 if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) {
6903 r.intelamt = Object.assign({}, r.intelamt); // Shallow clone
6904 if (r.intelamt.pass != null) { r.intelamt.pass = 1; }; // Remove the Intel AMT administrator password from the node