From 91282677cdb77bbef0f9136d0df9f0053a8fb4a9 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 11 Feb 2019 14:41:15 -0800 Subject: [PATCH] Added user account, user session and agent session per-domain limits. --- certoperations.js | 6 ++- db.js | 2 +- meshagent.js | 10 ++++ meshcentral.js | 1 + meshuser.js | 16 ++++++- package.json | 2 +- sample-config.json | 5 ++ views/default.handlebars | 14 ++++-- views/login-mobile.handlebars | 51 ++++++++++++++++++++- views/login.handlebars | 48 ++++++++++++++++++- webserver.js | 86 +++++++++++++++++++++++++++-------- 11 files changed, 212 insertions(+), 29 deletions(-) diff --git a/certoperations.js b/certoperations.js index 0379336a..e23f2005 100644 --- a/certoperations.js +++ b/certoperations.js @@ -112,7 +112,9 @@ module.exports.CertificateOperations = function (parent) { // Create a self-signed certificate obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization, strong) { - var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048); + // TODO: Use Async key generation to use web workers and go a lot faster. + // rsa.generateKeyPair({ bits: 3072, e: 0x10001, workers: -1 }, function (err, keypair) { /*keypair.privateKey, keypair.publicKey*/ }); + var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 }); var cert = obj.pki.createCertificate(); cert.publicKey = keys.publicKey; cert.serialNumber = String(Math.floor((Math.random() * 100000) + 1)); @@ -136,7 +138,7 @@ module.exports.CertificateOperations = function (parent) { // Issue a certificate from a root obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) { - var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048); + var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 }); var cert = obj.pki.createCertificate(); cert.publicKey = keys.publicKey; cert.serialNumber = String(Math.floor((Math.random() * 100000) + 1)); diff --git a/db.js b/db.js index 6f476a0e..a8cfe293 100644 --- a/db.js +++ b/db.js @@ -175,7 +175,7 @@ module.exports.CreateDB = function (parent) { obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }).exec(func); } else { obj.file.find({ type: 'power', node: { $in: ['*', nodeid] } }).sort({ time: 1 }, func); } }; obj.getLocalAmtNodes = function (func) { obj.file.find({ type: 'node', host: { $exists: true, $ne: null }, intelamt: { $exists: true } }, func); }; obj.getAmtUuidNode = function (meshid, uuid, func) { obj.file.find({ type: 'node', meshid: meshid, 'intelamt.uuid': uuid }, func); }; - obj.isMaxType = function (max, type, func) { if (max == null) { func(false); } else { obj.file.count({ type: type }, function (err, count) { func((err != null) || (count > max)); }); } } + obj.isMaxType = function (max, type, domainid, func) { if (max == null) { func(false); } else { obj.file.count({ type: type, domain: domainid }, function (err, count) { func((err != null) || (count > max)); }); } } // Read a configuration file from the database obj.getConfigFile = function (path, func) { obj.Get('cfile/' + path, func); } diff --git a/meshagent.js b/meshagent.js index 6fe73f05..1f178464 100644 --- a/meshagent.js +++ b/meshagent.js @@ -368,6 +368,16 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection) return; obj.pendingCompleteAgentConnection = true; + // Check if we have too many agent sessions + if (typeof domain.limits.maxagentsessions == 'number') { + // Count the number of agent sessions for this domain + var domainAgentSessionCount = 0; + for (var i in obj.parent.wsagents) { if (obj.parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } } + + // Check if we have too many user sessions + 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. diff --git a/meshcentral.js b/meshcentral.js index d063cef6..8681be2d 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -437,6 +437,7 @@ function CreateMeshCentralServer(config, args) { var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains for (i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } } for (i in obj.config.domains) { + if (obj.config.domains[i].limits == null) { obj.config.domains[i].limits = {}; } if (obj.config.domains[i].dns == null) { obj.config.domains[i].url = (i == '') ? '/' : ('/' + i + '/'); } else { obj.config.domains[i].url = '/'; } obj.config.domains[i].id = i; if (typeof obj.config.domains[i].userallowedip == 'string') { if (obj.config.domains[i].userallowedip == '') { obj.config.domains[i].userallowedip = null; } else { obj.config.domains[i].userallowedip = obj.config.domains[i].userallowedip.split(','); } } diff --git a/meshuser.js b/meshuser.js index 51f40f98..bb9121fd 100644 --- a/meshuser.js +++ b/meshuser.js @@ -127,6 +127,20 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Check if the user is logged in if (user == null) { try { obj.ws.close(); } catch (e) { } return; } + // Check if we have exceeded the user session limit + if (typeof domain.limits.maxusersessions == 'number') { + // Count the number of user sessions for this domain + var domainUserSessionCount = 0; + for (var i in obj.parent.wssessions2) { if (obj.parent.wssessions2[i].domainid == domain.id) { domainUserSessionCount++; } } + + // Check if we have too many user sessions + if (domainUserSessionCount >= domain.limits.maxusersessions) { + ws.send(JSON.stringify({ action: 'stopped', msg: 'Session count exceed' })); + try { obj.ws.close(); } catch (e) { } + return; + } + } + // Associate this websocket session with the web session obj.ws.userid = req.session.userid; obj.ws.domainid = domain.id; @@ -643,7 +657,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (obj.parent.users[newuserid]) break; // Account already exists // Check if we exceed the maximum number of user accounts - obj.db.isMaxType(domain.maxaccounts, 'user', function (maxExceed) { + obj.db.isMaxType(domain.limits.maxuseraccounts, 'user', domain.id, function (maxExceed) { if (maxExceed) { // Account count exceed, do notification diff --git a/package.json b/package.json index 48b33ab3..1f1e4243 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.7-u", + "version": "0.2.7-v", "keywords": [ "Remote Management", "Intel AMT", diff --git a/sample-config.json b/sample-config.json index a76ba3b4..8f6ec74d 100644 --- a/sample-config.json +++ b/sample-config.json @@ -41,6 +41,11 @@ "_UserBlockedIP": "127.0.0.1,::1,192.168.0.100", "_AgentAllowedIP": "192.168.0.100/24", "_AgentBlockedIP": "127.0.0.1,::1", + "_Limits": { + "MaxUserAccounts": 100, + "MaxUserSessions": 100, + "MaxAgentSessions": 100 + }, "_yubikey": { "id": "0000", "secret": "xxxxxxxxxxxxxxxxxxxxx", "_proxy": "http://myproxy.domain.com:80" }, }, "customer1": { diff --git a/views/default.handlebars b/views/default.handlebars index 6705a5a1..760db9b2 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -855,6 +855,7 @@