From 753b6c240a4050449267e6b10ca7d0692eb6e257 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 17 May 2022 16:09:35 -0700 Subject: [PATCH] SSH/RDP credentials are now stored per user account, #3995 --- apprelays.js | 192 +++++++++++++++++++++++++++++---------------------- meshuser.js | 31 ++++++--- webserver.js | 16 +++-- 3 files changed, 141 insertions(+), 98 deletions(-) diff --git a/apprelays.js b/apprelays.js index a70f8500..5c90564d 100644 --- a/apprelays.js +++ b/apprelays.js @@ -215,28 +215,33 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { } } - // Save SSH credentials into device + // Save RDP credentials into database function saveRdpCredentials() { if (domain.allowsavingdevicecredentials == false) return; parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - const changed = (node.rdp == null); + if (node.rdp == null) { node.rdp = {}; } - // Check if credentials are the same - if ((typeof node.rdp == 'object') && (node.rdp.d == obj.infos.domain) && (node.rdp.u == obj.infos.username) && (node.rdp.p == obj.infos.password)) return; + // Check if credentials are already set + if ((typeof node.rdp[obj.userid] == 'object') && (node.rdp[obj.userid].d == obj.infos.domain) && (node.rdp[obj.userid].u == obj.infos.username) && (node.rdp[obj.userid].p == obj.infos.password)) return; + + // Clear up any existing credentials or credentials for users that don't exist anymore + for (var i in node.rdp) { if (!i.startsWith('user/') || (parent.users[i] == null)) { delete node.rdp[i]; } } + + // Clear legacy credentials + delete node.rdp.d; + delete node.rdp.u; + delete node.rdp.p; // Save the credentials - node.rdp = { d: obj.infos.domain, u: obj.infos.username, p: obj.infos.password }; + node.rdp[obj.userid] = { d: obj.infos.domain, u: obj.infos.username, p: obj.infos.password }; parent.parent.db.Set(node); - // Event node change if needed - if (changed) { - // Event the node change - const event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: obj.userid, node: parent.CloneSafeNode(node), msg: "Changed RDP credentials" }; - 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. - parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); - } + // Event the node change + const event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: obj.userid, node: parent.CloneSafeNode(node), msg: "Changed RDP credentials" }; + 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. + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); }); } @@ -299,10 +304,10 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { // Check if we need to load server stored credentials if ((typeof obj.infos.options == 'object') && (obj.infos.options.useServerCreds == true)) { // Check if RDP credentials exist - if ((domain.allowsavingdevicecredentials !== false) && (typeof node.rdp == 'object') && (typeof node.rdp.d == 'string') && (typeof node.rdp.u == 'string') && (typeof node.rdp.p == 'string')) { - obj.infos.domain = node.rdp.d; - obj.infos.username = node.rdp.u; - obj.infos.password = node.rdp.p; + if ((domain.allowsavingdevicecredentials !== false) && (typeof node.rdp == 'object') && (typeof node.rdp[obj.userid] == 'object') && (typeof node.rdp[obj.userid].d == 'string') && (typeof node.rdp[obj.userid].u == 'string') && (typeof node.rdp[obj.userid].p == 'string')) { + obj.infos.domain = node.rdp[obj.userid].d; + obj.infos.username = node.rdp[obj.userid].u; + obj.infos.password = node.rdp[obj.userid].p; startTcpServer(); } else { // No server credentials. @@ -382,7 +387,7 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) { module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { const Net = require('net'); const WebSocket = require('ws'); - + // SerialTunnel object is used to embed SSH within another connection. function SerialTunnel(options) { const obj = new require('stream').Duplex(options); @@ -447,36 +452,43 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { delete obj.cookie; delete obj.nodeid; delete obj.meshid; + delete obj.userid; delete obj.ws; }; - // Save SSH credentials into device + // Save SSH credentials into database function saveSshCredentials(keep) { if (((keep != 1) && (keep != 2)) || (domain.allowsavingdevicecredentials == false)) return; parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - const changed = (node.ssh == null); + if (node.ssh == null) { node.ssh = {}; } // Check if credentials are the same - //if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; // TODO + //if ((typeof node.ssh[obj.userid] == 'object') && (node.ssh[obj.userid].u == obj.username) && (node.ssh[obj.userid].p == obj.password)) return; // TODO + + // Clear up any existing credentials or credentials for users that don't exist anymore + for (var i in node.ssh) { if (!i.startsWith('user/') || (parent.users[i] == null)) { delete node.ssh[i]; } } + + // Clear legacy credentials + delete node.ssh.u; + delete node.ssh.p; + delete node.ssh.k; + delete node.ssh.kp; // Save the credentials if (obj.password != null) { - node.ssh = { u: obj.username, p: obj.password }; + node.ssh[obj.userid] = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey }; - if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } + node.ssh[obj.userid] = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh[obj.userid].kp = obj.privateKeyPass; } } else return; parent.parent.db.Set(node); - // Event node change if needed - if (changed) { - // Event the node change - const event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: obj.userid, node: parent.CloneSafeNode(node), msg: "Changed SSH credentials" }; - 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. - parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); - } + // Event the node change + const event = { etype: 'node', action: 'changenode', nodeid: obj.nodeid, domain: domain.id, userid: obj.userid, node: parent.CloneSafeNode(node), msg: "Changed SSH credentials" }; + 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. + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); }); } @@ -592,24 +604,24 @@ module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) { parent.parent.db.Get(obj.cookie.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (node.ssh[user._id] == null) || (typeof node.ssh[user._id].u != 'string') || ((typeof node.ssh[user._id].p != 'string') && (typeof node.ssh[user._id].k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } - } else if ((domain.allowsavingdevicecredentials !== false) && (node.ssh != null) && (typeof node.ssh.k == 'string') && (node.ssh.kp == null)) { + } else if ((domain.allowsavingdevicecredentials !== false) && (node.ssh != null) && (typeof node.ssh[user._id].k == 'string') && (node.ssh[user._id].kp == null)) { // Send a request for SSH authentication with option for only the private key password - obj.username = node.ssh.u; - obj.privateKey = node.ssh.k; + obj.username = node.ssh[user._id].u; + obj.privateKey = node.ssh[user._id].k; try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials obj.termSize = msg; delete obj.keep; - obj.username = node.ssh.u; - 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; + obj.username = node.ssh[user._id].u; + if (typeof node.ssh[user._id].p == 'string') { + obj.password = node.ssh[user._id].p; + } else if (typeof node.ssh[user._id].k == 'string') { + obj.privateKey = node.ssh[user._id].k; + obj.privateKeyPass = node.ssh[user._id].kp; } startRelayConnection(); } @@ -786,30 +798,37 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - const changed = (node.ssh == null); + if (node.ssh == null) { node.ssh = {}; } // Check if credentials are the same //if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; // TODO + // Clear up any existing credentials or credentials for users that don't exist anymore + for (var i in node.ssh) { if (!i.startsWith('user/') || (parent.users[i] == null)) { delete node.ssh[i]; } } + + // Clear legacy credentials + delete node.ssh.u; + delete node.ssh.p; + delete node.ssh.k; + delete node.ssh.kp; + // Save the credentials if (obj.password != null) { - node.ssh = { u: obj.username, p: obj.password }; + node.ssh[user._id] = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey }; - if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } + node.ssh[user._id] = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh[user._id].kp = obj.privateKeyPass; } } else return; parent.parent.db.Set(node); - // Event node change if needed - if (changed) { - // Event the node change - const 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" }; - 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. - parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); - } + // Event the node change + const 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" }; + 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. + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); }); } + // Start the looppback server function startRelayConnection(authCookie) { try { @@ -1032,22 +1051,22 @@ module.exports.CreateSshTerminalRelay = function (parent, db, ws, req, domain, u ws._socket.resume(); // Check if we have SSH credentials for this device - if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (node.ssh[user._id] == null) || (typeof node.ssh[user._id].u != 'string') || ((typeof node.ssh[user._id].p != 'string') && (typeof node.ssh[user._id].k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } - } else if ((typeof node.ssh.k == 'string') && (typeof node.ssh.kp != 'string')) { + } else if ((typeof node.ssh[user._id].k == 'string') && (typeof node.ssh[user._id].kp != 'string')) { // Send a request for SSH authentication with option for only the private key password - obj.username = node.ssh.u; - obj.privateKey = node.ssh.k; + obj.username = node.ssh[user._id].u; + obj.privateKey = node.ssh[user._id].k; try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials - obj.username = node.ssh.u; - 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; + obj.username = node.ssh[user._id].u; + if (typeof node.ssh[user._id].p == 'string') { + obj.password = node.ssh[user._id].p; + } else if (typeof node.ssh[user._id].k == 'string') { + obj.privateKey = node.ssh[user._id].k; + obj.privateKeyPass = node.ssh[user._id].kp; } try { ws.send(JSON.stringify({ action: 'sshautoauth' })) } catch (ex) { } } @@ -1132,30 +1151,37 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user parent.parent.db.Get(obj.nodeid, function (err, nodes) { if ((err != null) || (nodes == null) || (nodes.length != 1)) return; const node = nodes[0]; - const changed = (node.ssh == null); + if (node.rdp == null) { node.rdp = {}; } // Check if credentials are the same - //if ((typeof node.ssh == 'object') && (node.ssh.u == obj.username) && (node.ssh.p == obj.password)) return; // TODO + //if ((typeof node.ssh[obj.userid] == 'object') && (node.ssh[obj.userid].u == obj.username) && (node.ssh[obj.userid].p == obj.password)) return; // TODO + + // Clear up any existing credentials or credentials for users that don't exist anymore + for (var i in node.ssh) { if (!i.startsWith('user/') || (parent.users[i] == null)) { delete node.ssh[i]; } } + + // Clear legacy credentials + delete node.ssh.u; + delete node.ssh.p; + delete node.ssh.k; + delete node.ssh.kp; // Save the credentials if (obj.password != null) { - node.ssh = { u: obj.username, p: obj.password }; + node.ssh[user._id] = { u: obj.username, p: obj.password }; } else if (obj.privateKey != null) { - node.ssh = { u: obj.username, k: obj.privateKey }; - if (keep == 2) { node.ssh.kp = obj.privateKeyPass; } + node.ssh[user._id] = { u: obj.username, k: obj.privateKey }; + if (keep == 2) { node.ssh[user._id].kp = obj.privateKeyPass; } } else return; parent.parent.db.Set(node); - // Event node change if needed - if (changed) { - // Event the node change - const 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" }; - 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. - parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); - } + // Event the node change + const 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" }; + 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. + parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.nodeid]), obj, event); }); } + // Start the looppback server function startRelayConnection(authCookie) { try { @@ -1554,22 +1580,22 @@ module.exports.CreateSshFilesRelay = function (parent, db, ws, req, domain, user ws._socket.resume(); // Check if we have SSH credentials for this device - if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (typeof node.ssh.u != 'string') || ((typeof node.ssh.p != 'string') && (typeof node.ssh.k != 'string'))) { + if ((domain.allowsavingdevicecredentials === false) || (node.ssh == null) || (typeof node.ssh != 'object') || (node.ssh[user._id] == null) || (typeof node.ssh[user._id].u != 'string') || ((typeof node.ssh[user._id].p != 'string') && (typeof node.ssh[user._id].k != 'string'))) { // Send a request for SSH authentication try { ws.send(JSON.stringify({ action: 'sshauth' })) } catch (ex) { } - } else if ((typeof node.ssh.k == 'string') && (typeof node.ssh.kp != 'string')) { + } else if ((typeof node.ssh[user._id].k == 'string') && (typeof node.ssh[user._id].kp != 'string')) { // Send a request for SSH authentication with option for only the private key password - obj.username = node.ssh.u; - obj.privateKey = node.ssh.k; + obj.username = node.ssh[user._id].u; + obj.privateKey = node.ssh[user._id].k; try { ws.send(JSON.stringify({ action: 'sshauth', askkeypass: true })) } catch (ex) { } } else { // Use our existing credentials - obj.username = node.ssh.u; - 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; + obj.username = node.ssh[user._id].u; + if (typeof node.ssh[user._id].p == 'string') { + obj.password = node.ssh[user._id].p; + } else if (typeof node.ssh[user._id].k == 'string') { + obj.privateKey = node.ssh[user._id].k; + obj.privateKeyPass = node.ssh[user._id].kp; } // Create a mesh relay authentication cookie diff --git a/meshuser.js b/meshuser.js index 74f5d58d..7f5bbb34 100644 --- a/meshuser.js +++ b/meshuser.js @@ -423,9 +423,16 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use else if (event == 'updatefiles') { updateUserFiles(user, ws, domain); } else { // If updating guest device shares, if we are updating a user that is not creator of the share, remove the URL. - if ((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) { + if (((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) || ((event.action == 'changenode') && (event.node != null) && ((event.node.rdp != null) || (event.node.ssh != null)))) { event = common.Clone(event); - for (var i in event.deviceShares) { if (event.deviceShares[i].userid != user._id) { delete event.deviceShares[i].url; } } + if ((event.action == 'deviceShareUpdate') && (Array.isArray(event.deviceShares))) { + for (var i in event.deviceShares) { if (event.deviceShares[i].userid != user._id) { delete event.deviceShares[i].url; } } + } + if ((event.action == 'changenode') && (event.node != null) && ((event.node.rdp != null) || (event.node.ssh != null))) { + // Clean up RDP & SSH credentials + if ((event.node.rdp != null) && (typeof event.node.rdp[user._id] == 'number')) { event.node.rdp = event.node.rdp[user._id]; } else { delete event.node.rdp; } + if ((event.node.ssh != null) && (typeof event.node.ssh[user._id] == 'number')) { event.node.ssh = event.node.ssh[user._id]; } else { delete event.node.ssh; } + } } // This is a MeshCentral Satellite message @@ -730,18 +737,18 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Remove SSH credentials if present if (docs[i].ssh != null) { - if (docs[i].ssh.u) { - if (docs[i].ssh.k && docs[i].ssh.kp) { docs[i].ssh = 2; } // Username, key and password - else if (docs[i].ssh.k) { docs[i].ssh = 3; } // Username and key. No password. - else if (docs[i].ssh.p) { docs[i].ssh = 1; } // Username and password + if ((docs[i].ssh[obj.user._id] != null) && (docs[i].ssh[obj.user._id].u)) { + if (docs[i].ssh.k && docs[i].ssh[obj.user._id].kp) { docs[i].ssh = 2; } // Username, key and password + else if (docs[i].ssh[obj.user._id].k) { docs[i].ssh = 3; } // Username and key. No password. + else if (docs[i].ssh[obj.user._id].p) { docs[i].ssh = 1; } // Username and password else { delete docs[i].ssh; } } else { delete docs[i].ssh; } } - // Remove RDP credentials if present - if (docs[i].rdp != null) { docs[i].rdp = 1; } + // Remove RDP credentials if present, only set to 1 if our userid has RDP credentials + if ((docs[i].rdp != null) && (docs[i].rdp[obj.user._id] != null)) { docs[i].rdp = 1; } else { delete docs[i].rdp; } // Remove Intel AMT credential if present if (docs[i].intelamt != null) { @@ -3014,13 +3021,17 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } if ((typeof command.ssh == 'number') && (command.ssh == 0)) { - if (node.ssh != null) { delete node.ssh; change = 1; changes.push('ssh'); } // Delete the SSH cendentials + if ((node.ssh != null) && (node.ssh[user._id] != null)) { delete node.ssh[user._id]; change = 1; changes.push('ssh'); } // Delete the SSH cendentials } if ((typeof command.rdp == 'number') && (command.rdp == 0)) { - if (node.rdp != null) { delete node.rdp; change = 1; changes.push('rdp'); } // Delete the RDP cendentials + if ((node.rdp != null) && (node.rdp[user._id] != null)) { delete node.rdp[user._id]; change = 1; changes.push('rdp'); } // Delete the RDP cendentials } + // Clean up any legacy RDP and SSH credentials + if (node.rdp != null) { delete node.rdp.d; delete node.rdp.u; delete node.rdp.p; } + if (node.ssh != null) { delete node.ssh.u; delete node.ssh.p; delete node.ssh.k; delete node.ssh.kp; } + if (domain.geolocation && command.userloc && ((node.userloc == null) || (command.userloc[0] != node.userloc[0]) || (command.userloc[1] != node.userloc[1]))) { change = 1; if ((command.userloc.length == 0) && (node.userloc)) { diff --git a/webserver.js b/webserver.js index 72b2e943..a3c79bf6 100644 --- a/webserver.js +++ b/webserver.js @@ -7533,12 +7533,18 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF 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 && r.ssh.u) { - if (r.ssh.p) { r.ssh = 1; } // Username and password - else if (r.ssh.k && r.ssh.kp) { r.ssh = 2; } // Username, key and password - else if (r.ssh.k) { r.ssh = 3; } // Username and key. No password. + if (r.ssh != null) { + var n = {}; + for (var i in r.ssh) { + if (i.startsWith('user/')) { + if (r.ssh[i].p) { n[i] = 1; } // Username and password + else if (r.ssh[i].k && r.ssh[i].kp) { n[i] = 2; } // Username, key and password + else if (r.ssh[i].k) { n[i] = 3; } // Username and key. No password. + } + } + r.ssh = n; } - if (r.rdp != null) { r.rdp = 1; } + if (r.rdp != null) { var n = {}; for (var i in r.rdp) { if (i.startsWith('user/')) { n[i] = 1; } } r.rdp = n; } if ((r.intelamt != null) && ((r.intelamt.pass != null) || (r.intelamt.mpspass != null))) { r.intelamt = Object.assign({}, r.intelamt); // Shallow clone if (r.intelamt.pass != null) { r.intelamt.pass = 1; }; // Remove the Intel AMT administrator password from the node