Improved the service fetching the device's IP address

This commit is contained in:
Sefinek 2025-03-21 10:15:16 +01:00
parent 1c4f916480
commit 1ae07a1ca2
7 changed files with 103 additions and 66 deletions

View file

@ -5,10 +5,11 @@ exports.CONFIG = {
CLOUDFLARE_API_KEY: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', // https://dash.cloudflare.com/profile/api-tokens
ABUSEIPDB_API_KEY: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', // API key for reporting malicious IPs to AbuseIPDB
RUN_ON_START: true, // Should the reporting function run immediately after the script starts?
IPv6_SUPPORT: true, // Specifies whether the device has been assigned an IPv6 address.
},
CYCLES: {
// Schedule for running cron jobs for reporting to AbuseIPDB.
// CRON: Schedule for running cron jobs for reporting to AbuseIPDB.
REPORT_SCHEDULE: '0 */2 * * *',
// The minimum time (in hours) that must pass after reporting an IP address before it can be reported again.
@ -22,9 +23,9 @@ exports.CONFIG = {
// Additional delay (in milliseconds) after each successful IP report to avoid overloading the AbuseIPDB API.
SUCCESS_COOLDOWN: 20,
// Interval for refreshing your IP address (in minutes).
// CRON: Interval for refreshing your IP address. Default: every 6 hours
// This ensures that WAF violations originating from your IP address are not reported to AbuseIPDB.
IP_REFRESH_INTERVAL: 8 * 60 * 1000,
IP_REFRESH_SCHEDULE: '0 */6 * * *',
},
SEFINEK_API: {

View file

@ -5,19 +5,18 @@ const PAYLOAD = require('./services/payload.js');
const SefinekAPI = require('./services/SefinekAPI.js');
const headers = require('./utils/headers.js');
const { logToCSV, readReportedIPs } = require('./services/csv.js');
const formatDelay = require('./utils/formatDelay.js');
const fetchServerIP = require('./services/fetchServerIP.js');
const { refreshServerIPs, getServerIPs } = require('./services/ipFetcher.js');
const getFilters = require('./services/getFilters.js');
const log = require('./utils/log.js');
const fetchBlockedIPs = async whitelist => {
const fetchCloudflareEvents = async whitelist => {
try {
const { data, status } = await axios.post('https://api.cloudflare.com/client/v4/graphql', PAYLOAD(), { headers: headers.CLOUDFLARE });
const events = data?.data?.viewer?.zones?.[0]?.firewallEventsAdaptive;
if (!events) throw new Error(`Failed to retrieve data from Cloudflare (status ${status}): ${JSON.stringify(data?.errors)}`);
const isWhitelisted = event =>
event.ip === fetchServerIP() ||
getServerIPs().includes(event.ip) ||
whitelist.userAgents.some(ua => event.userAgent.includes(ua)) ||
whitelist.imgExtensions.some(ext => event.clientRequestPath.endsWith(ext)) ||
whitelist.domains.some(domain => event.clientRequestHTTPHost?.includes(domain)) ||
@ -60,7 +59,7 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
return false;
}
if (event.clientIP === fetchServerIP()) {
if (getServerIPs().includes(event.clientIP)) {
logToCSV(event.rayName, event.clientIP, country, hostname, endpoint, event.userAgent, event.action, 'YOUR_IP_ADDRESS');
log(0, `Your IP address (${event.clientIP}) was unexpectedly received from Cloudflare. URI: ${uri}`);
return false;
@ -104,23 +103,27 @@ const reportIP = async (event, uri, country, hostname, endpoint, cycleErrorCount
let cycleId = 1;
const cron = async () => {
log(0, `======================== Reporting Cycle No. ${cycleId} ========================`);
// Fetch cloudflare events
const whitelist = await getFilters();
const events = await fetchCloudflareEvents(whitelist);
if (!events) return log(1, 'No events fetched, skipping cycle...');
log(0, `===================== Reporting Cycle No. ${cycleId} =====================`);
const blockedIPEvents = await fetchBlockedIPs(whitelist);
if (!blockedIPEvents) return log(1, 'No events fetched, skipping cycle...');
const serverIP = fetchServerIP();
if (!serverIP) log(1, `Server IP address is missing! Received: ${serverIP}`);
// IP
await refreshServerIPs();
const ips = getServerIPs();
if (!Array.isArray(ips)) return log(2, 'For some reason, \'ips\' is not an array');
log(0, `Fetched ${getServerIPs()?.length} of your IP addresses`);
// Cycle
let cycleProcessedCount = 0, cycleReportedCount = 0, cycleSkippedCount = 0;
const cycleErrorCounts = { blocked: 0, otherErrors: 0 };
for (const event of blockedIPEvents) {
for (const event of events) {
cycleProcessedCount++;
const ip = event.clientIP;
if (ip === serverIP) {
if (getServerIPs().includes(ip)) {
log(0, `The IP address ${ip} belongs to this machine. Ignoring...`);
cycleSkippedCount++;
continue;
@ -164,11 +167,12 @@ const cron = async () => {
new CronJob(CONFIG.SEFINEK_API.REPORT_SCHEDULE, SefinekAPI, null, true, 'UTC');
}
// Ready
process.send && process.send('ready');
log(0, 'The integration is ready!');
// AbuseIPDB
new CronJob(CONFIG.CYCLES.REPORT_SCHEDULE, cron, null, true, 'UTC');
// Ready
process.send && process.send('ready');
// Run on start?
if (CONFIG.MAIN.RUN_ON_START) await cron();
})();

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "cf-waf-to-abuseipdb",
"version": "1.5.1",
"version": "1.6.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cf-waf-to-abuseipdb",
"version": "1.5.1",
"version": "1.6.0",
"license": "MIT",
"dependencies": {
"axios": "^1.8.4",

View file

@ -1,6 +1,6 @@
{
"name": "cf-waf-to-abuseipdb",
"version": "1.5.1",
"version": "1.6.0",
"description": "Node.js script for automatically reporting incidents to AbuseIPDB using data obtained from Cloudflare WAF.",
"homepage": "https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB",
"bugs": {

View file

@ -1,7 +1,7 @@
const axios = require('./axios.js');
const { readReportedIPs, updateSefinekAPIInCSV } = require('./csv.js');
const log = require('../utils/log.js');
const fetchServerIP = require('./fetchServerIP.js');
const fetchServerIP = require('./ipFetcher.js');
const { SEFINEK_API } = require('../config.js').CONFIG;
module.exports = async () => {

View file

@ -1,41 +0,0 @@
const { networkInterfaces } = require('node:os');
const { get } = require('./axios.js');
const isLocalIP = require('../utils/isLocalIP.js');
const log = require('../utils/log.js');
const { CYCLES } = require('../config.js').CONFIG;
const ipAddrList = new Set();
const fetchIPv4Address = async () => {
try {
const { data } = await get('https://api.sefinek.net/api/v2/ip');
if (data?.success && data?.message) ipAddrList.add(data.message);
} catch (err) {
log(2, `Error fetching IPv4 address: ${err.message}`);
}
};
const fetchIPv6Address = () => {
try {
Object.values(networkInterfaces()).flat().forEach(({ address, internal }) => {
if (!internal && address && !isLocalIP(address)) ipAddrList.add(address);
});
} catch (err) {
log(2, `Error fetching IPv6 address: ${err.message}`);
}
};
const fetchServerIPs = async () => {
ipAddrList.clear();
await fetchIPv4Address();
fetchIPv6Address();
};
(async () => {
await fetchServerIPs();
setInterval(fetchServerIPs, CYCLES.IP_REFRESH_INTERVAL);
// console.debug(ipAddrList);
})();
module.exports = () => Array.from(ipAddrList);

73
services/ipFetcher.js Normal file
View file

@ -0,0 +1,73 @@
const { networkInterfaces } = require('node:os');
const https = require('node:https');
const { CronJob } = require('cron');
const { get } = require('./axios.js');
const isLocalIP = require('../utils/isLocalIP.js');
const log = require('../utils/log.js');
const { CYCLES, IPv6_SUPPORT } = require('../config.js').CONFIG;
const ipAddresses = new Set();
let ipv6ErrorCount = 0, ipv6ErrorLogged = false;
const IPv6Failed = 'It looks like your ISP hasn\'t assigned you any IPv6 address. I won\'t attempt to fetch it again.';
const fetchIPAddress = async family => {
if (family === 6 && (ipv6ErrorLogged || !IPv6_SUPPORT)) return;
try {
const { data } = await get('https://api.sefinek.net/api/v2/ip', {
httpsAgent: new https.Agent({ family }),
});
if (data?.success && data?.message) {
ipAddresses.add(data.message);
if (family === 6) {
if (ipv6ErrorCount > 0) {
const IPv6Success = `Uh, it looks like IPv6 has started working! It only succeeded after ${ipv6ErrorCount} attempts.`;
log(0, IPv6Success);
}
ipv6ErrorCount = 0;
}
} else {
log(2, `Unexpected API response: ${JSON.stringify(data)}`);
}
} catch (err) {
log(2, `Error fetching IPv${family} address: ${err.message}`);
if (family === 6 && err.code === 'ENOENT') {
ipv6ErrorCount++;
if (ipv6ErrorCount >= 6 && !ipv6ErrorLogged) {
ipv6ErrorLogged = true;
log(0, IPv6Failed);
} else {
await new Promise(resolve => setTimeout(resolve, 4000));
await fetchIPAddress(6);
}
}
}
};
const fetchLocalIPs = () => {
for (const iface of Object.values(networkInterfaces()).flat()) {
if (iface && !iface.internal && iface.address && !isLocalIP(iface.address)) {
ipAddresses.add(iface.address);
}
}
};
const refreshServerIPs = async () => {
await Promise.all([fetchIPAddress(4), fetchIPAddress(6)]);
fetchLocalIPs();
};
(async () => {
new CronJob(CYCLES.IP_REFRESH_SCHEDULE || '0 */6 * * *', refreshServerIPs, null, true, 'UTC');
await refreshServerIPs();
})();
module.exports = {
refreshServerIPs,
getServerIPs: () => [...ipAddresses],
};