v0.2.0
- Added integration with Discord Webhooks - Added daily report summaries - Other fixes and improvements
This commit is contained in:
parent
da212951a6
commit
d9ad94e579
10 changed files with 174 additions and 32 deletions
25
README.md
25
README.md
|
|
@ -21,16 +21,19 @@ Also, check this out: [sefinek/Cloudflare-WAF-To-AbuseIPDB](https://github.com/s
|
||||||
|
|
||||||
|
|
||||||
## ✅ Features
|
## ✅ Features
|
||||||
1. A [`config.js`](config.default.js) file enabling easy configuration.
|
1. **Easy Configuration** – The [`config.js`](config.default.js) file allows for quick and simple customization.
|
||||||
2. A simple installer allowing quick integration deployment.
|
2. **Simple Installer** – Enables fast and seamless integration deployment.
|
||||||
3. Integration with Discord Webhooks (coming soon):
|
3. **Self-IP Protection** – The script will never report an IP address belonging to you or your server, even if you use a dynamic IP.
|
||||||
- Alerts in case of script errors
|
4. **Discord Webhooks Integration**:
|
||||||
- Daily summaries of reported IP addresses
|
- Important notifications.
|
||||||
4. Automatic updates.
|
- Alerts for script errors.
|
||||||
|
- Daily summaries of reported IP addresses.
|
||||||
|
5. **Automatic Updates** – The script regularly fetches and applies the latest updates. If you want, you can disable it, of course.
|
||||||
|
|
||||||
|
|
||||||
## 📥 Installation (Ubuntu & Debian)
|
## 📥 Installation (Ubuntu & Debian)
|
||||||
|
|
||||||
### Automatic (easy & recommenced)
|
### Automatic (easy & fast & recommenced)
|
||||||
#### Via curl
|
#### Via curl
|
||||||
```bash
|
```bash
|
||||||
bash <(curl -fsS https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
|
bash <(curl -fsS https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
|
||||||
|
|
@ -44,20 +47,20 @@ bash <(wget -qO- https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporte
|
||||||
### Manually
|
### Manually
|
||||||
#### Node.js installation
|
#### Node.js installation
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install -y curl
|
sudo apt install -y curl
|
||||||
curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
|
curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
|
||||||
sudo -E bash nodesource_setup.sh && sudo apt-get install -y nodejs
|
sudo -E bash nodesource_setup.sh && sudo apt install -y nodejs
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Git installation
|
#### Git installation
|
||||||
```bash
|
```bash
|
||||||
sudo add-apt-repository ppa:git-core/ppa
|
sudo add-apt-repository ppa:git-core/ppa
|
||||||
sudo apt-get update && sudo apt-get -y install git
|
sudo apt update && sudo apt -y install git
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Commands
|
#### Commands
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get update && sudo apt-get upgrade
|
sudo apt update && sudo apt upgrade
|
||||||
cd ~
|
cd ~
|
||||||
git clone https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git
|
git clone https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git
|
||||||
cd UFW-AbuseIPDB-Reporter
|
cd UFW-AbuseIPDB-Reporter
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,19 @@ exports.MAIN = {
|
||||||
UFW_LOG_FILE: '/var/log/ufw.log',
|
UFW_LOG_FILE: '/var/log/ufw.log',
|
||||||
CACHE_FILE: '/tmp/ufw-abuseipdb-reporter.cache',
|
CACHE_FILE: '/tmp/ufw-abuseipdb-reporter.cache',
|
||||||
SERVER_ID: null, // The server name that will be visible in the reports (e.g., 'homeserver1'). If you don't want to define it, leave the value as null.
|
SERVER_ID: null, // The server name that will be visible in the reports (e.g., 'homeserver1'). If you don't want to define it, leave the value as null.
|
||||||
IP_REFRESH_INTERVAL: 8 * 60 * 1000, // How often should (every 5 minutes) the script check the server's IP address to avoid accidental self-reports?
|
IP_REFRESH_INTERVAL: 10 * 60 * 1000, // How often should (every 5 minutes) the script check the server's IP address to avoid accidental self-reports?
|
||||||
|
|
||||||
// Reporting
|
// Reporting
|
||||||
ABUSEIPDB_API_KEY: '', // Secret API key for AbuseIPDB.
|
ABUSEIPDB_API_KEY: '', // Secret API key for AbuseIPDB.
|
||||||
IP_REPORT_COOLDOWN: 12 * 60 * 60 * 1000, // The minimum time (12 hours) that must pass before reporting the same IP address again.
|
IP_REPORT_COOLDOWN: 12 * 60 * 60 * 1000, // The minimum time (12 hours) that must pass before reporting the same IP address again.
|
||||||
|
|
||||||
// Automatic updates
|
// Automatic Updates
|
||||||
AUTO_UPDATE_ENABLED: true, // Do you want the script to automatically update to the latest version using 'git pull'? (true = enabled, false = disabled)
|
AUTO_UPDATE_ENABLED: true, // Do you want the script to automatically update to the latest version using 'git pull'? (true = enabled, false = disabled)
|
||||||
AUTO_UPDATE_SCHEDULE: '0 18 * * *', // Schedule for automatic script updates (CRON format). Default: every day at 18:00
|
AUTO_UPDATE_SCHEDULE: '0 18 * * *', // Schedule for automatic script updates (CRON format). Default: every day at 18:00
|
||||||
|
|
||||||
|
// Discord Webhooks
|
||||||
|
DISCORD_WEBHOOKS_ENABLED: false,
|
||||||
|
DISCORD_WEBHOOKS_URL: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
13
index.js
13
index.js
|
|
@ -10,9 +10,10 @@ const { reportedIPs, loadReportedIPs, saveReportedIPs, isIPReportedRecently, mar
|
||||||
const log = require('./utils/log.js');
|
const log = require('./utils/log.js');
|
||||||
const axios = require('./services/axios.js');
|
const axios = require('./services/axios.js');
|
||||||
const serverAddress = require('./services/fetchServerIP.js');
|
const serverAddress = require('./services/fetchServerIP.js');
|
||||||
|
const discordWebhooks = require('./services/discord.js');
|
||||||
const config = require('./config.js');
|
const config = require('./config.js');
|
||||||
const { version } = require('./package.json');
|
const { version } = require('./package.json');
|
||||||
const { UFW_LOG_FILE, ABUSEIPDB_API_KEY, SERVER_ID, AUTO_UPDATE_ENABLED } = config.MAIN;
|
const { UFW_LOG_FILE, ABUSEIPDB_API_KEY, SERVER_ID, AUTO_UPDATE_ENABLED, AUTO_UPDATE_SCHEDULE, DISCORD_WEBHOOKS_ENABLED, DISCORD_WEBHOOKS_URL } = config.MAIN;
|
||||||
|
|
||||||
let fileOffset = 0;
|
let fileOffset = 0;
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@ const reportToAbuseIPDb = async (logData, categories, comment) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const processLogLine = async line => {
|
const processLogLine = async line => {
|
||||||
if (!line.includes('[UFW BLOCK]')) return log(1, `Ignoring line: ${line}`);
|
if (!line.includes('[UFW BLOCK]')) return log(0, `Ignoring line: ${line}`);
|
||||||
|
|
||||||
const timestampMatch = line.match(/\[(\d+\.\d+)\]/);
|
const timestampMatch = line.match(/\[(\d+\.\d+)\]/);
|
||||||
const logData = {
|
const logData = {
|
||||||
|
|
@ -143,9 +144,9 @@ const processLogLine = async line => {
|
||||||
log(0, `Ready! Now monitoring: ${UFW_LOG_FILE}`);
|
log(0, `Ready! Now monitoring: ${UFW_LOG_FILE}`);
|
||||||
log(0, '=====================================================================');
|
log(0, '=====================================================================');
|
||||||
|
|
||||||
|
await discordWebhooks(0, `[UFW-AbuseIPDB-Reporter](https://github.com/sefinek/UFW-AbuseIPDB-Reporter) has been successfully launched on the device \`${SERVER_ID}\`.`);
|
||||||
|
|
||||||
// Auto updates
|
// Auto updates
|
||||||
if (AUTO_UPDATE_ENABLED) {
|
if (AUTO_UPDATE_ENABLED && AUTO_UPDATE_SCHEDULE) await require('./services/updates.js')();
|
||||||
const autoUpdates = require('./services/updates.js');
|
if (DISCORD_WEBHOOKS_ENABLED && DISCORD_WEBHOOKS_URL) await require('./services/summaries.js')();
|
||||||
await autoUpdates.pull();
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "ufw-abuseipdb-reporter",
|
"name": "ufw-abuseipdb-reporter",
|
||||||
"version": "0.1.3",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ufw-abuseipdb-reporter",
|
"name": "ufw-abuseipdb-reporter",
|
||||||
"version": "0.1.3",
|
"version": "0.2.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "ufw-abuseipdb-reporter",
|
"name": "ufw-abuseipdb-reporter",
|
||||||
"version": "0.1.3",
|
"version": "0.2.0",
|
||||||
"description": "A tool (with a simple installer) that monitors UFW firewall logs in real time and reports IP addresses to the AbuseIPDB database.",
|
"description": "A tool (with a simple installer) that monitors UFW firewall logs in real time and reports IP addresses to the AbuseIPDB database.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ufw",
|
"ufw",
|
||||||
"abuseipdb"
|
"abuseipdb"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter",
|
"homepage": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues"
|
"url": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
45
services/discord.js
Normal file
45
services/discord.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
const log = require('../utils/log.js');
|
||||||
|
const { SERVER_ID, DISCORD_WEBHOOKS_ENABLED, DISCORD_WEBHOOKS_URL } = require('../config.js').MAIN;
|
||||||
|
|
||||||
|
const TYPES = {
|
||||||
|
0: { type: 'SUCCESS', emoji: '\\✅', color: 0x60D06D },
|
||||||
|
1: { type: 'WARN', emoji: '\\⚠️', color: 0xFFB02E },
|
||||||
|
2: { type: 'ERROR', emoji: '\\❌', color: 0xF92F60 },
|
||||||
|
3: { type: 'FAIL', emoji: '\\🔴', color: 0xF8312F },
|
||||||
|
4: { type: 'INFO', emoji: '\\📄', color: 0xF2EEF8 },
|
||||||
|
5: { type: 'DEBUG', emoji: '\\🛠️', color: 0xB4ACBC },
|
||||||
|
6: { type: 'CRITICAL', emoji: '\\🔴', color: 0xF8312F },
|
||||||
|
7: { type: 'NOTICE', emoji: '\\📝', color: 0xF3EEF8 },
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async (id, description) => {
|
||||||
|
if (!DISCORD_WEBHOOKS_ENABLED || !DISCORD_WEBHOOKS_URL) return;
|
||||||
|
|
||||||
|
const logType = TYPES[id];
|
||||||
|
if (!logType) return log(1, 'Invalid log type ID provided!');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
method: 'POST',
|
||||||
|
url: DISCORD_WEBHOOKS_URL,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: {
|
||||||
|
embeds: [{
|
||||||
|
title: `${logType.emoji} ${SERVER_ID}: ${logType.type} [ID ${id}]`,
|
||||||
|
description,
|
||||||
|
color: logType.color,
|
||||||
|
footer: {
|
||||||
|
text: `Date: ${new Date().toLocaleString()} | sefinek/UFW-AbuseIPDB-Reporter`,
|
||||||
|
},
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios(config);
|
||||||
|
if (res.status !== 204) log(1, 'Failed to deliver Discord Webhook');
|
||||||
|
} catch (err) {
|
||||||
|
log(2, `Failed to send Discord Webhook! ${err.stack}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
const { exec } = require('node:child_process');
|
const { exec } = require('node:child_process');
|
||||||
const ecosystem = require('../ecosystem.config.js');
|
const ecosystem = require('../ecosystem.config.js');
|
||||||
|
const discordWebhooks = require('./discord.js');
|
||||||
|
const log = require('../utils/log.js');
|
||||||
|
|
||||||
const executeCommand = cmd =>
|
const executeCmd = cmd =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
exec(cmd, (err, stdout, stderr) => {
|
exec(cmd, (err, stdout, stderr) => {
|
||||||
if (err || stderr) reject(err || stderr);
|
if (err || stderr) reject(err || stderr);
|
||||||
|
|
@ -10,10 +12,13 @@ const executeCommand = cmd =>
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
|
const process = ecosystem.apps[0].name;
|
||||||
|
await discordWebhooks(4, `Restarting the ${process} process...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(await executeCommand('npm install --omit=dev'));
|
console.log(await executeCmd('npm install --omit=dev'));
|
||||||
console.log(await executeCommand(`pm2 restart ${ecosystem.apps[0].name}`));
|
console.log(await executeCmd(`pm2 restart ${process}`));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
log(2, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
73
services/summaries.js
Normal file
73
services/summaries.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
const { CronJob } = require('cron');
|
||||||
|
const fs = require('node:fs/promises');
|
||||||
|
const discordWebhooks = require('./discord.js');
|
||||||
|
const log = require('../utils/log.js');
|
||||||
|
const { CACHE_FILE } = require('../config.js').MAIN;
|
||||||
|
|
||||||
|
const formatHourRange = hour => `${hour.toString().padStart(2, '0')}:00-${hour.toString().padStart(2, '0')}:59`;
|
||||||
|
const pluralizeReport = count => (count === 1 ? 'report' : 'reports');
|
||||||
|
|
||||||
|
const sendWebhook = async () => {
|
||||||
|
try {
|
||||||
|
await fs.access(CACHE_FILE);
|
||||||
|
} catch {
|
||||||
|
return log(2, `Cache file not found: ${CACHE_FILE}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = (await fs.readFile(CACHE_FILE, 'utf8')).trim();
|
||||||
|
} catch (err) {
|
||||||
|
return log(2, `Error reading file: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
log(0, `Cache file is empty: ${CACHE_FILE}`);
|
||||||
|
return discordWebhooks(4, `Cache file is empty: \`${CACHE_FILE}\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const yesterday = new Date();
|
||||||
|
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
||||||
|
const yesterdayString = yesterday.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
const hourlySummary = {};
|
||||||
|
const uniqueEntries = new Set();
|
||||||
|
|
||||||
|
data.split('\n').forEach((line) => {
|
||||||
|
const [ip, timestamp] = line.split(' ');
|
||||||
|
if (!ip || isNaN(timestamp)) return;
|
||||||
|
|
||||||
|
const entryKey = `${ip}_${timestamp}`;
|
||||||
|
if (uniqueEntries.has(entryKey)) return;
|
||||||
|
uniqueEntries.add(entryKey);
|
||||||
|
|
||||||
|
const dateObj = new Date(parseInt(timestamp, 10) * 1000);
|
||||||
|
if (dateObj.toISOString().split('T')[0] !== yesterdayString) return;
|
||||||
|
|
||||||
|
const hour = dateObj.getUTCHours();
|
||||||
|
hourlySummary[hour] = (hourlySummary[hour] || 0) + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalReports = Object.values(hourlySummary).reduce((sum, count) => sum + count, 0);
|
||||||
|
const sortedEntries = Object.entries(hourlySummary).sort((a, b) => b[1] - a[1]);
|
||||||
|
const maxReports = sortedEntries.length > 0 ? sortedEntries[0][1] : 0;
|
||||||
|
const topHours = sortedEntries
|
||||||
|
.filter(([, count]) => count === maxReports && count > 1)
|
||||||
|
.map(([hour]) => parseInt(hour));
|
||||||
|
|
||||||
|
const summaryString = Object.entries(hourlySummary)
|
||||||
|
.map(([hour, count]) => `${formatHourRange(parseInt(hour))}: ${count} ${pluralizeReport(count)}${topHours.includes(parseInt(hour)) ? ' 🔥' : ''}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
await discordWebhooks(7, `Midnight. Summary of IP address reports (${totalReports}) from yesterday (${yesterdayString}).\nGood night to you, sleep well! 😴\n\`\`\`${summaryString}\`\`\``);
|
||||||
|
log(0, `Reported IPs yesterday by hour:\n${summaryString}\nTotal reported IPs: ${totalReports} ${pluralizeReport(totalReports)}`);
|
||||||
|
} catch (err) {
|
||||||
|
log(2, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
await sendWebhook();
|
||||||
|
new CronJob('0 0 * * *', sendWebhook, null, true, 'UTC');
|
||||||
|
};
|
||||||
|
|
@ -4,14 +4,21 @@ const simpleGit = require('simple-git');
|
||||||
const { CronJob } = require('cron');
|
const { CronJob } = require('cron');
|
||||||
const restartApp = require('./reloadApp.js');
|
const restartApp = require('./reloadApp.js');
|
||||||
const log = require('../utils/log.js');
|
const log = require('../utils/log.js');
|
||||||
|
const discordWebhooks = require('./discord.js');
|
||||||
|
|
||||||
const git = simpleGit();
|
const git = simpleGit();
|
||||||
|
|
||||||
const pull = async () => {
|
const pull = async () => {
|
||||||
|
await discordWebhooks(4, 'Updating the local repository in progress `(git pull)`...');
|
||||||
log(0, '$ git pull');
|
log(0, '$ git pull');
|
||||||
|
|
||||||
const { summary } = await git.pull();
|
try {
|
||||||
log(0, `Changes: ${summary.changes}; Deletions: ${summary.insertions}; Insertions: ${summary.insertions};`);
|
const { summary } = await git.pull();
|
||||||
|
log(0, `Changes: ${summary.changes}; Deletions: ${summary.insertions}; Insertions: ${summary.insertions}`);
|
||||||
|
await discordWebhooks(4, `**Changes:** ${summary.changes}; **Deletions:** ${summary.insertions}; **Insertions:** ${summary.insertions}`);
|
||||||
|
} catch (err) {
|
||||||
|
log(2, err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pullAndRestart = async () => {
|
const pullAndRestart = async () => {
|
||||||
|
|
@ -19,11 +26,11 @@ const pullAndRestart = async () => {
|
||||||
await pull();
|
await pull();
|
||||||
await restartApp();
|
await restartApp();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log(2, err.message);
|
log(2, err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://crontab.guru
|
// https://crontab.guru
|
||||||
new CronJob(AUTO_UPDATE_SCHEDULE || '0 18 * * *', pullAndRestart, null, true, 'UTC'); // At 18:00
|
new CronJob(AUTO_UPDATE_SCHEDULE, pullAndRestart, null, true, 'UTC');
|
||||||
|
|
||||||
module.exports = { pull };
|
module.exports = pull;
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const discordWebhooks = require('../services/discord.js');
|
||||||
|
|
||||||
const levels = {
|
const levels = {
|
||||||
0: { method: 'log', label: '[INFO]' },
|
0: { method: 'log', label: '[INFO]' },
|
||||||
1: { method: 'warn', label: '[WARN]' },
|
1: { method: 'warn', label: '[WARN]' },
|
||||||
|
|
@ -7,4 +9,6 @@ const levels = {
|
||||||
module.exports = (level, msg) => {
|
module.exports = (level, msg) => {
|
||||||
const { method, label } = levels[level] || { method: 'log', label: '[N/A]' };
|
const { method, label } = levels[level] || { method: 'log', label: '[N/A]' };
|
||||||
console[method](`${label} ${msg}`);
|
console[method](`${label} ${msg}`);
|
||||||
|
|
||||||
|
if (level >= 1) discordWebhooks(level, msg).catch(console.error);
|
||||||
};
|
};
|
||||||
Loading…
Add table
Reference in a new issue