diff --git a/db.js b/db.js index a738fb95..61428627 100644 --- a/db.js +++ b/db.js @@ -124,12 +124,13 @@ module.exports.CreateDB = function (parent) { // Check if we need to reset indexes var indexesByName = {}, indexCount = 0; for (var i in indexes) { indexesByName[indexes[i].name] = indexes[i]; indexCount++; } - if ((indexCount != 2) || (indexesByName['ExpireTime1'] == null)) { + if ((indexCount != 3) || (indexesByName['ExpireTime1'] == null)) { // Reset all indexes console.log('Resetting server stats indexes...'); obj.serverstatsfile.dropIndexes(function (err) { // Create all indexes obj.serverstatsfile.createIndex({ "time": 1 }, { expireAfterSeconds: expireServerStatsSeconds, name: 'ExpireTime1' }); + obj.serverstatsfile.createIndex({ "expire": 1 }, { expireAfterSeconds: 0, name: 'ExpireTime2' }); // Auto-expire events }); } else if (indexesByName['ExpireTime1'].expireAfterSeconds != expireServerStatsSeconds) { // Reset the timeout index @@ -197,6 +198,7 @@ module.exports.CreateDB = function (parent) { obj.serverstatsfile = new Datastore({ filename: obj.parent.getConfigFilePath('meshcentral-stats.db'), autoload: true }); obj.serverstatsfile.persistence.setAutocompactionInterval(36000); obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days) + obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events } obj.SetupDatabase = function (func) { diff --git a/meshcentral.js b/meshcentral.js index e9766eae..030111e3 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -60,6 +60,7 @@ function CreateMeshCentralServer(config, args) { obj.serverKey = Buffer.from(obj.crypto.randomBytes(48), 'binary'); obj.loginCookieEncryptionKey = null; obj.serverSelfWriteAllowed = true; + obj.serverStatsCounter = Math.floor(Math.random() * 1000); obj.taskLimiter = obj.common.createTaskLimiterQueue(50, 20, 60); // (maxTasks, maxTaskTime, cleaningInterval) This is a task limiter queue to smooth out server work. try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { } // Fetch server version @@ -802,8 +803,19 @@ function CreateMeshCentralServer(config, args) { // Start collecting server stats every 5 minutes setInterval(function () { + obj.serverStatsCounter++; + var hours = 720; // Start with all events lasting 30 days. + if (((obj.serverStatsCounter) % 2) == 1) { hours = 3; } // Half of the event get removed after 3 hours. + else if ((math.floor(obj.serverStatsCounter / 2) % 2) == 1) { hours = 8; } // Another half of the event get removed after 8 hours. + else if ((math.floor(obj.serverStatsCounter / 4) % 2) == 1) { hours = 24; } // Another half of the event get removed after 24 hours. + else if ((math.floor(obj.serverStatsCounter / 8) % 2) == 1) { hours = 48; } // Another half of the event get removed after 48 hours. + else if ((math.floor(obj.serverStatsCounter / 16) % 2) == 1) { hours = 72; } // Another half of the event get removed after 72 hours. + var expire = new Date(); + t.setTime(t.getTime() + (60 * 60 * 1000 * hours)); + var data = { time: new Date(), + expire: expire, mem: process.memoryUsage(), //cpu: process.cpuUsage(), conn: { @@ -1616,7 +1628,13 @@ function getConfig(createSampleConfig) { function InstallModules(modules, func) { var missingModules = []; if (modules.length > 0) { - for (var i in modules) { try { var xxmodule = require(modules[i]); } catch (e) { missingModules.push(modules[i]); } } + for (var i in modules) { + try { + var xxmodule = require(modules[i]); + } catch (e) { + if (previouslyInstalledModules[modules[i]] !== true) { previouslyInstalledModules[modules[i]] = true; missingModules.push(modules[i]); } + } + } if (missingModules.length > 0) { InstallModule(missingModules.shift(), InstallModules, modules, func); } else { func(); } } } @@ -1641,6 +1659,7 @@ process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); // Load the really basic modules var meshserver = null; +var previouslyInstalledModules = { }; function mainStart(args) { // Check the NodeJS is version 6 or better. if (Number(process.version.match(/^v(\d+\.\d+)/)[1]) < 6) { console.log("MeshCentral requires Node v6.x or above, current version is " + process.version + "."); return; } @@ -1664,7 +1683,6 @@ function mainStart(args) { if (config.letsencrypt != null) { modules.push('greenlock'); modules.push('le-store-certbot'); modules.push('le-challenge-fs'); modules.push('le-acme-core'); } // Add Greenlock Modules if (config.settings.mongodb != null) { modules.push('mongojs'); } // Add MongoDB if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support - if (yubikey == true) { modules.push('yubikeyotp'); } // Add YubiKey OTP support // Get the current node version var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); @@ -1672,8 +1690,13 @@ function mainStart(args) { // If running NodeJS < 8, install "util.promisify" if (nodeVersion < 8) { modules.push('util.promisify'); } - // if not all SSPI, WebAuthn/FIDO2 or U2F support depending on the NodeJS version. FIDO2 does not work below NodeJS 8.x - if (allsspi == false) { modules.push('otplib'); if (nodeVersion >= 8) { modules.push('@davedoesdev/fido2-lib'); } else { modules.push('authdog'); } } + // Setup 2nd factor authentication + if (config.settings.no2factorauth !== true) { + // Setup YubiKey OTP if configured + if (yubikey == true) { modules.push('yubikeyotp'); } // Add YubiKey OTP support + // if not all SSPI, WebAuthn/FIDO2 or U2F support depending on the NodeJS version. FIDO2 does not work below NodeJS 8.x + if (allsspi == false) { modules.push('otplib'); if (nodeVersion >= 8) { modules.push('@davedoesdev/fido2-lib'); } else { modules.push('authdog'); } } + } // Install any missing modules and launch the server InstallModules(modules, function () { meshserver = CreateMeshCentralServer(config, args); meshserver.Start(); }); diff --git a/meshuser.js b/meshuser.js index 4fbc3649..01d72352 100644 --- a/meshuser.js +++ b/meshuser.js @@ -1896,6 +1896,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'otp-hkey-yubikey-add': { + if (parent.parent.config.settings.no2factorauth === true) return; + // Yubico API id and signature key can be requested from https://upgrade.yubico.com/getapikey/ var yubikeyotp = null; try { yubikeyotp = require('yubikeyotp'); } catch (ex) { } @@ -1945,6 +1947,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'otp-hkey-setup-request': { + if (parent.parent.config.settings.no2factorauth === true) return; + var authdoglib = null; try { authdoglib = require('authdog'); } catch (ex) { } @@ -1971,6 +1975,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'otp-hkey-setup-response': { + if (parent.parent.config.settings.no2factorauth === true) return; + var authdoglib = null; try { authdoglib = require('authdog'); } catch (ex) { } @@ -1997,6 +2003,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'webauthn-startregister': { + if (parent.parent.config.settings.no2factorauth === true) return; + // Check is 2-step login is supported const twoStepLoginSupported = ((domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.lanonly !== true) && (args.nousers !== true)); if ((twoStepLoginSupported == false) || (command.name == null) || (parent.f2l == null)) break; @@ -2019,6 +2027,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } case 'webauthn-endregister': { + if (parent.parent.config.settings.no2factorauth === true) return; if ((obj.webAuthnReqistrationRequest == null) || (parent.f2l == null)) return; // Figure out the origin diff --git a/package-lock.json b/package-lock.json index 671ebfa9..2a9d7b13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.1-a", + "version": "0.3.1-h", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -16,7 +16,7 @@ "jwk-to-pem": "2.0.1", "node-jose": "1.1.3", "pkijs": "2.1.58", - "psl": "1.1.29" + "psl": "1.1.31" } }, "@peculiar/asn1-schema": { @@ -50,9 +50,9 @@ } }, "@types/node": { - "version": "10.14.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.3.tgz", - "integrity": "sha512-2lhc7S28vo8FwR3Jv3Ifyd77AxEsx+Nl9ajWiac6/eWuvZ84zPK4RE05pfqcn3acIzlZDpQj5F1rIKQZX3ptLQ==" + "version": "10.14.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz", + "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg==" }, "accepts": { "version": "1.3.5", @@ -1638,9 +1638,9 @@ } }, "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha1-YPWA02AXC7cip5fMcEQR5tqFDGc=" + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, "punycode": { "version": "2.1.1", @@ -1652,7 +1652,7 @@ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.4.tgz", "integrity": "sha512-lBDyLfPIWZjxHr6Nnl83/iaZgVLczDcpEqWdqRnghzBKXifRU/7D5T6JPYWUAm0sJdFeF9+sNTKto6dj/3P/Kg==", "requires": { - "@types/node": "10.14.3", + "@types/node": "10.14.4", "tslib": "1.9.3" } }, @@ -1920,7 +1920,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "psl": "1.1.29", + "psl": "1.1.31", "punycode": "1.4.1" }, "dependencies": { diff --git a/package.json b/package.json index b52ece0b..72de9a3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.3.1-h", + "version": "0.3.1-i", "keywords": [ "Remote Management", "Intel AMT", diff --git a/sample-config.json b/sample-config.json index 8c8eb1ca..2affe5a5 100644 --- a/sample-config.json +++ b/sample-config.json @@ -30,6 +30,7 @@ }, "_TlsOffload": true, "_MpsTlsOffload": true, + "_No2FactorAuth": true, "_WebRtConfig": { "iceServers": [ { "urls": "stun:stun.services.mozilla.com" }, diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 32dedec6..56b56fed 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 3983c704..02eddbab 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -759,9 +759,11 @@   +  
  +  Log-X
@@ -7264,7 +7266,7 @@ maintainAspectRatio: false, scales: { xAxes: [{ type: 'time', time: { tooltipFormat: 'll HH:mm' }, display: true, scaleLabel: { display: false, labelString: '' } }], - yAxes: [{ display: true, scaleLabel: { display: true, labelString: '' } }] + yAxes: [{ type: 'linear', display: true, scaleLabel: { display: true, labelString: '' } }] } } }; @@ -7295,7 +7297,11 @@ } */ updateServerTimelineHours(); } - function updateServerTimelineHours() { serverTimelineConfig.options.scales.xAxes[0].time = { min: pastDate(Q('p40time').value) }; window.serverMainStats.update(); } + function updateServerTimelineHours() { + serverTimelineConfig.options.scales.yAxes[0].type = (Q('p40log').checked ? 'logarithmic' : 'linear'); + serverTimelineConfig.options.scales.xAxes[0].time = { min: pastDate(Q('p40time').value) }; + window.serverMainStats.update(); + } function setupServerTimelineStats() { window.serverMainStats = new Chart(document.getElementById('serverMainStats').getContext('2d'), serverTimelineConfig); } function updateServerTimelineStats() { @@ -7362,6 +7368,16 @@ window.serverMainStats.update(); } + function p40downloadEvents() { + var csv = "time, conn.agent, conn.users, conn.usersessions, conn.relaysession, conn.intelamt, mem.external, mem.heapused, mem.heaptotal, mem.rss\r\n"; + for (var i = 0; i < serverTimelineStats.length; i++) { + if (serverTimelineStats[i].conn && serverTimelineStats[i].mem) { + csv += new Date(serverTimelineStats[i].time) + ', ' + serverTimelineStats[i].conn.ca + ', ' + serverTimelineStats[i].conn.cu + ', ' + serverTimelineStats[i].conn.us + ', ' + serverTimelineStats[i].conn.rs + ', ' + (serverTimelineStats[i].conn.am ? serverTimelineStats[i].conn.am : '') + ', ' + serverTimelineStats[i].mem.external + ', ' + serverTimelineStats[i].mem.heapUsed + ', ' + serverTimelineStats[i].mem.heapTotal + ', ' + serverTimelineStats[i].mem.rss + '\r\n'; + } + } + saveAs(new Blob([csv], { type: "application/octet-stream" }), "ServerStats.csv"); + } + // // POPUP DIALOG // diff --git a/webserver.js b/webserver.js index 667caed3..7be5cce8 100644 --- a/webserver.js +++ b/webserver.js @@ -328,12 +328,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Return true if this user has 2-step auth active function checkUserOneTimePasswordRequired(domain, user) { - return ((user.otpsecret != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0))); + return ((parent.config.settings.no2factorauth !== true) && ((user.otpsecret != null) || ((user.otphkeys != null) && (user.otphkeys.length > 0)))); } // Check the 2-step auth token function checkUserOneTimePassword(req, domain, user, token, hwtoken, func) { - const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true)); + const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true) && (parent.config.settings.no2factorauth !== true)); if (twoStepLoginSupported == false) { func(true); return; }; // Check hardware key @@ -1157,19 +1157,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { if (obj.args.nousers == true) { features += 0x00000004; } // Single user mode if (domain.userQuota == -1) { features += 0x00000008; } // No server files mode if (obj.args.mpstlsoffload) { features += 0x00000010; } // No mutual-auth CIRA - if ((parent.config != null) && (parent.config.settings != null) && (parent.config.settings.allowframing == true)) { features += 0x00000020; } // Allow site within iframe + if (parent.config.settings.allowframing == true) { features += 0x00000020; } // Allow site within iframe if ((obj.parent.mailserver != null) && (obj.parent.certificates.CommonName != null) && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.lanonly != true)) { features += 0x00000040; } // Email invites if (obj.args.webrtc == true) { features += 0x00000080; } // Enable WebRTC (Default false for now) if (obj.args.clickonce !== false) { features += 0x00000100; } // Enable ClickOnce (Default true) if (obj.args.allowhighqualitydesktop == true) { features += 0x00000200; } // Enable AllowHighQualityDesktop (Default false) if (obj.args.lanonly == true || obj.args.mpsport == 0) { features += 0x00000400; } // No CIRA if ((obj.parent.serverSelfWriteAllowed == true) && (user != null) && (user.siteadmin == 0xFFFFFFFF)) { features += 0x00000800; } // Server can self-write (Allows self-update) - if ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true)) { features += 0x00001000; } // 2-step login supported + if ((parent.config.settings.no2factorauth !== true) && (domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true)) { features += 0x00001000; } // 2-step login supported if (domain.agentnoproxy === true) { features += 0x00002000; } // Indicates that agents should be installed without using a HTTP proxy - if (domain.yubikey && domain.yubikey.id && domain.yubikey.secret) { features += 0x00004000; } // Indicates Yubikey support + if ((parent.config.settings.no2factorauth !== true) && domain.yubikey && domain.yubikey.id && domain.yubikey.secret) { features += 0x00004000; } // Indicates Yubikey support if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints - if (obj.f2l != null) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support + if ((parent.config.settings.no2factorauth !== true) && (obj.f2l != null)) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support // Create a authentication cookie const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey);