mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	Many CrowdSec improvements.
This commit is contained in:
		
							parent
							
								
									32d3d24649
								
							
						
					
					
						commit
						40bc91b6f3
					
				
					 3 changed files with 159 additions and 7 deletions
				
			
		
							
								
								
									
										131
									
								
								crowdsec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								crowdsec.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
module.exports.CreateCrowdSecBouncer = function (parent, config) {
 | 
			
		||||
    const obj = {};
 | 
			
		||||
 | 
			
		||||
    // Setup constants
 | 
			
		||||
    const { getLogger } = require('@crowdsec/express-bouncer/src/nodejs-bouncer/lib/logger');
 | 
			
		||||
    const { configure, renderBanWall, testConnectionToCrowdSec, getRemediationForIp } = require('@crowdsec/express-bouncer/src/nodejs-bouncer');
 | 
			
		||||
    const applyCaptcha = require('@crowdsec/express-bouncer/src/express-crowdsec-middleware/lib/captcha');
 | 
			
		||||
    const { BYPASS_REMEDIATION, CAPTCHA_REMEDIATION, BAN_REMEDIATION } = require('@crowdsec/express-bouncer/src/nodejs-bouncer/lib/constants');
 | 
			
		||||
    const svgCaptcha = require('svg-captcha');
 | 
			
		||||
    const { renderCaptchaWall } = require('@crowdsec/express-bouncer/src/nodejs-bouncer');
 | 
			
		||||
 | 
			
		||||
    // Current captcha state
 | 
			
		||||
    const currentCaptchaIpList = {};
 | 
			
		||||
 | 
			
		||||
    // Set the default values
 | 
			
		||||
    if (typeof config.userAgent != 'string') { config.userAgent = "CrowdSec Express-NodeJS bouncer/v0.0.1"; }
 | 
			
		||||
    if (typeof config.timeout != 'number') { config.timeout = 2000; }
 | 
			
		||||
    if (typeof config.fallbackRemediation != 'number') { config.fallbackRemediation = BAN_REMEDIATION; }
 | 
			
		||||
    if (typeof config.maxRemediation != 'number') { config.maxRemediation = BAN_REMEDIATION; }
 | 
			
		||||
    if (typeof config.captchaGenerationCacheDuration != 'number') { config.captchaGenerationCacheDuration = 60 * 1000; }
 | 
			
		||||
    if (typeof config.captchaResolutionCacheDuration != 'number') { config.captchaResolutionCacheDuration = 30 * 60 * 1000; }
 | 
			
		||||
    if (typeof config.captchaTexts != 'object') { config.captchaTexts = {}; }
 | 
			
		||||
    if (typeof config.banTexts != 'object') { config.banTexts = {}; }
 | 
			
		||||
    if (typeof config.colors != 'object') { config.colors = {}; }
 | 
			
		||||
    if (typeof config.hideCrowdsecMentions != 'boolean') { config.hideCrowdsecMentions = false; }
 | 
			
		||||
    if (typeof config.customCss != 'string') { config.customCss = ''; }
 | 
			
		||||
    if (typeof config.bypass != 'boolean') { config.bypass = false; }
 | 
			
		||||
    if (typeof config.trustedRangesForIpForwarding != 'object') { config.trustedRangesForIpForwarding = []; }
 | 
			
		||||
    if (typeof config.customLogger != 'object') { config.customLogger = null; }
 | 
			
		||||
    if (typeof config.bypassConnectionTest != 'boolean') { config.bypassConnectionTest = false; }
 | 
			
		||||
 | 
			
		||||
    // Setup the logger
 | 
			
		||||
    var logger = config.customLogger ? config.customLogger : getLogger();
 | 
			
		||||
 | 
			
		||||
    // Configure the bouncer
 | 
			
		||||
    configure({
 | 
			
		||||
        url: config.url,
 | 
			
		||||
        apiKey: config.apiKey,
 | 
			
		||||
        userAgent: config.userAgent,
 | 
			
		||||
        timeout: config.timeout,
 | 
			
		||||
        fallbackRemediation: config.fallbackRemediation,
 | 
			
		||||
        maxRemediation: config.maxRemediation,
 | 
			
		||||
        captchaTexts: config.captchaTexts,
 | 
			
		||||
        banTexts: config.banTexts,
 | 
			
		||||
        colors: config.colors,
 | 
			
		||||
        hideCrowdsecMentions: config.hideCrowdsecMentions,
 | 
			
		||||
        customCss: config.customCss
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Test connectivity
 | 
			
		||||
    obj.testConnectivity = async function() { return (await testConnectionToCrowdSec())['success']; }
 | 
			
		||||
 | 
			
		||||
    // Process a web request
 | 
			
		||||
    obj.process = async function (domain, req, res, next) {
 | 
			
		||||
        try {
 | 
			
		||||
            const remediation = await getRemediationForIp(req.clientIp);
 | 
			
		||||
            //console.log('CrowdSec', req.clientIp, remediation, req.url);
 | 
			
		||||
            switch (remediation) {
 | 
			
		||||
                case BAN_REMEDIATION:
 | 
			
		||||
                    const banWallTemplate = await renderBanWall();
 | 
			
		||||
                    res.status(403);
 | 
			
		||||
                    res.send(banWallTemplate);
 | 
			
		||||
                    return true;
 | 
			
		||||
                case CAPTCHA_REMEDIATION:
 | 
			
		||||
                    if ((currentCaptchaIpList[req.clientIp] == null) || (currentCaptchaIpList[req.clientIp].resolved !== true)) {
 | 
			
		||||
                        var domainCaptchaUrl = ((domain != null) && (domain.id != '') && (domain.dns == null)) ? ('/' + domain.id + '/captcha.ashx') : '/captcha.ashx';
 | 
			
		||||
                        if (req.url != domainCaptchaUrl) { res.redirect(domainCaptchaUrl); return true; }
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (ex) { }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process a captcha request
 | 
			
		||||
    obj.applyCaptcha = async function (req, res, next) {
 | 
			
		||||
        await applyCaptchaEx(req.clientIp, req, res, next, config.captchaGenerationCacheDuration, config.captchaResolutionCacheDuration, logger);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Process a captcha request
 | 
			
		||||
    async function applyCaptchaEx(ip, req, res, next, captchaGenerationCacheDuration, captchaResolutionCacheDuration, loggerInstance) {
 | 
			
		||||
        logger = loggerInstance;
 | 
			
		||||
        let error = false;
 | 
			
		||||
 | 
			
		||||
        if (currentCaptchaIpList[ip] == null) {
 | 
			
		||||
            generateCaptcha(ip, captchaGenerationCacheDuration);
 | 
			
		||||
        } else {
 | 
			
		||||
            if (currentCaptchaIpList[ip] && currentCaptchaIpList[ip].resolved) {
 | 
			
		||||
                logger.debug({ type: 'CAPTCHA_ALREADY_SOLVED', ip });
 | 
			
		||||
                next();
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (req.body && req.body.crowdsec_captcha) {
 | 
			
		||||
                    if (req.body.refresh === '1') { generateCaptcha(ip, captchaGenerationCacheDuration); }
 | 
			
		||||
                    if (req.body.phrase !== '') {
 | 
			
		||||
                        if (currentCaptchaIpList[ip].text === req.body.phrase) {
 | 
			
		||||
                            currentCaptchaIpList[ip].resolved = true;
 | 
			
		||||
                            setTimeout(function() { if (currentCaptchaIpList[ip]) { delete currentCaptchaIpList[ip]; } }, captchaResolutionCacheDuration);
 | 
			
		||||
                            res.redirect(req.originalUrl);
 | 
			
		||||
                            logger.info({ type: 'CAPTCHA_RESOLUTION', ip, result: true });
 | 
			
		||||
                            return;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            logger.info({ type: 'CAPTCHA_RESOLUTION', ip, result: false });
 | 
			
		||||
                            error = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const captchaWallTemplate = await renderCaptchaWall({ captchaImageTag: currentCaptchaIpList[ip].data, captchaResolutionFormUrl: '', error });
 | 
			
		||||
        res.status(401);
 | 
			
		||||
        res.send(captchaWallTemplate);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Generate a CAPTCHA
 | 
			
		||||
    function generateCaptcha(ip, captchaGenerationCacheDuration) {
 | 
			
		||||
        const captcha = svgCaptcha.create();
 | 
			
		||||
        currentCaptchaIpList[ip] = {
 | 
			
		||||
            data: captcha.data,
 | 
			
		||||
            text: captcha.text,
 | 
			
		||||
            resolved: false,
 | 
			
		||||
        };
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            if (currentCaptchaIpList[ip]) { delete currentCaptchaIpList[ip]; }
 | 
			
		||||
        }, captchaGenerationCacheDuration);
 | 
			
		||||
        logger.debug({ type: "GENERATE_CAPTCHA", ip });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return obj;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1233,10 +1233,7 @@ 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; }
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof obj.args.crowdsec == 'object') { obj.crowdSecBounser = require('./crowdsec.js').CreateCrowdSecBouncer(obj, obj.args.crowdsec); }
 | 
			
		||||
 | 
			
		||||
        // 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; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								webserver.js
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								webserver.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -3196,6 +3196,23 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle Captcha GET
 | 
			
		||||
    function handleCaptchaGetRequest(req, res) {
 | 
			
		||||
        const domain = checkUserIpAddress(req, res);
 | 
			
		||||
        if (domain == null) { return; }
 | 
			
		||||
        if (parent.crowdSecBounser == null) { res.sendStatus(404); return; }
 | 
			
		||||
        parent.crowdSecBounser.applyCaptcha(req, res, function () { res.redirect((((domain.id == '') && (domain.dns == null)) ? '/' : ('/' + domain.id))); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Handle Captcha POST
 | 
			
		||||
    function handleCaptchaPostRequest(req, res) {
 | 
			
		||||
        if (parent.crowdSecBounser == null) { res.sendStatus(404); return; }
 | 
			
		||||
        const domain = checkUserIpAddress(req, res);
 | 
			
		||||
        if (domain == null) { return; }
 | 
			
		||||
        req.originalUrl = (((domain.id == '') && (domain.dns == null)) ? '/' : ('/' + domain.id));
 | 
			
		||||
        parent.crowdSecBounser.applyCaptcha(req, res, function () { res.redirect(req.originalUrl); });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Render the terms of service.
 | 
			
		||||
    function handleTermsRequest(req, res) {
 | 
			
		||||
        const domain = checkUserIpAddress(req, res);
 | 
			
		||||
| 
						 | 
				
			
			@ -5714,11 +5731,9 @@ 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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5762,7 +5777,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
 | 
			
		|||
        });
 | 
			
		||||
 | 
			
		||||
        // Add HTTP security headers to all responses
 | 
			
		||||
        obj.app.use(function (req, res, next) {
 | 
			
		||||
        obj.app.use(async function (req, res, next) {
 | 
			
		||||
            // Check if a session is destroyed
 | 
			
		||||
            if (typeof req.session.userid == 'string') {
 | 
			
		||||
                if (typeof req.session.x == 'string') {
 | 
			
		||||
| 
						 | 
				
			
			@ -5905,6 +5920,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
 | 
			
		|||
            // Extend the session time by forcing a change to the session every minute.
 | 
			
		||||
            if (req.session.userid != null) { req.session.t = Math.floor(Date.now() / 60e3); } else { delete req.session.t; }
 | 
			
		||||
 | 
			
		||||
            // Check CrowdSec Bounser if configured
 | 
			
		||||
            if ((parent.crowdSecBounser != null) && (req.headers['upgrade'] != 'websocket') && (req.session.userid == null)) { if ((await parent.crowdSecBounser.process(domain, req, res, next)) == true) { return; } }
 | 
			
		||||
 | 
			
		||||
            // Debugging code, this will stop the agent from crashing if two responses are made to the same request.
 | 
			
		||||
            const render = res.render;
 | 
			
		||||
            const send = res.send;
 | 
			
		||||
| 
						 | 
				
			
			@ -6080,6 +6098,12 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
 | 
			
		|||
                obj.app.get(url + 'pluginHandler.js', obj.handlePluginJS);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check CrowdSec Bounser if configured
 | 
			
		||||
            if (parent.crowdSecBounser != null) {
 | 
			
		||||
                obj.app.get(url + 'captcha.ashx', handleCaptchaGetRequest);
 | 
			
		||||
                obj.app.post(url + 'captcha.ashx', obj.bodyParser.urlencoded({ extended: false }), handleCaptchaPostRequest);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Setup IP-KVM relay if supported
 | 
			
		||||
            if (domain.ipkvm) {
 | 
			
		||||
                obj.app.ws(url + 'ipkvm.ashx/*', function (ws, req) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue