| 1 | /** |
| 2 | * @description MeshCentral monitoring module |
| 3 | * @author Simon Smith |
| 4 | * @license Apache-2.0 |
| 5 | * @version v0.0.1 |
| 6 | */ |
| 7 | |
| 8 | "use strict"; |
| 9 | |
| 10 | module.exports.CreateMonitoring = function (parent, args) { |
| 11 | var obj = {}; |
| 12 | obj.args = args; |
| 13 | obj.parent = parent; |
| 14 | obj.express = require('express'); |
| 15 | obj.app = obj.express(); |
| 16 | obj.prometheus = null; |
| 17 | obj.app.disable('x-powered-by'); |
| 18 | obj.counterMetrics = { // Counter Metrics always start at 0 and increase but never decrease |
| 19 | RelayErrors: { description: "Relay Errors" }, // parent.webserver.relaySessionErrorCount |
| 20 | UnknownGroup: { description: "Unknown Group" }, // meshDoesNotExistCount |
| 21 | InvalidPKCSsignature: { description: "Invalid PKCS signature" }, // invalidPkcsSignatureCount |
| 22 | InvalidRSAsignature: { description: "Invalid RSA signature" }, // invalidRsaSignatureCount |
| 23 | InvalidJSON: { description: "Invalid JSON" }, // invalidJsonCount |
| 24 | UnknownAction: { description: "Unknown Action" }, // unknownAgentActionCount |
| 25 | BadWebCertificate: { description: "Bad Web Certificate" }, // agentBadWebCertHashCount |
| 26 | BadSignature: { description: "Bad Signature" }, // (agentBadSignature1Count + agentBadSignature2Count) |
| 27 | MaxSessionsReached: { description: "Max Sessions Reached" }, // agentMaxSessionHoldCount |
| 28 | UnknownDeviceGroup: { description: "Unknown Device Group" }, // (invalidDomainMeshCount + invalidDomainMesh2Count) |
| 29 | InvalidDeviceGroupType: { description: "Invalid Device Group Type" }, // invalidMeshTypeCount |
| 30 | DuplicateAgent: { description: "Duplicate Agent" }, // duplicateAgentCount |
| 31 | blockedUsers: { description: "Blocked Users" }, // blockedUsers |
| 32 | blockedAgents: { description: "Blocked Agents" }, // blockedAgents |
| 33 | }; |
| 34 | obj.gaugeMetrics = { // Gauge Metrics always start at 0 and can increase and decrease |
| 35 | ConnectedIntelAMT: { description: "Connected Intel AMT" }, // parent.parent.connectivityByNode[i].connectivity == 4 |
| 36 | ConnectedIntelAMTCira: { description: "Connected Intel AMT CIRA" }, // parent.mpsserver.ciraConnections[i].length |
| 37 | UserAccounts: { description: "User Accounts" }, // Object.keys(parent.webserver.users).length |
| 38 | DeviceGroups: { description: "Device Groups" }, // parent.webserver.meshes (ONLY WHERE deleted=null) |
| 39 | AgentSessions: { description: "Agent Sessions" }, // Object.keys(parent.webserver.wsagents).length |
| 40 | ConnectedUsers: { description: "Connected Users" }, // Object.keys(parent.webserver.wssessions).length |
| 41 | UsersSessions: { description: "Users Sessions" }, // Object.keys(parent.webserver.wssessions2).length |
| 42 | RelaySessions: { description: "Relay Sessions" }, // parent.webserver.relaySessionCount |
| 43 | RelayCount: { description: "Relay Count" } // Object.keys(parent.webserver.wsrelays).length30bb4fb74dfb758d36be52a7 |
| 44 | } |
| 45 | obj.collectors = []; |
| 46 | if (parent.config.settings.prometheus != null) { // Create Prometheus Monitoring Endpoint |
| 47 | if ((typeof parent.config.settings.prometheus == 'number') && ((parent.config.settings.prometheus < 1) || (parent.config.settings.prometheus > 65535))) { |
| 48 | console.log('Promethus port number is invalid, Prometheus metrics endpoint has be disabled'); |
| 49 | delete parent.config.settings.prometheus; |
| 50 | } else { |
| 51 | const port = ((typeof parent.config.settings.prometheus == 'number') ? parent.config.settings.prometheus : 9464); |
| 52 | obj.prometheus = require('prom-client'); |
| 53 | const collectDefaultMetrics = obj.prometheus.collectDefaultMetrics; |
| 54 | collectDefaultMetrics(); |
| 55 | for (const key in obj.gaugeMetrics) { |
| 56 | obj.gaugeMetrics[key].prometheus = new obj.prometheus.Gauge({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.gaugeMetrics[key].description }); |
| 57 | } |
| 58 | for (const key in obj.counterMetrics) { |
| 59 | obj.counterMetrics[key].prometheus = new obj.prometheus.Counter({ name: 'meshcentral_' + String(key).toLowerCase(), help: obj.counterMetrics[key].description }); |
| 60 | } |
| 61 | obj.app.get('/', function (req, res) { res.send('MeshCentral Prometheus server.'); }); |
| 62 | obj.app.listen(port, function () { |
| 63 | console.log('MeshCentral Prometheus server running on port ' + port + '.'); |
| 64 | obj.parent.updateServerState('prometheus-port', port); |
| 65 | }); |
| 66 | obj.app.get('/metrics', async (req, res) => { |
| 67 | try { |
| 68 | // Count the number of device groups that are not deleted |
| 69 | var activeDeviceGroups = 0; |
| 70 | for (var i in parent.webserver.meshes) { if (parent.webserver.meshes[i].deleted == null) { activeDeviceGroups++; } } // This is not ideal for performance, we want to dome something better. |
| 71 | var gauges = { |
| 72 | UserAccounts: Object.keys(parent.webserver.users).length, |
| 73 | DeviceGroups: activeDeviceGroups, |
| 74 | AgentSessions: Object.keys(parent.webserver.wsagents).length, |
| 75 | ConnectedUsers: Object.keys(parent.webserver.wssessions).length, |
| 76 | UsersSessions: Object.keys(parent.webserver.wssessions2).length, |
| 77 | RelaySessions: parent.webserver.relaySessionCount, |
| 78 | RelayCount: Object.keys(parent.webserver.wsrelays).length, |
| 79 | ConnectedIntelAMT: 0 |
| 80 | }; |
| 81 | if (parent.mpsserver != null) { |
| 82 | gauges.ConnectedIntelAMTCira = 0; |
| 83 | for (var i in parent.mpsserver.ciraConnections) { |
| 84 | gauges.ConnectedIntelAMTCira += parent.mpsserver.ciraConnections[i].length; |
| 85 | } |
| 86 | } |
| 87 | for (var i in parent.connectivityByNode) { |
| 88 | const node = parent.connectivityByNode[i]; |
| 89 | if (node && typeof node.connectivity !== 'undefined' && node.connectivity === 4) { gauges.ConnectedIntelAMT++; } |
| 90 | } |
| 91 | for (const key in gauges) { obj.gaugeMetrics[key].prometheus.set(gauges[key]); } |
| 92 | // Take a look at agent errors |
| 93 | var agentstats = parent.webserver.getAgentStats(); |
| 94 | const counters = { |
| 95 | RelayErrors: parent.webserver.relaySessionErrorCount, |
| 96 | UnknownGroup: agentstats.meshDoesNotExistCount, |
| 97 | InvalidPKCSsignature: agentstats.invalidPkcsSignatureCount, |
| 98 | InvalidRSAsignature: agentstats.invalidRsaSignatureCount, |
| 99 | InvalidJSON: agentstats.invalidJsonCount, |
| 100 | UnknownAction: agentstats.unknownAgentActionCount, |
| 101 | BadWebCertificate: agentstats.agentBadWebCertHashCount, |
| 102 | BadSignature: (agentstats.agentBadSignature1Count + agentstats.agentBadSignature2Count), |
| 103 | MaxSessionsReached: agentstats.agentMaxSessionHoldCount, |
| 104 | UnknownDeviceGroup: (agentstats.invalidDomainMeshCount + agentstats.invalidDomainMesh2Count), |
| 105 | InvalidDeviceGroupType: (agentstats.invalidMeshTypeCount + agentstats.invalidMeshType2Count), |
| 106 | DuplicateAgent: agentstats.duplicateAgentCount, |
| 107 | blockedUsers: parent.webserver.blockedUsers, |
| 108 | blockedAgents: parent.webserver.blockedAgents |
| 109 | }; |
| 110 | for (const key in counters) { obj.counterMetrics[key].prometheus.reset(); obj.counterMetrics[key].prometheus.inc(counters[key]); } |
| 111 | res.set('Content-Type', obj.prometheus.register.contentType); |
| 112 | await Promise.all(obj.collectors.map((collector) => (collector(req, res)))); |
| 113 | res.end(await obj.prometheus.register.metrics()); |
| 114 | } catch (ex) { |
| 115 | console.log(ex); |
| 116 | res.status(500).end(); |
| 117 | } |
| 118 | }); |
| 119 | } |
| 120 | } |
| 121 | return obj; |
| 122 | } |