From 140a142243c6e679617794d4bba1ac3154c8eee9 Mon Sep 17 00:00:00 2001 From: Sefinek Date: Wed, 18 Sep 2024 12:29:19 +0200 Subject: [PATCH] 1.1.6: Migrate to .env, bugfixes --- .env.default | 39 +++++++++++++++++++++++++++++++++------ config.js | 37 ++++++++++++++++++------------------- index.js | 32 ++++++++++++++++++-------------- package-lock.json | 4 ++-- package.json | 4 ++-- scripts/sefinekAPI.js | 2 +- 6 files changed, 74 insertions(+), 44 deletions(-) diff --git a/.env.default b/.env.default index 541e278..7a6910a 100644 --- a/.env.default +++ b/.env.default @@ -1,13 +1,40 @@ # production or development NODE_ENV=production -# Cloudflare: https://dash.cloudflare.com/profile/api-tokens +############################### TOKENS ############################### +# Cloudflare (https://dash.cloudflare.com/profile/api-tokens) CLOUDFLARE_EMAIL= -CLOUDFLARE_ZONE_ID= -CLOUDFLARE_API_KEY= +CLOUDFLARE_ZONE_ID=00000000000000000000000000000000 +CLOUDFLARE_API_KEY=0000000000000000000000000000000000000000 -# AbuseIPDB: https://www.abuseipdb.com/account/api -ABUSEIPDB_API_KEY= +# AbuseIPDB (https://www.abuseipdb.com/account/api) +ABUSEIPDB_API_KEY=00000000000000000000000000000000000000000000000000000000000000000000000000000000 # API key for api.sefinek.net. Contact me at contact@sefinek.net to obtain it and contribute to the sefinek24/Malicious-IP-Addresses. Leave empty if you don't have a token. -SEFINEK_API_SECRET= \ No newline at end of file +SEFINEK_API_SECRET= + +############################### CYCLES ############################### +# Main interval (in minutes) of each cycle. +CYCLE_INTERVAL=120 + +# The minimum time that must pass after reporting an IP address before it can be reported again. +# The required time is >= 15 minutes, according to AbuseIPDB API limits. +REPORTED_IP_COOLDOWN_MS=6 + +# The maximum URI length that can be reported to AbuseIPDB. +# If Cloudflare returns a longer URI, the API request will fail. +MAX_URL_LENGTH=920 + +# Additional delay (in seconds) after each successful IP report to avoid overloading the AbuseIPDB API. +SUCCESS_COOLDOWN_MS=2 + +# Interval for refreshing your IP address (in minutes). +# This ensures that WAF violations originating from your IP address are not reported to AbuseIPDB. +IP_REFRESH_INTERVAL=80 + +############################### SEFINEK API ############################### +# Report IP addresses to api.sefinek.net to support the development of the repository at https://github.com/sefinek24/Malicious-IP-Addresses. SEFINEK_API_SECRET is required if true. +REPORT_TO_SEFINEK_API=true + +# How often should the log (reported_ips.csv) be analyzed and sent to the Sefinek API? In minutes. +SEFINEK_API_INTERVAL=60 \ No newline at end of file diff --git a/config.js b/config.js index f5dedab..6c53327 100644 --- a/config.js +++ b/config.js @@ -1,26 +1,25 @@ -// Main interval of each cycle. -// In production mode, it's 2 hours, and in development mode, it's 8 seconds. -const CYCLE_INTERVAL = process.env.NODE_ENV === 'production' ? 2 * 60 * 60 * 1000 : 8 * 1000; +const CYCLE_INTERVAL = process.env.NODE_ENV === 'production' ? + parseInt(process.env.CYCLE_INTERVAL) * 60 * 1000 : 8 * 1000; -// The minimum time that must pass after reporting an IP address before it can be reported again. -// The required time is >= 15 minutes, according to AbuseIPDB API limits. -const REPORTED_IP_COOLDOWN_MS = 6 * 60 * 60 * 1000; // 6h +const REPORTED_IP_COOLDOWN_MS = parseInt(process.env.REPORTED_IP_COOLDOWN_MS) * 60 * 60 * 1000; -// The maximum URI length that can be reported to AbuseIPDB. -// If Cloudflare returns a longer URI, the API request will fail. -const MAX_URL_LENGTH = 920; +const MAX_URL_LENGTH = parseInt(process.env.MAX_URL_LENGTH); -// Additional delay after each successful IP report to avoid overloading the AbuseIPDB API. -const SUCCESS_COOLDOWN_MS = 2 * 1000; // 2s +const SUCCESS_COOLDOWN_MS = parseInt(process.env.SUCCESS_COOLDOWN_MS); -// Interval for refreshing your IP address. -// This ensures that WAF violations originating from your IP address are not reported to AbuseIPDB. -const IP_REFRESH_INTERVAL = 80 * 60 * 1000; // 80m +const IP_REFRESH_INTERVAL = parseInt(process.env.IP_REFRESH_INTERVAL) * 60 * 1000; -// Report IP addresses to api.sefinek.net to support the development of the repository at https://github.com/sefinek24/Malicious-IP-Addresses. If true, please see the .env file. -const REPORT_TO_SEFINEK_API = true; +const REPORT_TO_SEFINEK_API = process.env.REPORT_TO_SEFINEK_API === 'true'; -// How often should the log (reported_ips.csv) be analyzed and sent to the Sefinek API? -const SEFINEK_API_INTERVAL = process.env.NODE_ENV === 'production' ? 60 * 60 * 1000 : 5 * 1000; +const SEFINEK_API_INTERVAL = process.env.NODE_ENV === 'production' ? + parseInt(process.env.SEFINEK_API_INTERVAL) * 60 * 1000 : 5 * 1000; -module.exports = { CYCLE_INTERVAL, REPORTED_IP_COOLDOWN_MS, MAX_URL_LENGTH, SUCCESS_COOLDOWN_MS, IP_REFRESH_INTERVAL, REPORT_TO_SEFINEK_API, SEFINEK_API_INTERVAL }; \ No newline at end of file +module.exports = { + CYCLE_INTERVAL, + REPORTED_IP_COOLDOWN_MS, + MAX_URL_LENGTH, + SUCCESS_COOLDOWN_MS, + IP_REFRESH_INTERVAL, + REPORT_TO_SEFINEK_API, + SEFINEK_API_INTERVAL +}; \ No newline at end of file diff --git a/index.js b/index.js index 14eb081..59d7a8a 100644 --- a/index.js +++ b/index.js @@ -30,16 +30,20 @@ const fetchBlockedIPs = async () => { }; const isIPReportedRecently = (rayId, ip, reportedIPs) => { - const lastReport = reportedIPs.find(entry => - (entry.rayId === rayId || entry.ip === ip) && - (entry.action === 'REPORTED' || entry.action === 'TOO_MANY_REQUESTS') - ); + const lastReport = reportedIPs.reduce((latest, entry) => { + if ( + (entry.rayId === rayId || entry.ip === ip) && + (entry.status === 'TOO_MANY_REQUESTS' || entry.status === 'REPORTED') && + (!latest || entry.timestamp > latest.timestamp) + ) return entry; + return latest; + }, null); if (lastReport) { - const lastTimestamp = new Date(lastReport.timestamp).getTime(); - const currentTime = Date.now(); - const timeDifference = currentTime - lastTimestamp; - if (timeDifference < REPORTED_IP_COOLDOWN_MS) return { recentlyReported: true, timeDifference }; + const timeDifference = Date.now() - lastReport.timestamp; + if (timeDifference < REPORTED_IP_COOLDOWN_MS) { + return { recentlyReported: true, timeDifference, reason: lastReport.status === 'TOO_MANY_REQUESTS' ? 'RATE-LIMITED' : 'REPORTED' }; + } } return { recentlyReported: false }; @@ -96,7 +100,7 @@ const reportIP = async (event, country, hostname, endpoint, userAgent, cycleErro // Sefinek API if (REPORT_TO_SEFINEK_API && SEFINEK_API_INTERVAL && process.env.SEFINEK_API_SECRET) { - setInterval(async () => await SefinekAPI(), SEFINEK_API_INTERVAL); + setInterval(SefinekAPI, SEFINEK_API_INTERVAL); } // Ready @@ -111,7 +115,7 @@ const reportIP = async (event, country, hostname, endpoint, userAgent, cycleErro // AbuseIPDB let cycleId = 1; while (true) { - log('log', `===================== New Reporting Cycle (v${moduleVersion}) =====================`); + log('log', `================ New Reporting Cycle v${moduleVersion}; ID: ${cycleId} ================`); const blockedIPEvents = await fetchBlockedIPs(); if (!blockedIPEvents) { @@ -136,12 +140,13 @@ const reportIP = async (event, country, hostname, endpoint, userAgent, cycleErro } const reportedIPs = readReportedIPs(); - const { recentlyReported, timeDifference } = isIPReportedRecently(event.rayName, ip, reportedIPs); + const { recentlyReported, timeDifference, reason } = isIPReportedRecently(event.rayName, ip, reportedIPs); if (recentlyReported) { const hoursAgo = Math.floor(timeDifference / (1000 * 60 * 60)); const minutesAgo = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); const secondsAgo = Math.floor((timeDifference % (1000 * 60)) / 1000); - log('log', `${ip} was reported or rate-limited ${hoursAgo}h ${minutesAgo}m ${secondsAgo}s ago. Skipping...`); + + log('log', `${ip} was ${reason} ${hoursAgo}h ${minutesAgo}m ${secondsAgo}s ago. Skipping...`); cycleSkippedCount++; continue; } @@ -164,7 +169,6 @@ const reportIP = async (event, country, hostname, endpoint, userAgent, cycleErro } } - log('log', `Cycle Summary [${cycleId}]:`); log('log', `- Reported IPs: ${cycleReportedCount}`); log('log', `- Total IPs processed: ${cycleProcessedCount}`); log('log', `- Skipped IPs: ${cycleSkippedCount}`); @@ -172,7 +176,7 @@ const reportIP = async (event, country, hostname, endpoint, userAgent, cycleErro log('log', `- 429 Too Many Requests: ${cycleErrorCounts.blocked}`); log('log', `- No response errors: ${cycleErrorCounts.noResponse}`); log('log', `- Other errors: ${cycleErrorCounts.otherErrors}`); - log('log', '==================== End of Reporting Cycle ===================='); + log('log', '===================== End of Reporting Cycle ====================='); log('log', `Waiting ${formatDelay(CYCLE_INTERVAL)}...`); cycleId++; diff --git a/package-lock.json b/package-lock.json index ddf73e8..05149b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waf-to-abuseipdb", - "version": "1.1.5", + "version": "1.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waf-to-abuseipdb", - "version": "1.1.5", + "version": "1.1.6", "license": "MIT", "dependencies": { "axios": "^1.7.7", diff --git a/package.json b/package.json index 281c555..d92e615 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "waf-to-abuseipdb", - "version": "1.1.5", + "version": "1.1.6", "description": "A Node.js project that enables automatic reporting of incidents detected by Cloudflare WAF to the AbuseIPDB database.", "keywords": [ "abuseipdb", @@ -20,7 +20,7 @@ "url": "git+https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB.git" }, "license": "MIT", - "author": "Sefinek (https://sefinek.net)", + "author": "Sefinek (https://sefinek.net)", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", diff --git a/scripts/sefinekAPI.js b/scripts/sefinekAPI.js index 098cdea..52b4a73 100644 --- a/scripts/sefinekAPI.js +++ b/scripts/sefinekAPI.js @@ -8,7 +8,7 @@ const SEFINEK_API_URL = process.env.SEFINEK_API_URL || `${process.env.NODE_ENV = module.exports = async () => { const userIp = clientIp.getAddress(); const reportedIPs = readReportedIPs().filter(x => x.status === 'REPORTED' && x.ip !== userIp && !x.sefinekAPI); - if (reportedIPs.length === 0) return log('log', 'No IPs with `action Reported` and `SefinekAPI false` to send to Sefinek API'); + if (reportedIPs.length === 0) return; const uniqueLogs = reportedIPs.reduce((acc, ip) => { if (acc.seen.has(ip.ip)) return acc;