mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-26 07:23:03 +03:00
Many server improvements & stability fixes.
This commit is contained in:
parent
86e98041a7
commit
76ba7fa799
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
120
meshagent.js
120
meshagent.js
@ -258,7 +258,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.send(obj.common.ShortToStr(1) + msg.substring(2, 50) + obj.nonce); // Command 1, hash + nonce. Use the web hash given by the agent.
|
||||
} else {
|
||||
// Check that the server hash matches our own web certificate hash (SHA384)
|
||||
if ((getWebCertHash(obj.domain) != msg.substring(2, 50)) && (getWebCertFullHash(obj.domain) != msg.substring(2, 50))) { console.log('Agent bad web cert hash (Agent:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (Buffer.from(getWebCertHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (new Buffer(getWebCertFullHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); return; }
|
||||
if ((getWebCertHash(obj.domain) != msg.substring(2, 50)) && (getWebCertFullHash(obj.domain) != msg.substring(2, 50))) {
|
||||
console.log('Agent bad web cert hash (Agent:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (Buffer.from(getWebCertHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (new Buffer(getWebCertFullHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').');
|
||||
console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
|
||||
@ -365,6 +369,31 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.send(obj.common.ShortToStr(1) + getWebCertHash(obj.domain) + obj.nonce); // Command 1, hash + nonce
|
||||
}
|
||||
|
||||
// Return the mesh for this device, in some cases, we may auto-create the mesh.
|
||||
function getMeshAutoCreate() {
|
||||
var mesh = obj.parent.meshes[obj.dbMeshKey];
|
||||
if ((mesh == null) && (typeof obj.domain.orphanagentuser == 'string')) {
|
||||
var adminUser = obj.parent.users['user/' + domain.id + '/' + obj.domain.orphanagentuser.toLowerCase()];
|
||||
if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
|
||||
// Mesh name is hex instead of base64
|
||||
var meshname = Buffer.from(obj.meshid, 'base64').toString('hex').substring(0, 18);
|
||||
|
||||
// Create a new mesh for this device
|
||||
var links = {};
|
||||
links[adminUser._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
|
||||
mesh = { type: 'mesh', _id: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', domain: domain.id, links: links };
|
||||
obj.db.Set(obj.common.escapeLinksFieldName(mesh));
|
||||
obj.parent.meshes[obj.dbMeshKey] = mesh;
|
||||
|
||||
if (adminUser.links == null) user.links = {};
|
||||
adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF };
|
||||
obj.db.SetUser(adminUser);
|
||||
obj.parent.parent.DispatchEvent(['*', obj.dbMeshKey, adminUser._id], obj, { etype: 'mesh', username: adminUser.name, meshid: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id });
|
||||
}
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Once we get all the information about an agent, run this to hook everything up to the server
|
||||
function completeAgentConnection() {
|
||||
if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection) return;
|
||||
@ -380,21 +409,69 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
if (domainAgentSessionCount >= domain.limits.maxagentsessions) { return; } // Too many, hold the connection.
|
||||
}
|
||||
|
||||
/*
|
||||
// Check that the mesh exists
|
||||
var mesh = obj.parent.meshes[obj.dbMeshKey];
|
||||
if (mesh == null) { console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
if (mesh == null) {
|
||||
var holdConnection = true;
|
||||
if (typeof obj.domain.orphanagentuser == 'string') {
|
||||
var adminUser = obj.parent.users['user/' + domain.id + '/' + obj.args.orphanagentuser];
|
||||
if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
|
||||
// Create a new mesh for this device
|
||||
holdConnection = false;
|
||||
var links = {};
|
||||
links[user._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
|
||||
mesh = { type: 'mesh', _id: obj.dbMeshKey, name: obj.meshid, mtype: 2, desc: '', domain: domain.id, links: links };
|
||||
obj.db.Set(obj.common.escapeLinksFieldName(mesh));
|
||||
obj.parent.meshes[obj.meshid] = mesh;
|
||||
obj.parent.parent.AddEventDispatch([obj.meshid], ws);
|
||||
|
||||
if (adminUser.links == null) user.links = {};
|
||||
adminUser.links[obj.meshid] = { rights: 0xFFFFFFFF };
|
||||
//adminUser.subscriptions = obj.parent.subscribe(adminUser._id, ws);
|
||||
obj.db.SetUser(user);
|
||||
obj.parent.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: obj.meshid, name: obj.meshid, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id });
|
||||
}
|
||||
}
|
||||
|
||||
if (holdConnection == true) {
|
||||
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mesh.mtype != 2) { console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); return; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
*/
|
||||
|
||||
// Check that the node exists
|
||||
obj.db.Get(obj.dbNodeKey, function (err, nodes) {
|
||||
var device;
|
||||
|
||||
// Mark when we connected to this agent
|
||||
obj.connectTime = Date.now();
|
||||
obj.db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport });
|
||||
|
||||
// See if this node exists in the database
|
||||
if (nodes.length == 0) {
|
||||
// This device does not exist, use the meshid given by the device
|
||||
|
||||
// See if this mesh exists, if it does not we may want to create it.
|
||||
var mesh = getMeshAutoCreate();
|
||||
|
||||
// Check if the mesh exists
|
||||
if (mesh == null) {
|
||||
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the mesh is the right type
|
||||
if (mesh.mtype != 2) {
|
||||
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark when this device connected
|
||||
obj.connectTime = Date.now();
|
||||
obj.db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport });
|
||||
|
||||
// This node does not exist, create it.
|
||||
device = { type: 'node', mtype: mesh.mtype, _id: obj.dbNodeKey, icon: obj.agentInfo.platformType, meshid: obj.dbMeshKey, name: obj.agentInfo.computerName, rname: obj.agentInfo.computerName, domain: domain.id, agent: { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }, host: null };
|
||||
obj.db.Set(device);
|
||||
@ -407,15 +484,42 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, { etype: 'node', action: 'addnode', node: device, msg: ('Added device ' + obj.agentInfo.computerName + ' to mesh ' + mesh.name), domain: domain.id });
|
||||
}
|
||||
} else {
|
||||
// Device already exists, look if changes has occured
|
||||
device = nodes[0];
|
||||
|
||||
// This device exists, meshid given by the device must be ignored, use the server side one.
|
||||
if (device.meshid != obj.dbMeshKey) {
|
||||
obj.dbMeshKey = device.meshid;
|
||||
obj.meshid = device.meshid.split('/')[2];
|
||||
}
|
||||
|
||||
// See if this mesh exists, if it does not we may want to create it.
|
||||
var mesh = getMeshAutoCreate();
|
||||
|
||||
// Check if the mesh exists
|
||||
if (mesh == null) {
|
||||
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the mesh is the right type
|
||||
if (mesh.mtype != 2) {
|
||||
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
||||
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark when this device connected
|
||||
obj.connectTime = Date.now();
|
||||
obj.db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport });
|
||||
|
||||
// Device already exists, look if changes has occured
|
||||
var changes = [], change = 0, log = 0;
|
||||
if (device.agent == null) { device.agent = { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }; change = 1; }
|
||||
if (device.rname != obj.agentInfo.computerName) { device.rname = obj.agentInfo.computerName; change = 1; changes.push('computer name'); }
|
||||
if (device.agent.ver != obj.agentInfo.agentVersion) { device.agent.ver = obj.agentInfo.agentVersion; change = 1; changes.push('agent version'); }
|
||||
if (device.agent.id != obj.agentInfo.agentId) { device.agent.id = obj.agentInfo.agentId; change = 1; changes.push('agent type'); }
|
||||
if ((device.agent.caps & 24) != (obj.agentInfo.capabilities & 24)) { device.agent.caps = obj.agentInfo.capabilities; change = 1; changes.push('agent capabilities'); } // If agent console or javascript support changes, update capabilities
|
||||
if (device.meshid != obj.dbMeshKey) { obj.dbMeshKey = device.meshid; obj.meshid = device.meshid.split('/')[2]; } // If the mesh does not match, the server mesh is the correct one. This is because we allow the server to change the mesh of a device server-side.
|
||||
if (change == 1) {
|
||||
obj.db.Set(device);
|
||||
|
||||
|
33
meshuser.js
33
meshuser.js
@ -642,6 +642,22 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
obj.parent.parent.DispatchEvent(['*', 'server-users'], obj, { etype: 'user', userid: deluserid, username: deluser.name, action: 'accountremove', msg: 'Account removed', domain: domain.id });
|
||||
obj.parent.parent.DispatchEvent([deluserid], obj, 'close');
|
||||
|
||||
break;
|
||||
}
|
||||
case 'userbroadcast':
|
||||
{
|
||||
// Broadcast a message to all currently connected users.
|
||||
if ((user.siteadmin & 2) == 0) break;
|
||||
if (obj.common.validateUsername(command.msg, 1, 256) == false) break; // Notification message is between 1 and 256 characters
|
||||
|
||||
// Create the notification message
|
||||
var notification = { "action": "msg", "type": "notify", "value": command.msg };
|
||||
|
||||
// Send the notification on all user sessions for this server
|
||||
for (var i in obj.parent.wssessions2) { try { obj.parent.wssessions2[i].send(JSON.stringify(notification)); } catch (ex) { } }
|
||||
|
||||
// TODO: Notify all sessions on other peers.
|
||||
|
||||
break;
|
||||
}
|
||||
case 'adduser':
|
||||
@ -879,7 +895,6 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
|
||||
// We only create Agent-less Intel AMT mesh (Type1), or Agent mesh (Type2)
|
||||
if ((command.meshtype == 1) || (command.meshtype == 2)) {
|
||||
// Create a type 1 agent-less Intel AMT mesh.
|
||||
obj.parent.crypto.randomBytes(48, function (err, buf) {
|
||||
meshid = 'mesh/' + domain.id + '/' + buf.toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
|
||||
var links = {};
|
||||
@ -1674,17 +1689,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
|
||||
// Check is 2-step login is supported
|
||||
const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly !== true) && (obj.args.nousers !== true));
|
||||
if ((twoStepLoginSupported == false) || (typeof command.otp != 'string')) break;
|
||||
if ((twoStepLoginSupported == false) || (typeof command.otp != 'string')) {
|
||||
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if Yubikey support is present
|
||||
if ((typeof domain.yubikey != 'object') || (typeof domain.yubikey.id != 'string') || (typeof domain.yubikey.secret != 'string')) break;
|
||||
// Check if Yubikey support is present or OTP no exactly 44 in length
|
||||
if ((typeof domain.yubikey != 'object') || (typeof domain.yubikey.id != 'string') || (typeof domain.yubikey.secret != 'string') || (command.otp.length != 44)) {
|
||||
ws.send(JSON.stringify({ action: 'otp-hkey-yubikey-add', result: false, name: command.name }));
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Check if command.otp is modhex encoded, reject if not.
|
||||
|
||||
// Query the YubiKey server to validate the OTP
|
||||
var yubikeyotp = require('yubikeyotp');
|
||||
var request = { otp: command.otp, id: domain.yubikey.id, key: domain.yubikey.secret, timestamp: true }
|
||||
if (domain.yubikey.proxy) { request.requestParams = { proxy: domain.yubikey.proxy }; }
|
||||
yubikeyotp.verifyOTP(request, function (err, results) {
|
||||
if (results.status == 'OK') {
|
||||
if ((results != null) && (results.status == 'OK')) {
|
||||
var keyIndex = obj.parent.crypto.randomBytes(4).readUInt32BE(0);
|
||||
var keyId = command.otp.substring(0, 12);
|
||||
if (user.otphkeys == null) { user.otphkeys = []; }
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meshcentral",
|
||||
"version": "0.2.8-d",
|
||||
"version": "0.2.8-g",
|
||||
"keywords": [
|
||||
"Remote Management",
|
||||
"Intel AMT",
|
||||
|
File diff suppressed because one or more lines are too long
@ -299,9 +299,14 @@
|
||||
<tr>
|
||||
<td class=h1></td>
|
||||
<td class=style14>
|
||||
|
||||
<input type=button onclick=showCreateNewAccountDialog() value="New Account..." />
|
||||
<input id=UserSearchInput type=text style=width:120px placeholder=Filter onchange=onUserSearchInputChanged() onkeyup=onUserSearchInputChanged() autocomplete=off onfocus=onUserSearchFocus(1) onblur=onUserSearchFocus(0) />
|
||||
<div style="float:right">
|
||||
<input type=button onclick=showUserBroadcastDialog() value="Broadcast" />
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<input type=button onclick=showCreateNewAccountDialog() value="New Account..." />
|
||||
<input id=UserSearchInput type=text style=width:120px placeholder=Filter onchange=onUserSearchInputChanged() onkeyup=onUserSearchInputChanged() autocomplete=off onfocus=onUserSearchFocus(1) onblur=onUserSearchFocus(0) />
|
||||
</div>
|
||||
</td>
|
||||
<td class=h2></td>
|
||||
</tr>
|
||||
@ -6349,6 +6354,17 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function showUserBroadcastDialog() {
|
||||
if (xxdialogMode) return;
|
||||
var x = 'Broadcast a message to all connected users.<textarea id=broadcastMessage value="" style=width:370px;height:100px;resize:none maxlength=256 /></textarea>';
|
||||
setDialogMode(2, "Broadcast Message", 3, showUserBroadcastDialogEx, x);
|
||||
Q('broadcastMessage').focus();
|
||||
}
|
||||
|
||||
function showUserBroadcastDialogEx() {
|
||||
meshserver.send({ action: 'userbroadcast', msg: Q('broadcastMessage').value });
|
||||
}
|
||||
|
||||
function showCreateNewAccountDialog() {
|
||||
if (xxdialogMode) return;
|
||||
var x = '';
|
||||
|
23
webserver.js
23
webserver.js
@ -1076,10 +1076,20 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
*/
|
||||
}
|
||||
|
||||
// Return true if it looks like we are using a real TLS certificate.
|
||||
function isTrustedCert() {
|
||||
if (obj.args.notls == true) return false; // We are not using TLS, so not trusted cert.
|
||||
if (obj.args.tlsoffload != null) return true; // We are using TLS offload, a real cert is likely used.
|
||||
if (obj.parent.config.letsencrypt != null) return true; // We are using Let's Encrypt, real cert in use.
|
||||
if (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) return false; // Our cert is issued by self-signed cert.
|
||||
if (obj.certificates.CommonName == 'un-configured') return false; // Out cert is named with a fake name
|
||||
return true; // This is a guess
|
||||
}
|
||||
|
||||
// Get the link to the root certificate if needed
|
||||
function getRootCertLink() {
|
||||
// Check if the HTTPS certificate is issued from MeshCentralRoot, if so, add download link to root certificate.
|
||||
if ((obj.args.notls == null) && (obj.tlsSniCredentials == null) && (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) && (obj.certificates.CommonName != 'un-configured')) { return '<a href=/MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>'; }
|
||||
if ((obj.args.notls == null) && (obj.args.tlsoffload == null) && (obj.parent.config.letsencrypt == null) && (obj.tlsSniCredentials == null) && (obj.certificates.WebIssuer.indexOf('MeshCentralRoot-') == 0) && (obj.certificates.CommonName != 'un-configured')) { return '<a href=/MeshServerRootCert.cer title="Download the root certificate for this server">Root Certificate</a>'; }
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -2193,18 +2203,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
// Two more headers to take a look at:
|
||||
// 'Public-Key-Pins': 'pin-sha256="X3pGTSOuJeEVw989IJ/cEtXUEmy52zs1TZQrU06KUKg="; max-age=10'
|
||||
// 'strict-transport-security': 'max-age=31536000; includeSubDomains'
|
||||
/*
|
||||
var headers = null;
|
||||
if (obj.args.notls) {
|
||||
if (isTrustedCert() == false) {
|
||||
// Default headers if no TLS is used
|
||||
headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
|
||||
//headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src http: ws: data: 'self';script-src http: 'unsafe-inline';style-src http: 'unsafe-inline'" };
|
||||
} else {
|
||||
// Default headers if TLS is used
|
||||
headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src https: wss: data: 'self';script-src https: 'unsafe-inline';style-src https: 'unsafe-inline'" };
|
||||
//headers = { 'Referrer-Policy': 'no-referrer', 'x-frame-options': 'SAMEORIGIN', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src https: wss: data: 'self';script-src https: 'unsafe-inline';style-src https: 'unsafe-inline'" };
|
||||
|
||||
// Set Strict-Transport-Security if we are using a trusted certificate or TLS offload.
|
||||
headers = { 'Strict-Transport-Security': 'max-age=31536000;includeSubDomains' };
|
||||
}
|
||||
if (parent.config.settings.accesscontrolalloworigin != null) { headers['Access-Control-Allow-Origin'] = parent.config.settings.accesscontrolalloworigin; }
|
||||
res.set(headers);
|
||||
*/
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user