diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index 544fbfc1..a8ce4280 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -159,7 +159,7 @@ "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\"" }, "authLog": { "type": "string", "default": null, "description": "File path and name of the authentication log to be created. This log can be parsed by Fail2ban." }, "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." }, - "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." }, + "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." }, "manageCrossDomain": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Users in this list are allowed to manage all users in all domains." }, "localDiscovery": { "type": "object", @@ -195,6 +195,16 @@ }, "required": [ "iceServers" ] }, + "crowdsec": { + "type": "object", + "additionalProperties": true, + "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", + "properties": { + "url": { "type": "string", "description": "The URL of your LAPI instance. Ex: http://localhost:8080" }, + "apiKey": { "type": "string", "description": "The bouncer key (generated via cscli)" } + }, + "required": [ "url", "apiKey" ] + }, "autoBackup": { "type": "object", "properties": { diff --git a/meshcentral.js b/meshcentral.js index a1474d54..83968a05 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -698,7 +698,7 @@ function CreateMeshCentralServer(config, args) { obj.args = args = config2.settings; // Lower case all keys in the config file - obj.common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']); + obj.common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']); // Grad some of the values from the original config.json file if present. if ((config.settings.vault != null) && (config2.settings != null)) { config2.settings.vault = config.settings.vault; } @@ -1196,7 +1196,7 @@ function CreateMeshCentralServer(config, args) { for (i in args) { config2.settings[i] = args[i]; } // Lower case all keys in the config file - common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']); + common.objKeysToLower(config2, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']); // Grad some of the values from the original config.json file if present. config2['mysql'] = config['mysql']; @@ -1219,7 +1219,7 @@ function CreateMeshCentralServer(config, args) { }; // Time to start the server of real. - obj.StartEx1b = function () { + obj.StartEx1b = async function () { var i; // Setup certificate operations @@ -1232,6 +1232,12 @@ function CreateMeshCentralServer(config, args) { }) } + // Start CrowdSec bouncer if needed: https://www.crowdsec.net/ + if (typeof obj.args.crowdsec == 'object') { + const expressCrowdsecBouncer = require("@crowdsec/express-bouncer"); + try { obj.crowdsecMiddleware = await expressCrowdsecBouncer(obj.args.crowdsec); } catch (ex) { delete obj.crowdsecMiddleware; } + } + // Check if self update is allowed. If running as a Windows service, self-update is not possible. if (obj.fs.existsSync(obj.path.join(__dirname, 'daemon'))) { obj.serverSelfWriteAllowed = false; } @@ -3515,7 +3521,7 @@ function getConfig(createSampleConfig) { // Lower case all keys in the config file try { - require('./common.js').objKeysToLower(config, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders']); + require('./common.js').objKeysToLower(config, ['ldapoptions', 'defaultuserwebstate', 'forceduserwebstate', 'httpheaders', 'crowdsec']); } catch (ex) { console.log('CRITICAL ERROR: Unable to access the file \"./common.js\".\r\nCheck folder & file permissions.'); process.exit(); @@ -3713,6 +3719,7 @@ function mainStart() { if (nodemailer || (config.smtp != null) || (config.sendmail != null)) { modules.push('nodemailer'); } // Add SMTP support if (sendgrid || (config.sendgrid != null)) { modules.push('@sendgrid/mail'); } // Add SendGrid support if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support + if (typeof config.settings.crowdsec == 'object') { modules.push('@crowdsec/express-bouncer'); } // Add CrowdSec bounser module (https://www.npmjs.com/package/@crowdsec/express-bouncer) // Setup encrypted zip support if needed if (config.settings.autobackup && config.settings.autobackup.zippassword) { diff --git a/package.json b/package.json index 2b69cbac..c9460d1f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "sample-config-advanced.json" ], "dependencies": { + "@crowdsec/express-bouncer": "^0.1.0", + "@yetzt/nedb": "^1.8.0", "archiver": "^5.3.1", "body-parser": "^1.19.0", "cbor": "~5.2.0", @@ -45,13 +47,21 @@ "express": "^4.17.0", "express-handlebars": "^5.3.5", "express-ws": "^4.0.0", + "image-size": "^1.0.1", "ipcheck": "^0.1.0", + "loadavg-windows": "^1.1.1", "minimist": "^1.2.5", "multiparty": "^4.2.1", - "@yetzt/nedb": "^1.8.0", "node-forge": "^1.0.0", + "node-windows": "^0.1.4", + "otplib": "^10.2.3", + "pg": "^8.7.1", + "pgtools": "^0.3.2", + "ssh2": "^1.11.0", + "web-push": "^3.5.0", "ws": "^5.2.3", - "yauzl": "^2.10.0" + "yauzl": "^2.10.0", + "yubikeyotp": "^0.2.0" }, "engines": { "node": ">=10.0.0" diff --git a/redirserver.js b/redirserver.js index 54f69b30..2995a716 100644 --- a/redirserver.js +++ b/redirserver.js @@ -39,6 +39,9 @@ module.exports.CreateRedirServer = function (parent, db, args, func) { res.redirect('https://' + host + ':' + httpsPort + req.url); } + // Setup CrowdSec bouncer middleware if needed + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); } + /* // Return the current domain of the request function getDomain(req) { diff --git a/sample-config-advanced.json b/sample-config-advanced.json index caf6769e..79d8c998 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -154,6 +154,10 @@ "trustedFqdn": "sample.com", "ip": "192.168.1.1" }, + "_crowdsec": { + "url": "http://localhost:8080", + "apiKey": "BOUNCER_API_KEY" + }, "_plugins": { "enabled": true } }, "_domaindefaults": { diff --git a/webrelayserver.js b/webrelayserver.js index 25355740..831cc6b8 100644 --- a/webrelayserver.js +++ b/webrelayserver.js @@ -36,6 +36,9 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, var tlsSessionStoreCount = 0; // Number of cached TLS session information in store. function serverStart() { + // Setup CrowdSec bouncer middleware if needed + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); } + if (args.trustedproxy) { // Reverse proxy should add the "X-Forwarded-*" headers try { diff --git a/webserver.js b/webserver.js index fbf20ddd..055a9706 100644 --- a/webserver.js +++ b/webserver.js @@ -5706,9 +5706,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF obj.tlsAltServer.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); }); obj.expressWsAlt = require('express-ws')(obj.agentapp, obj.tlsAltServer, { wsOptions: { perMessageDeflate: (args.wscompression === true) } }); } + if (parent.crowdsecMiddleware != null) { obj.agentapp.use(parent.crowdsecMiddleware); } // Setup CrowdSec bouncer middleware if needed } // Setup middleware + if (parent.crowdsecMiddleware != null) { obj.app.use(parent.crowdsecMiddleware); } // Setup CrowdSec bouncer middleware if needed obj.app.engine('handlebars', obj.exphbs({ defaultLayout: false })); obj.app.set('view engine', 'handlebars'); if (obj.args.trustedproxy) {