Improved charts, added no2factorauth switch

This commit is contained in:
Ylian Saint-Hilaire 2019-03-26 14:11:51 -07:00
parent e4e517bffb
commit 9828179321
9 changed files with 76 additions and 25 deletions

4
db.js
View File

@ -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) {

View File

@ -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(); });

View File

@ -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

20
package-lock.json generated
View File

@ -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": {

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.3.1-h",
"version": "0.3.1-i",
"keywords": [
"Remote Management",
"Intel AMT",

View File

@ -30,6 +30,7 @@
},
"_TlsOffload": true,
"_MpsTlsOffload": true,
"_No2FactorAuth": true,
"_WebRtConfig": {
"iceServers": [
{ "urls": "stun:stun.services.mozilla.com" },

File diff suppressed because one or more lines are too long

View File

@ -759,9 +759,11 @@
<option value=168>Last week</option>
<option value=5040>Last 30 days</option>
</select>&nbsp;
<img src=images/link4.png height=10 width=10 title="Download data points (.csv)" style=cursor:pointer onclick=p40downloadEvents()>&nbsp;
</div>
<div>
&nbsp;<input value="Refresh" type="button" onclick="refreshServerTimelineStats()" />
&nbsp;<input id=p40log type="checkbox" onclick="updateServerTimelineHours()" />Log-X
</div>
</div>
<canvas id=serverMainStats style="height:calc(100vh - 250px);max-height:calc(100vh - 250px);width:100%"></canvas>
@ -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
//

View File

@ -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);