Added support for Crowdsec, an open-source and collaborative IPS (Intrusion Prevention System)

Ylian Saint-Hilaire committed Jul 6, 2022 at 20:34 UTC 947d9094cbc2b52b4cba8afdc54963bb9d21fb6a
7 files changed +46 -7
meshcentral-config-schema.json
+11 -1
@@ -159,7 +159,7 @@
159 "agentBlockedIP": { "type": [ "string", "array" ], "default": null, "description": "When set, agents from these denied IP address ranges will not be able to connect to the server. Example: \"192.168.2.100,192.168.1.0/24\"" },
160 "authLog": { "type": "string", "default": null, "description": "File path and name of the authentication log to be created. This log can be parsed by Fail2ban." },
161 "InterUserMessaging": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Users in this list are allowed to send and receive inter-user messages. This can be used to implement bots or other software where MeshCentral is used as data transport. See \"interuser\" websocket command in the code." },
162 - "manageAllDeviceGroups": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Users in this list are allowed to see and manage all device groups within their domain." },
162 + "manageAllDeviceGroups": { "type": "array", "uniqueItems": true, "items": { "type": " string" }, "description": "Users in this list are allowed to see and manage all device groups within their domain." },
163 "manageCrossDomain": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Users in this list are allowed to manage all users in all domains." },
164 "localDiscovery": {
165 "type": "object",
@@ -195,6 +195,16 @@
195 },
196 "required": [ "iceServers" ]
197 },
198 + "crowdsec": {
199 + "type": "object",
200 + "additionalProperties": true,
201 + "description": "Enabled the MeshCentral built-in Crowdsec bouncer. This section is passed directly to the bouncer, all of the settings are documented at https://www.npmjs.com/package/@crowdsec/express-bouncer",
202 + "properties": {
203 + "url": { "type": "string", "description": "The URL of your LAPI instance. Ex: http://localhost:8080" },
204 + "apiKey": { "type": "string", "description": "The bouncer key (generated via cscli)" }
205 + },
206 + "required": [ "url", "apiKey" ]
207 + },
208 "autoBackup": {
209 "type": "object",
210 "properties": {
meshcentral.js
+11 -4
@@ -698,7 +698,7 @@ function CreateMeshCentralServer(config, args) {
698 obj.args = args = config2.settings;
699
700 // Lower case all keys in the config file
701 - obj.common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']);
701 + obj.common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']);
702
703 // Grad some of the values from the original config.json file if present.
704 if ((config.settings.vault != null) && (config2.settings != null)) { config2.settings.vault = config.settings.vault; }
@@ -1196,7 +1196,7 @@ function CreateMeshCentralServer(config, args) {
1196 for (i in args) { config2.settings[i] = args[i]; }
1197
1198 // Lower case all keys in the config file
1199 - common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']);
1199 + common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']);
1200
1201 // Grad some of the values from the original config.json file if present.
1202 config2['mysql'] = config['mysql'];
@@ -1219,7 +1219,7 @@ function CreateMeshCentralServer(config, args) {
1219 };
1220
1221 // Time to start the server of real.
1222 - obj.StartEx1b = function () {
1222 + obj.StartEx1b = async function () {
1223 var i;
1224
1225 // Setup certificate operations
@@ -1232,6 +1232,12 @@ function CreateMeshCentralServer(config, args) {
1232 })
1233 }
1234
1235 + // Start CrowdSec bouncer if needed: https://www.crowdsec.net/
1236 + if (typeof obj.args.crowdsec == 'object') {
1237 + const expressCrowdsecBouncer = require("@crowdsec/express-bouncer");
1238 + try { obj.crowdsecMiddleware = await expressCrowdsecBouncer(obj.args.crowdsec); } catch (ex) { delete obj.crowdsecMiddleware; }
1239 + }
1240 +
1241 // Check if self update is allowed. If running as a Windows service, self-update is not possible.
1242 if (obj.fs.existsSync(obj.path.join(__dirname, 'daemon'))) { obj.serverSelfWriteAllowed = false; }
1243
@@ -3515,7 +3521,7 @@ function getConfig(createSampleConfig) {
3521
3522 // Lower case all keys in the config file
3523 try {
3518 - require('./common.js').objKeysToLower(config, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']);
3524 + require('./common.js').objKeysToLower(config, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']);
3525 } catch (ex) {
3526 console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.');
3527 process.exit();
@@ -3713,6 +3719,7 @@ function mainStart() {
3719 if (nodemailer || (config.smtp != null) || (config.sendmail != null)) { modules.push('nodemailer'); } // Add SMTP support
3720 if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support
3721 if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support
3722 + if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer)
3723
3724 // Setup encrypted zip support if needed
3725 if (config.settings.autobackup && config.settings.autobackup.zippassword) {
package.json
+12 -2
@@ -37,6 +37,8 @@
37 "sample-config-advanced.json"
38 ],
39 "dependencies": {
40 + "@crowdsec/express-bouncer": "^0.1.0",
41 + "@yetzt/nedb": "^1.8.0",
42 "archiver": "^5.3.1",
43 "body-parser": "^1.19.0",
44 "cbor": "~5.2.0",
@@ -45,13 +47,21 @@
47 "express": "^4.17.0",
48 "express-handlebars": "^5.3.5",
49 "express-ws": "^4.0.0",
50 + "image-size": "^1.0.1",
51 "ipcheck": "^0.1.0",
52 + "loadavg-windows": "^1.1.1",
53 "minimist": "^1.2.5",
54 "multiparty": "^4.2.1",
51 - "@yetzt/nedb": "^1.8.0",
55 "node-forge": "^1.0.0",
56 + "node-windows": "^0.1.4",
57 + "otplib": "^10.2.3",
58 + "pg": "^8.7.1",
59 + "pgtools": "^0.3.2",
60 + "ssh2": "^1.11.0",
61 + "web-push": "^3.5.0",
62 "ws": "^5.2.3",
54 - "yauzl": "^2.10.0"
63 + "yauzl": "^2.10.0",
64 + "yubikeyotp": "^0.2.0"
65 },
66 "engines": {
67 "node": ">=10.0.0"
redirserver.js
+3
@@ -39,6 +39,9 @@ module.exports.CreateRedirServer = function (parent, db, args, func) {
39 res.redirect('https://' + host + ':' + httpsPort + req.url);
40 }
41
42 + // Setup CrowdSec bouncer middleware if needed
43 + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); }
44 +
45 /*
46 // Return the current domain of the request
47 function getDomain(req) {
sample-config-advanced.json
+4
@@ -154,6 +154,10 @@
154 "trustedFqdn": "sample.com",
155 "ip": "192.168.1.1"
156 },
157 + "_crowdsec": {
158 + "url": "http://localhost:8080",
159 + "apiKey": "BOUNCER_API_KEY"
160 + },
161 "_plugins": { "enabled": true }
162 },
163 "_domaindefaults": {
webrelayserver.js
+3
@@ -36,6 +36,9 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates,
36 var tlsSessionStoreCount = 0; // Number of cached TLS session information in store.
37
38 function serverStart() {
39 + // Setup CrowdSec bouncer middleware if needed
40 + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); }
41 +
42 if (args.trustedproxy) {
43 // Reverse proxy should add the "X-Forwarded-*" headers
44 try {
webserver.js
+2
@@ -5706,9 +5706,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
5706 obj.tlsAltServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });
5707 obj.expressWsAlt = require('express-ws')(obj.agentapp, obj.tlsAltServer, { wsOptions: { perMessageDeflate: (args.wscompression === true) } });
5708 }
5709 + if (parent.crowdsecMiddleware != null) { obj.agentapp.use(parent.crowdsecMiddleware); } // Setup CrowdSec bouncer middleware if needed
5710 }
5711
5712 // Setup middleware
5713 + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); } // Setup CrowdSec bouncer middleware if needed
5714 obj.app.engine('handlebars', obj.exphbs({ defaultLayout: false }));
5715 obj.app.set('view engine', 'handlebars');
5716 if (obj.args.trustedproxy) {