Node.js version (not finished yet)
This commit is contained in:
parent
4469aa0816
commit
c19d2dd7c1
18 changed files with 487 additions and 562 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
config.js
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/jsLinters/eslint.xml
generated
Normal file
7
.idea/jsLinters/eslint.xml
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EslintConfiguration">
|
||||||
|
<custom-configuration-file used="true" path="$PROJECT_DIR$/eslint.config.mjs" />
|
||||||
|
<option name="fix-on-save" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
<div align="center">
|
|
||||||
[<a href="README.md">English</a>]
|
|
||||||
[<a href="README_PL.md">Polski</a>]
|
|
||||||
</div>
|
|
||||||
|
|
||||||
# 🛡️ UFW AbuseIPDB Reporter
|
# 🛡️ UFW AbuseIPDB Reporter
|
||||||
A utility designed to analyze UFW firewall logs and report malicious IP addresses to the [AbuseIPDB](https://www.abuseipdb.com) database.
|
A utility designed to analyze UFW firewall logs and report malicious IP addresses to the [AbuseIPDB](https://www.abuseipdb.com) database.
|
||||||
To prevent redundant reporting of the same IP address within a short period, the tool uses a temporary cache file to track previously reported IPs.
|
To prevent redundant reporting of the same IP address within a short period, the tool uses a temporary cache file to track previously reported IPs.
|
||||||
|
|
|
||||||
82
README_PL.md
82
README_PL.md
|
|
@ -1,82 +0,0 @@
|
||||||
<div align="center">
|
|
||||||
[<a href="README.md">English</a>]
|
|
||||||
[<a href="README_PL.md">Polski</a>]
|
|
||||||
</div>
|
|
||||||
|
|
||||||
# 🛡️ UFW AbuseIPDB Reporter
|
|
||||||
Narzędzie zaprojektowane do analizowania dzienników zapory sieciowej UFW i zgłaszania złośliwych adresów IP do bazy danych [AbuseIPDB](https://www.abuseipdb.com).
|
|
||||||
Aby zapobiec nadmiarowemu zgłaszaniu tego samego adresu IP w krótkim okresie, narzędzie wykorzystuje tymczasowy plik pamięci podręcznej do śledzenia wcześniej zgłoszonych adresów IP.
|
|
||||||
|
|
||||||
Jeśli podoba Ci się to repozytorium lub uważasz je za przydatne, byłbym bardzo wdzięczny, za przyznanie mu gwiazdki ⭐. Wielkie dzięki!
|
|
||||||
Zobacz również to: [sefinek/Cloudflare-WAF-To-AbuseIPDB](https://github.com/sefinek/Cloudflare-WAF-To-AbuseIPDB)
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Jeśli chcesz wprowadzić zmiany do jakichkolwiek pliku z tego repozytorium, zacznij od utworzenia [publicznego forka](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/fork).
|
|
||||||
|
|
||||||
|
|
||||||
## 📋 Wymagania
|
|
||||||
- **System operacyjny:** Linux z zainstalowanym i skonfigurowanym firewallem UFW.
|
|
||||||
- **Konto AbuseIPDB:** Wymagane jest konto w serwisie AbuseIPDB [z ważnym tokenem API](https://www.abuseipdb.com/account/api). Token API jest niezbędny.
|
|
||||||
- **Zainstalowane pakiety:**
|
|
||||||
- `wget` lub `curl`: Jedno z tych narzędzi jest wymagane do pobrania [skryptu instalacyjnego](install.sh) z repozytorium GitHub oraz do wysyłania zapytań do API AbuseIPDB.
|
|
||||||
- `jq`: Narzędzie do przetwarzania i parsowania danych w formacie JSON, zwracanych przez API AbuseIPDB.
|
|
||||||
- `openssl`: Używane do kodowania i dekodowania tokena API, aby zabezpieczyć dane uwierzytelniające.
|
|
||||||
- `tail`, `awk`, `grep`, `sed`: Standardowe narzędzia Unixowe wykorzystywane do przetwarzania tekstu i analizy logów.
|
|
||||||
|
|
||||||
|
|
||||||
## 🧪 Testowane systemy operacyjne
|
|
||||||
- **Ubuntu Server:** 20.04 & 22.04
|
|
||||||
|
|
||||||
*Jeśli dystrybucja, której używasz do uruchomienia tego narzędzia, nie jest wymieniona tutaj, ale działa poprawnie, utwórz nowy [Issue](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues) lub prześlij [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls).*
|
|
||||||
|
|
||||||
|
|
||||||
## 📥 Instalacja
|
|
||||||
### curl
|
|
||||||
```bash
|
|
||||||
bash <(curl -s https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
### wget
|
|
||||||
```bash
|
|
||||||
bash <(wget -qO- https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
Skrypt instalacyjny automatycznie pobierze i skonfiguruje narzędzie na komputerze użytkownika. Podczas procesu instalacji zostaniesz poproszony o podanie [tokenu API AbuseIPDB](https://www.abuseipdb.com/account/api).
|
|
||||||
|
|
||||||
|
|
||||||
## 🖥️ Użycie
|
|
||||||
Po pomyślnej instalacji skrypt będzie działać cały czas w tle, monitorując logi UFW i automatycznie zgłaszając złośliwe adresy IP.
|
|
||||||
Narzędzie nie wymaga dodatkowych działań użytkownika po instalacji. Warto jednak od czasu do czasu sprawdzić jego działanie oraz aktualizować skrypt na bieżąco (wywołując polecenie instalacyjne).
|
|
||||||
|
|
||||||
Serwery otwarte na świat są nieustannie skanowane przez boty, które zazwyczaj szukają podatności lub jakichkolwiek innych luk w zabezpieczeniach.
|
|
||||||
Więc nie zdziw się, jeśli następnego dnia liczba zgłoszeń na AbuseIPDB przekroczy tysiąc.
|
|
||||||
|
|
||||||
### 🔍 Sprawdzenie statusu usługi
|
|
||||||
```bash
|
|
||||||
sudo systemctl status abuseipdb-ufw.service
|
|
||||||
```
|
|
||||||
|
|
||||||
Aby zobaczyć bieżące logi generowane przez proces, użyj polecenia:
|
|
||||||
```bash
|
|
||||||
journalctl -u abuseipdb-ufw.service -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### 📄 Przykładowe zgłoszenie
|
|
||||||
```
|
|
||||||
Blocked by UFW (TCP on 80)
|
|
||||||
Source port: 28586
|
|
||||||
TTL: 116
|
|
||||||
Packet length: 48
|
|
||||||
TOS: 0x08
|
|
||||||
|
|
||||||
This report (for 46.174.191.31) was generated by:
|
|
||||||
https://github.com/sefinek/UFW-AbuseIPDB-Reporter
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 🤝 Rozwój
|
|
||||||
Jeśli chcesz przyczynić się do rozwoju tego projektu, śmiało stwórz nowy [Pull request](https://github.com/sefinek/UFW-AbuseIPDB-Reporter/pulls). Z pewnością to docenię!
|
|
||||||
|
|
||||||
|
|
||||||
## 🔑 Licencja GPL-3.0
|
|
||||||
Copyright 2024 © by [Sefinek](https://sefinek.net). Wszelkie prawa zastrzeżone. Zobacz plik [LICENSE](LICENSE), aby dowiedzieć się więcej.
|
|
||||||
20
default.config.js
Normal file
20
default.config.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
exports.MAIN = {
|
||||||
|
LOG_FILE: 'D:\\test\\ufw.log',
|
||||||
|
CACHE_FILE: 'D:\\test\\ufw-abuseipdb-reporter.cache',
|
||||||
|
|
||||||
|
ABUSEIPDB_API_KEY: '',
|
||||||
|
GITHUB_REPO: 'https://github.com/sefinek/UFW-AbuseIPDB-Reporter',
|
||||||
|
|
||||||
|
REPORT_INTERVAL: 43200,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.REPORT_COMMENT = (timestamp, srcIp, dstIp, proto, spt, dpt, ttl, len, tos) => {
|
||||||
|
return `Blocked by UFW (${proto} on ${dpt})
|
||||||
|
Source port: ${spt}
|
||||||
|
TTL: ${ttl || 'N/A'}
|
||||||
|
Packet length: ${len || 'N/A'}
|
||||||
|
TOS: ${tos || 'N/A'}
|
||||||
|
|
||||||
|
This report (for ${srcIp}) was generated by:
|
||||||
|
https://github.com/sefinek/UFW-AbuseIPDB-Reporter`; // Please do not remove the URL to the repository of this script. I would be really grateful. 💙
|
||||||
|
};
|
||||||
1
ecosystem.config.js
Normal file
1
ecosystem.config.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = {};
|
||||||
55
eslint.config.mjs
Normal file
55
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.es2024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'arrow-spacing': [1, { before: true, after: true }],
|
||||||
|
'comma-dangle': [1, { arrays: 'always-multiline', objects: 'always-multiline' }],
|
||||||
|
'comma-spacing': 1,
|
||||||
|
'comma-style': 'error',
|
||||||
|
'curly': ['error', 'multi-line', 'consistent'],
|
||||||
|
'dot-location': ['error', 'property'],
|
||||||
|
'handle-callback-err': 'off',
|
||||||
|
'indent': [1, 'tab'],
|
||||||
|
'keyword-spacing': 1,
|
||||||
|
'max-nested-callbacks': ['error', { max: 4 }],
|
||||||
|
'max-statements-per-line': ['error', { max: 2 }],
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-empty': 1,
|
||||||
|
'no-empty-function': 1,
|
||||||
|
'no-floating-decimal': 'error',
|
||||||
|
'no-lonely-if': 1,
|
||||||
|
'no-multi-spaces': 1,
|
||||||
|
'no-multiple-empty-lines': [1, { max: 3, maxEOF: 1, maxBOF: 0 }],
|
||||||
|
'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }],
|
||||||
|
'no-trailing-spaces': 1,
|
||||||
|
'no-unreachable': 1,
|
||||||
|
'no-unused-vars': 1,
|
||||||
|
'no-use-before-define': ['error', { functions: false, classes: true }],
|
||||||
|
'no-var': 'error',
|
||||||
|
'object-curly-spacing': [1, 'always'],
|
||||||
|
'prefer-const': 'error',
|
||||||
|
'quotes': [1, 'single'],
|
||||||
|
'semi': [1, 'always'],
|
||||||
|
'sort-vars': 1,
|
||||||
|
'space-before-blocks': 1,
|
||||||
|
'space-before-function-paren': [1, { anonymous: 'never', named: 'never', asyncArrow: 'always' }],
|
||||||
|
'space-in-parens': 1,
|
||||||
|
'space-infix-ops': 1,
|
||||||
|
'space-unary-ops': 1,
|
||||||
|
'spaced-comment': 1,
|
||||||
|
'wrap-regex': 1,
|
||||||
|
'yoda': 'error',
|
||||||
|
},
|
||||||
|
ignores: ['node_modules', '*min.js', '*bundle*', 'build/*', 'dist/*'],
|
||||||
|
},
|
||||||
|
];
|
||||||
112
index.js
Normal file
112
index.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
const isLocalIP = require('./utils/isLocalIP.js');
|
||||||
|
const { loadReportedIps, saveReportedIps, isIpReportedRecently, markIpAsReported } = require('./utils/cache.js');
|
||||||
|
const log = require('./utils/log.js');
|
||||||
|
const axios = require('./services/axios.js');
|
||||||
|
const config = require('./config.js');
|
||||||
|
const { LOG_FILE, ABUSEIPDB_API_KEY } = config.MAIN;
|
||||||
|
|
||||||
|
let fileOffset = 0;
|
||||||
|
|
||||||
|
const reportToAbuseIpDb = async (ip, categories, comment) => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post('https://api.abuseipdb.com/api/v2/report', new URLSearchParams({ ip, categories, comment }), {
|
||||||
|
headers: { 'Key': ABUSEIPDB_API_KEY },
|
||||||
|
});
|
||||||
|
|
||||||
|
log(0, `Successfully reported IP ${ip} (score: ${data.data.abuseConfidenceScore})`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
log(2, `${err.message}\n${JSON.stringify(err.response.data)}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const determineCategories = (proto, dpt) => {
|
||||||
|
const categories = {
|
||||||
|
TCP: {
|
||||||
|
22: '14,22,18', 80: '14,21', 443: '14,21', 8080: '14,21',
|
||||||
|
25: '14,11', 21: '14,5,18', 53: '14,1,2', 23: '14,15,18',
|
||||||
|
3389: '14,15,18', 3306: '14,16', 6666: '14,8',
|
||||||
|
6667: '14,8', 6668: '14,8', 6669: '14,8', 9999: '14,6',
|
||||||
|
},
|
||||||
|
UDP: {
|
||||||
|
53: '14,1,2', 123: '14,17',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return categories[proto]?.[dpt] || '14';
|
||||||
|
};
|
||||||
|
|
||||||
|
const processLogLine = async line => {
|
||||||
|
if (!line.includes('[UFW BLOCK]')) return log(1, `Ignoring line: ${line}`);
|
||||||
|
|
||||||
|
const match = {
|
||||||
|
timestamp: line.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?/)[0],
|
||||||
|
srcIp: line.match(/SRC=([\d.]+)/)?.[1],
|
||||||
|
dstIp: line.match(/DST=([\d.]+)/)?.[1],
|
||||||
|
proto: line.match(/PROTO=(\S+)/)?.[1],
|
||||||
|
spt: line.match(/SPT=(\d+)/)?.[1],
|
||||||
|
dpt: line.match(/DPT=(\d+)/)?.[1],
|
||||||
|
ttl: line.match(/TTL=(\d+)/)?.[1],
|
||||||
|
len: line.match(/LEN=(\d+)/)?.[1],
|
||||||
|
tos: line.match(/TOS=(\S+)/)?.[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { srcIp, proto, dpt } = match;
|
||||||
|
if (!srcIp) {
|
||||||
|
log(1, `Missing SRC in log line: ${line}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLocalIP(srcIp)) {
|
||||||
|
log(0, `Ignoring local/private IP: ${srcIp}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIpReportedRecently(srcIp)) {
|
||||||
|
log(0, `IP ${srcIp} reported recently`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = determineCategories(proto, dpt);
|
||||||
|
const comment = config.REPORT_COMMENT(match.timestamp, srcIp, match.dstIp, proto, match.spt, dpt, match.ttl, match.len, match.tos);
|
||||||
|
|
||||||
|
log(0, `Reporting IP ${srcIp} (${proto} ${dpt}) with categories ${categories}`);
|
||||||
|
|
||||||
|
if (await reportToAbuseIpDb(srcIp, categories, comment)) {
|
||||||
|
markIpAsReported(srcIp);
|
||||||
|
saveReportedIps();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startMonitoring = () => {
|
||||||
|
loadReportedIps();
|
||||||
|
|
||||||
|
if (!fs.existsSync(LOG_FILE)) {
|
||||||
|
log(2, `Log file ${LOG_FILE} does not exist.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOffset = fs.statSync(LOG_FILE).size;
|
||||||
|
|
||||||
|
chokidar.watch(LOG_FILE, { persistent: true, ignoreInitial: true })
|
||||||
|
.on('change', path => {
|
||||||
|
const stats = fs.statSync(path);
|
||||||
|
if (stats.size < fileOffset) {
|
||||||
|
log(1, 'File truncated. Resetting offset...');
|
||||||
|
fileOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.createReadStream(path, { start: fileOffset, encoding: 'utf8' }).on('data', chunk => {
|
||||||
|
chunk.split('\n').filter(line => line.trim()).forEach(processLogLine);
|
||||||
|
}).on('end', () => {
|
||||||
|
fileOffset = stats.size;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
log(0, `Now monitoring ${LOG_FILE}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
startMonitoring();
|
||||||
261
install.sh
261
install.sh
|
|
@ -1,261 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
###
|
|
||||||
# https://github.com/sefinek/UFW-AbuseIPDB-Reporter
|
|
||||||
##
|
|
||||||
|
|
||||||
VERSION="1.1.1"
|
|
||||||
DATE="01.12.2024"
|
|
||||||
REPO="https://github.com/sefinek/UFW-AbuseIPDB-Reporter"
|
|
||||||
|
|
||||||
cat << "EOF"
|
|
||||||
_ _ ___ ____ ____ ____
|
|
||||||
/ \ | |__ _ _ ___ ___ |_ _| | _ \ | _ \ | __ )
|
|
||||||
/ _ \ | '_ \ | | | | / __| / _ \ | | | |_) | | | | | | _ \
|
|
||||||
/ ___ \ | |_) | | |_| | \__ \ | __/ | | | __/ | |_| | | |_) |
|
|
||||||
/_/ \_\_|_.__/ _ \__,_| |___/ \___| |___| |_| |____/ |____/
|
|
||||||
|
|
||||||
(_)_ __ | |_ ___ __ _ _ __ __ _| |_(_) ___ _ __
|
|
||||||
| | '_ \| __/ _ \/ _` | '__/ _` | __| |/ _ \| '_ \
|
|
||||||
| | | | | || __/ (_| | | | (_| | |_| | (_) | | | |
|
|
||||||
|_|_| |_|\__\___|\__, |_| \__,_|\__|_|\___/|_| |_|
|
|
||||||
|___/
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
>> Made by sefinek.net || Version: $VERSION [$DATE] <<
|
|
||||||
|
|
||||||
This installer will configure UFW-AbuseIPDB-Reporter, a tool that analyzes
|
|
||||||
UFW firewall logs and reports IP addresses to AbuseIPDB. Remember to perform
|
|
||||||
updates periodically. You can join my Discord server to receive notifications
|
|
||||||
about the latest changes and more: https://discord.gg/53DBjTuzgZ
|
|
||||||
================================================================================
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Function to download a file using either curl or wget
|
|
||||||
download_file() {
|
|
||||||
local url="$1"
|
|
||||||
local output="$2"
|
|
||||||
local user_agent="UFW-AbuseIPDB-Reporter/$VERSION (+$REPO)"
|
|
||||||
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
|
||||||
echo "INFO: Using 'curl' to download the file..."
|
|
||||||
sudo curl -A "$user_agent" -o "$output" "$url"
|
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
|
||||||
echo "INFO: 'curl' is not installed. Using 'wget' to download the file..."
|
|
||||||
sudo wget --header="User-Agent: $user_agent" -O "$output" "$url"
|
|
||||||
else
|
|
||||||
echo "FAIL: Neither 'curl' nor 'wget' is installed! Please install one of these packages and try running the script again."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ask
|
|
||||||
ask_user() {
|
|
||||||
local question="$1"
|
|
||||||
local response
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
read -rp "$ $question [Yes/no]: " response
|
|
||||||
case "${response,,}" in
|
|
||||||
yes|y) return 0;;
|
|
||||||
no|n) return 1;;
|
|
||||||
*) echo "Invalid input. Please answer 'yes' or 'no'."
|
|
||||||
echo;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# ========================= CHECK FOR MISSING PACKAGES =========================
|
|
||||||
required_packages=(ufw jq openssl)
|
|
||||||
missing_packages=()
|
|
||||||
|
|
||||||
for pkg in "${required_packages[@]}"; do
|
|
||||||
if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
|
|
||||||
missing_packages+=("$pkg")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ ${#missing_packages[@]} -gt 0 ]; then
|
|
||||||
echo "WARN: The following packages are not installed: ${missing_packages[*]}"
|
|
||||||
if ! ask_user "Do you want to install them now?"; then
|
|
||||||
echo "FAIL: Missing dependencies packages. Installation cannot proceed without them."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "INFO: Installing missing dependencies: ${missing_packages[*]}"
|
|
||||||
if ! sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"; then
|
|
||||||
echo "FAIL: Failed to install the required dependencies. Aborting installation!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "INFO: All required dependencies have been successfully installed.\n"
|
|
||||||
else
|
|
||||||
echo "INFO: Dependencies are already installed on this machine."
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# =========================== Check if the service already exists ===========================
|
|
||||||
if systemctl list-unit-files | grep -q '^abuseipdb-ufw.service'; then
|
|
||||||
echo "WARN: abuseipdb-ufw.service is already installed! If you plan to update or reinstall, choose 'Yes'."
|
|
||||||
if ask_user "Do you want to remove the existing service?"; then
|
|
||||||
sudo systemctl stop abuseipdb-ufw.service
|
|
||||||
sudo systemctl disable abuseipdb-ufw.service
|
|
||||||
sudo rm /etc/systemd/system/abuseipdb-ufw.service
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
else
|
|
||||||
echo -e "INFO: Existing service will not be removed\n"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# =========================== Prepare installation directory ===========================
|
|
||||||
install_dir="/usr/local/bin/UFW-AbuseIPDB-Reporter"
|
|
||||||
script_path="$install_dir/reporter.sh"
|
|
||||||
if [ -d "$install_dir" ]; then
|
|
||||||
echo "INFO: Directory $install_dir already exists. Removing it..."
|
|
||||||
if ! sudo rm -rf "$install_dir"; then
|
|
||||||
echo "FAIL: Something went wrong. Failed to remove existing directory $install_dir."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "INFO: Creating installation directory at $install_dir..."
|
|
||||||
if ! sudo mkdir -p "$install_dir"; then
|
|
||||||
echo "FAIL: Something went wrong. Failed to create installation directory."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
|
||||||
# =========================== Prepare reporter.sh script ===========================
|
|
||||||
GITHUB_URL="https://raw.githubusercontent.com/sefinek/UFW-AbuseIPDB-Reporter/main/reporter.sh"
|
|
||||||
echo "INFO: Downloading reporter.sh from $GITHUB_URL..."
|
|
||||||
if ! download_file "$GITHUB_URL" "$script_path"; then
|
|
||||||
echo "FAIL: Something went wrong while downloading the file from GitHub servers! Maybe try running this script as sudo?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "INFO: Saved reporter.sh at location $script_path"
|
|
||||||
|
|
||||||
if ! sudo chmod +x "$script_path"; then
|
|
||||||
echo "FAIL: Failed to make reporter.sh executable."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo -e "INFO: reporter.sh has been made executable.\n"
|
|
||||||
|
|
||||||
|
|
||||||
# =========================== AbuseIPDB API token ===========================
|
|
||||||
max_attempts=4
|
|
||||||
valid_token=false
|
|
||||||
for ((attempts = 0; attempts < max_attempts; attempts++)); do
|
|
||||||
read -rsp "$ Please enter your AbuseIPDB API token: " api_key
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [[ "$api_key" =~ ^[a-f0-9]{80}$ ]]; then
|
|
||||||
valid_token=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
attempts_left=$((max_attempts - attempts - 1))
|
|
||||||
if (( attempts_left > 0 )); then
|
|
||||||
echo "WARN: Invalid API token format. Please enter an 80-character hexadecimal string. You have $attempts_left/$max_attempts attempts left."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "$valid_token" != true ]]; then
|
|
||||||
echo "FAIL: Maximum number of attempts reached. Installation aborted!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Encode the API token
|
|
||||||
token_file="$install_dir/.abuseipdb_token"
|
|
||||||
echo "INFO: Encoding data (file $token_file)..."
|
|
||||||
if ! echo -n "$api_key" | openssl enc -base64 | sudo tee "$token_file" >/dev/null; then
|
|
||||||
echo "FAIL: Something went wrong. Failed to encode API token."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update the ENCODED_API_KEY_FILE variable in reporter.sh by replacing the existing definition
|
|
||||||
echo "INFO: Updating ENCODED_API_KEY_FILE variable in reporter.sh..."
|
|
||||||
if ! sudo sed -i "s|^ENCODED_API_KEY_FILE=.*|ENCODED_API_KEY_FILE=\"$token_file\"|" "$script_path"; then
|
|
||||||
echo "FAIL: Failed to update ENCODED_API_KEY_FILE in reporter.sh."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "INFO: Setting permissions (chmod 644) for /var/log/ufw.log..."
|
|
||||||
sudo chmod 644 /var/log/ufw.log
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
|
||||||
# =========================== abuseipdb-ufw.service ===========================
|
|
||||||
if ask_user "Would you like to add reporter.sh as a service and start it?"; then
|
|
||||||
service_file="/etc/systemd/system/abuseipdb-ufw.service"
|
|
||||||
echo "INFO: Setting up reporter.sh as a service"
|
|
||||||
if ! sudo bash -c "cat > $service_file" <<-EOF
|
|
||||||
[Unit]
|
|
||||||
Description=UFW AbuseIPDB Reporter
|
|
||||||
After=network.target
|
|
||||||
Documentation=$REPO
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
ExecStart=$script_path
|
|
||||||
Restart=always
|
|
||||||
User=$(logname)
|
|
||||||
WorkingDirectory=$install_dir
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
then
|
|
||||||
echo "FAIL: Failed to create service file. Please check your permissions!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
|
|
||||||
if sudo systemctl enable abuseipdb-ufw.service && sudo systemctl start abuseipdb-ufw.service; then
|
|
||||||
echo "INFO: Attempting to start the abuseipdb-ufw.service..."
|
|
||||||
else
|
|
||||||
echo "FAIL: Failed to enable or start the abuseipdb-ufw.service. Please check the system logs for details."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "INFO: Waiting 8 seconds to verify the script's stability..."
|
|
||||||
sleep 8
|
|
||||||
|
|
||||||
if sudo systemctl is-active --quiet abuseipdb-ufw.service; then
|
|
||||||
echo "INFO: abuseipdb-ufw.service is running!"
|
|
||||||
sudo systemctl status abuseipdb-ufw.service --no-pager
|
|
||||||
else
|
|
||||||
echo "FAIL: abuseipdb-ufw.service failed to start."
|
|
||||||
sudo systemctl status abuseipdb-ufw.service --no-pager
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "INFO: reporter.sh will not be added as a service. Running the script directly... Press ^C to stop.\n"
|
|
||||||
if "$script_path"; then
|
|
||||||
echo "INFO: reporter.sh executed successfully."
|
|
||||||
else
|
|
||||||
echo "FAIL: Failed to execute reporter.sh!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Prompt to add the service to autostart
|
|
||||||
if ask_user "Do you want to add abuseipdb-ufw.service to autostart?"; then
|
|
||||||
if sudo systemctl enable abuseipdb-ufw.service; then
|
|
||||||
echo "INFO: Great! abuseipdb-ufw.service has been added to autostart. Installation finished!"
|
|
||||||
echo "INFO: Run 'journalctl -u abuseipdb-ufw.service -f' to view more logs."
|
|
||||||
else
|
|
||||||
echo "FAIL: Failed to add abuseipdb-ufw.service to autostart!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "INFO: abuseipdb-ufw.service will not be added to autostart. Installation finished!"
|
|
||||||
fi
|
|
||||||
181
package-lock.json
generated
Normal file
181
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
{
|
||||||
|
"name": "ufw-abuseipdb-reporter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "ufw-abuseipdb-reporter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
|
"ipaddr.js": "^2.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"globals": "^15.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@eslint/js": {
|
||||||
|
"version": "9.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz",
|
||||||
|
"integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||||
|
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/globals": {
|
||||||
|
"version": "15.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
|
||||||
|
"integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
package.json
Normal file
34
package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "ufw-abuseipdb-reporter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"keywords": [
|
||||||
|
"ufw",
|
||||||
|
"abuseipdb"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/sefinek/UFW-AbuseIPDB-Reporter/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/sefinek/UFW-AbuseIPDB-Reporter.git"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"author": "Sefinek <contact@sefinek.net> (https://sefinek.net)",
|
||||||
|
"type": "commonjs",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"up": "ncu -u && npm install && npm update && npm audit fix"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.17.0",
|
||||||
|
"globals": "^15.14.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
|
"ipaddr.js": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
214
reporter.sh
214
reporter.sh
|
|
@ -1,214 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
###
|
|
||||||
# https://github.com/sefinek/UFW-AbuseIPDB-Reporter
|
|
||||||
##
|
|
||||||
|
|
||||||
LOG_FILE="/var/log/ufw.log"
|
|
||||||
ENCODED_API_KEY_FILE="./.abuseipdb_token"
|
|
||||||
REPORTED_IPS_FILE="/tmp/ufw-abuseipdb-reporter.cache"
|
|
||||||
REPORT_INTERVAL=43200 # 12h (in seconds)
|
|
||||||
|
|
||||||
declare -A reported_ips
|
|
||||||
|
|
||||||
log() {
|
|
||||||
local level="$1"
|
|
||||||
local message="$2"
|
|
||||||
echo "[$level] $message"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the API key file exists and decode it
|
|
||||||
if [[ -f "$ENCODED_API_KEY_FILE" ]]; then
|
|
||||||
DECODED_API_KEY=$(openssl enc -d -base64 -in "$ENCODED_API_KEY_FILE")
|
|
||||||
if [[ -z "$DECODED_API_KEY" ]]; then
|
|
||||||
log "ERROR" "Failed to decode API key from $ENCODED_API_KEY_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "ERROR" "API key file not found at $ENCODED_API_KEY_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ABUSEIPDB_API_KEY="$DECODED_API_KEY"
|
|
||||||
|
|
||||||
# Check if jq, curl, or wget packages are available
|
|
||||||
if ! command -v jq &> /dev/null; then
|
|
||||||
log "ERROR" "jq is not installed. Please install jq to run this script."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then
|
|
||||||
log "ERROR" "Neither curl nor wget is available. Please install one of them to continue."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
load_reported_ips() {
|
|
||||||
if [[ -f "$REPORTED_IPS_FILE" ]]; then
|
|
||||||
while IFS= read -r line; do
|
|
||||||
[[ -z "$line" ]] && continue
|
|
||||||
IFS=' ' read -r ip report_time <<< "$line"
|
|
||||||
if [[ -n "$ip" && -n "$report_time" ]]; then
|
|
||||||
reported_ips["$ip"]=$report_time
|
|
||||||
else
|
|
||||||
log "WARN" "Invalid line format: '$line'"
|
|
||||||
fi
|
|
||||||
done < "$REPORTED_IPS_FILE"
|
|
||||||
log "INFO" "Loaded ${#reported_ips[@]} IPs from $REPORTED_IPS_FILE"
|
|
||||||
else
|
|
||||||
log "INFO" "$REPORTED_IPS_FILE does not exist. No data to load."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
save_reported_ips() {
|
|
||||||
: > "$REPORTED_IPS_FILE"
|
|
||||||
for ip in "${!reported_ips[@]}"; do
|
|
||||||
echo "$ip ${reported_ips[$ip]}" >> "$REPORTED_IPS_FILE"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
is_local_ip() {
|
|
||||||
local ip="$1"
|
|
||||||
[[ "$ip" =~ ^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|fc|fd|fe80|::1) ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
report_to_abuseipdb() {
|
|
||||||
local ip="$1" categories="$2" proto="$3" spt="$4" dpt="$5" ttl="$6" len="$7" tos="$8" timestamp="$9"
|
|
||||||
|
|
||||||
local comment="Blocked by UFW ($proto on $dpt)
|
|
||||||
Source port: $spt"
|
|
||||||
|
|
||||||
[[ -n "$ttl" ]] && comment+="
|
|
||||||
TTL: $ttl"
|
|
||||||
|
|
||||||
[[ -n "$len" ]] && comment+="
|
|
||||||
Packet length: $len"
|
|
||||||
|
|
||||||
[[ -n "$tos" ]] && comment+="
|
|
||||||
TOS: $tos"
|
|
||||||
|
|
||||||
comment+="
|
|
||||||
|
|
||||||
This report (for $ip) was generated by:
|
|
||||||
https://github.com/sefinek/UFW-AbuseIPDB-Reporter" # Please do not remove the URL to the repository of this script. I would be really grateful. 💙
|
|
||||||
|
|
||||||
local res
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
|
||||||
res=$(curl -s -X POST "https://api.abuseipdb.com/api/v2/report" \
|
|
||||||
--data-urlencode "ip=$ip" \
|
|
||||||
--data-urlencode "categories=$categories" \
|
|
||||||
--data-urlencode "comment=$comment" \
|
|
||||||
-H "Key: $ABUSEIPDB_API_KEY" \
|
|
||||||
-H "Accept: application/json")
|
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
|
||||||
res=$(wget -qO- --post-data="ip=$ip&categories=$categories&comment=$comment" \
|
|
||||||
--header="Key: $ABUSEIPDB_API_KEY" \
|
|
||||||
--header="Accept: application/json" \
|
|
||||||
"https://api.abuseipdb.com/api/v2/report")
|
|
||||||
else
|
|
||||||
log "ERROR" "Neither curl nor wget is available to send the report."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local abuse_confidence_score
|
|
||||||
abuse_confidence_score=$(echo "$res" | jq -r '.data.abuseConfidenceScore')
|
|
||||||
|
|
||||||
if [[ "$abuse_confidence_score" =~ ^[0-9]+$ ]]; then
|
|
||||||
log "INFO" "Successfully reported IP $ip to AbuseIPDB (score $abuse_confidence_score)"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log "ERROR" "Failed to report IP $ip to AbuseIPDB: $res"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
is_ip_reported_recently() {
|
|
||||||
local ip="$1"
|
|
||||||
local current_time
|
|
||||||
current_time=$(date +%s)
|
|
||||||
|
|
||||||
if [[ -v reported_ips["$ip"] ]]; then
|
|
||||||
local report_time=${reported_ips["$ip"]}
|
|
||||||
(( current_time - report_time < REPORT_INTERVAL )) && return 0
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
mark_ip_as_reported() {
|
|
||||||
local ip="$1"
|
|
||||||
reported_ips["$ip"]=$(date +%s)
|
|
||||||
}
|
|
||||||
|
|
||||||
determine_categories() {
|
|
||||||
local proto="$1"
|
|
||||||
local dpt="$2"
|
|
||||||
|
|
||||||
# See: https://www.abuseipdb.com/categories
|
|
||||||
case "$proto" in
|
|
||||||
"TCP")
|
|
||||||
case "$dpt" in
|
|
||||||
22) echo "14,22,18" ;; # Port Scan | SSH | Brute-Force
|
|
||||||
80 | 443 | 8080) echo "14,21" ;; # Port Scan | Web App Attack
|
|
||||||
25) echo "14,11" ;; # Port Scan | Email Spam
|
|
||||||
21) echo "14,5,18" ;; # Port Scan | FTP Brute-Force | Brute-Force
|
|
||||||
53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning
|
|
||||||
23 | 3389) echo "14,15,18" ;; # Port Scan | Hacking | Brute-Force
|
|
||||||
3306) echo "14,16" ;; # Port Scan | SQL Injection
|
|
||||||
6666 | 6667 | 6668 | 6669) echo "14,8" ;; # Port Scan | Fraud VoIP
|
|
||||||
9999) echo "14,6" ;; # Port Scan | Ping of Death
|
|
||||||
*) echo "14" ;; # Port Scan
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"UDP")
|
|
||||||
case "$dpt" in
|
|
||||||
53) echo "14,1,2" ;; # Port Scan | DNS Compromise | DNS Poisoning
|
|
||||||
123) echo "14,17" ;; # Port Scan | Spoofing
|
|
||||||
*) echo "14" ;; # Port Scan
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*) echo "14" ;; # Port Scan
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
process_log_line() {
|
|
||||||
local line="$1"
|
|
||||||
if [[ "$line" == *"[UFW BLOCK]"* ]]; then
|
|
||||||
local timestamp src_ip proto spt dpt ttl len tos categories
|
|
||||||
|
|
||||||
timestamp=$(echo "$line" | awk '{print $1, $2, $3}')
|
|
||||||
[[ -z "$timestamp" ]] && timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
src_ip=$(echo "$line" | grep -oP 'SRC=\K[^\s]+')
|
|
||||||
|
|
||||||
if is_local_ip "$src_ip"; then
|
|
||||||
log "INFO" "Ignoring local IP: $src_ip"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
proto=$(echo "$line" | grep -oP 'PROTO=\K[^\s]+')
|
|
||||||
spt=$(echo "$line" | grep -oP 'SPT=\K[^\s]+')
|
|
||||||
dpt=$(echo "$line" | grep -oP 'DPT=\K[^\s]+')
|
|
||||||
ttl=$(echo "$line" | grep -oP 'TTL=\K[^\s]+')
|
|
||||||
len=$(echo "$line" | grep -oP 'LEN=\K[^\s]+')
|
|
||||||
tos=$(echo "$line" | grep -oP 'TOS=\K[^\s]+')
|
|
||||||
|
|
||||||
if is_ip_reported_recently "$src_ip"; then
|
|
||||||
log "INFO" "IP $src_ip ($proto) was reported recently"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
categories=$(determine_categories "$proto" "$dpt")
|
|
||||||
|
|
||||||
log "INFO" "Reporting IP $src_ip ($proto $dpt) with categories $categories..."
|
|
||||||
if report_to_abuseipdb "$src_ip" "$categories" "$proto" "$spt" "$dpt" "$ttl" "$len" "$tos" "$timestamp"; then
|
|
||||||
mark_ip_as_reported "$src_ip"
|
|
||||||
save_reported_ips
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
load_reported_ips
|
|
||||||
|
|
||||||
log "INFO" "Starting to monitor $LOG_FILE"
|
|
||||||
|
|
||||||
tail -Fn0 "$LOG_FILE" | while read -r line; do
|
|
||||||
process_log_line "$line"
|
|
||||||
done
|
|
||||||
13
services/axios.js
Normal file
13
services/axios.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
const { version, homepage } = require('../config.js');
|
||||||
|
|
||||||
|
axios.defaults.headers.common = {
|
||||||
|
'User-Agent': `Mozilla/5.0 (compatible; UFW-AbuseIPDB-Reporter/${version}; +${homepage})`,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
};
|
||||||
|
|
||||||
|
axios.defaults.timeout = 30000;
|
||||||
|
|
||||||
|
module.exports = axios;
|
||||||
30
utils/cache.js
Normal file
30
utils/cache.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const { CACHE_FILE, REPORT_INTERVAL } = require('../config.js').MAIN;
|
||||||
|
const log = require('./log.js');
|
||||||
|
|
||||||
|
const reportedIps = new Map();
|
||||||
|
|
||||||
|
const loadReportedIps = () => {
|
||||||
|
if (fs.existsSync(CACHE_FILE)) {
|
||||||
|
fs.readFileSync(CACHE_FILE, 'utf8')
|
||||||
|
.split('\n')
|
||||||
|
.forEach(line => {
|
||||||
|
const [ip, time] = line.split(' ');
|
||||||
|
if (ip && time) reportedIps.set(ip, Number(time));
|
||||||
|
});
|
||||||
|
log(0, `Loaded ${reportedIps.size} IPs from ${CACHE_FILE}`);
|
||||||
|
} else {
|
||||||
|
log(0, `${CACHE_FILE} does not exist. No data to load.`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveReportedIps = () => fs.writeFileSync(CACHE_FILE, Array.from(reportedIps).map(([ip, time]) => `${ip} ${time}`).join('\n'), 'utf8');
|
||||||
|
|
||||||
|
const isIpReportedRecently = ip => {
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
return reportedIps.has(ip) && (now - reportedIps.get(ip) < REPORT_INTERVAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
const markIpAsReported = ip => reportedIps.set(ip, Math.floor(Date.now() / 1000));
|
||||||
|
|
||||||
|
module.exports = { loadReportedIps, saveReportedIps, isIpReportedRecently, markIpAsReported };
|
||||||
10
utils/isLocalIP.js
Normal file
10
utils/isLocalIP.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
const ipaddr = require('ipaddr.js');
|
||||||
|
|
||||||
|
module.exports = ip => {
|
||||||
|
const range = ipaddr.parse(ip).range();
|
||||||
|
return [
|
||||||
|
'unspecified', 'multicast', 'linkLocal', 'loopback', 'reserved', 'benchmarking',
|
||||||
|
'amt', 'broadcast', 'carrierGradeNat', 'private', 'as112', 'uniqueLocal',
|
||||||
|
'ipv4Mapped', 'rfc6145', '6to4', 'teredo', 'as112v6', 'orchid2', 'droneRemoteIdProtocolEntityTags',
|
||||||
|
].includes(range);
|
||||||
|
};
|
||||||
10
utils/log.js
Normal file
10
utils/log.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
const levels = {
|
||||||
|
0: { method: 'log', label: '[INFO]' },
|
||||||
|
1: { method: 'warn', label: '[WARN]' },
|
||||||
|
2: { method: 'error', label: '[FAIL]' },
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (level, msg) => {
|
||||||
|
const { method, label } = levels[level] || { method: 'log', label: '[N/A]' };
|
||||||
|
console[method](`${label} ${msg}`);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue