Skip to content

Commit db38fa7

Browse files
authored
Beta 5
This version makes a big jump forward: 🌐 Multi-Server Support – view & manage multiple Fail2Ban servers from one dashboard πŸ” Authentication & Roles – viewer (read-only) and admin (ban/unban, blocklist mgmt.) πŸ“‚ Per-server archives & blocklists for cleaner organization ⚑ Performance improvements + refined marker system (repeat bans & ban increases) It’s still shell + PHP (no DB, no frameworks), designed for small to medium setups, and works great on Raspberry Pi or VPS.
2 parents eda05ae + b4bb587 commit db38fa7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+3049
-1173
lines changed

β€ŽBackend/fail2ban_log2json.shβ€Ž

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
# This is the Logfile-Reader for the local installation - so you will have to edit the OUTPUT_JSON_DIR to fit your Webserver Installation
3+
#
4+
LOGFILE="/var/log/fail2ban.log"
5+
OUTPUT_JSON_DIR="/var/www/html/Fail2Ban-Report/archive/<SERVERNAME>/fail2ban"
6+
# <SERVERNAME> is the Name of your local Server Folder in archive/
7+
8+
TODAY=$(date +"%Y-%m-%d")
9+
OUTPUT_JSON_FILE="$OUTPUT_JSON_DIR/fail2ban-events-$(date +"%Y%m%d").json"
10+
mkdir -p "$OUTPUT_JSON_DIR"
11+
12+
echo "[" > "$OUTPUT_JSON_FILE"
13+
14+
# Grep all relevant Events
15+
grep -E "(Ban|Unban)" "$LOGFILE" | awk -v today="$TODAY" '
16+
{
17+
timestamp = $1 " " $2;
18+
if (index(timestamp, today) != 1) next;
19+
20+
action = "";
21+
ip = "";
22+
if ($0 ~ /Increase Ban/) {
23+
action = "Increase Ban";
24+
match($0, /Increase Ban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
25+
if (m[1]) ip = m[1];
26+
} else if ($0 ~ /Ban/) {
27+
action = "Ban";
28+
match($0, /Ban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
29+
if (m[1]) ip = m[1];
30+
} else if ($0 ~ /Unban/) {
31+
action = "Unban";
32+
match($0, /Unban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
33+
if (m[1]) ip = m[1];
34+
}
35+
36+
# Extract jail from first non-numeric bracketed section
37+
text = $0;
38+
c = 0;
39+
delete arr;
40+
while (match(text, /\[[^]]+\]/)) {
41+
content = substr(text, RSTART+1, RLENGTH-2);
42+
c++;
43+
arr[c] = content;
44+
text = substr(text, RSTART + RLENGTH);
45+
}
46+
47+
jail = "unknown";
48+
for(i=1; i<=c; i++) {
49+
if (arr[i] !~ /^[0-9]+$/) {
50+
jail = arr[i];
51+
break;
52+
}
53+
}
54+
55+
if (ip != "") {
56+
printf " {\n \"timestamp\": \"%s\",\n \"action\": \"%s\",\n \"ip\": \"%s\",\n \"jail\": \"%s\"\n },\n", timestamp, action, ip, jail;
57+
}
58+
}
59+
' >> "$OUTPUT_JSON_FILE"
60+
61+
# Remove last comma
62+
if [ -s "$OUTPUT_JSON_FILE" ]; then
63+
sed -i '$ s/},/}/' "$OUTPUT_JSON_FILE"
64+
fi
65+
66+
echo "]" >> "$OUTPUT_JSON_FILE"
67+
echo "βœ… JSON created: $OUTPUT_JSON_FILE"
Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#!/bin/bash
2-
2+
#
3+
# This is the Firewall-Update Script for the local installation - you you would have to edit the BLOCKLIST_DIR to fit your Installation
4+
#
35
set -euo pipefail
46

57
# --- Configuration ---
6-
BLOCKLIST_DIR="/var/www/vhosts/suble.org/xbkupx/Fail2Ban-Report/archive"
7-
LOGFILE="/opt/Fail2Ban-Report/Firewall-Report.log"
8-
LOGGING=true # Set to true to enable logging
8+
BLOCKLIST_DIR="/var/www/html/Fail2Ban-Report/archive/<SERVERNAME>/blocklists" # Or Webarchive when local
9+
LOGFILE="/opt/Fail2Ban-Report/Firewall.log"
10+
LOGGING=false # Set to true to enable logging
911

1012
# --- Set PATH ---
1113
export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
@@ -49,37 +51,50 @@
4951
fi
5052

5153
# Extract active and inactive IPs
52-
active_ips=$(jq -r '.[] | select(.active != false) | .ip' "$FILE")
53-
inactive_ips=$(jq -r '.[] | select(.active == false) | .ip' "$FILE")
54+
mapfile -t active_ips < <(jq -r '.[] | select(.active != false) | .ip' "$FILE")
55+
mapfile -t inactive_ips < <(jq -r '.[] | select(.active == false) | .ip' "$FILE")
56+
57+
blocked_success=()
5458

55-
# Block new IPs and update pending flag
56-
for ip in $active_ips; do
59+
# --- BLOCK: Collect all new IPs and block them ---
60+
for ip in "${active_ips[@]}"; do
5761
if ! grep -qw "$ip" "$TMP_BLOCKED"; then
5862
log "Blocking IP: $ip"
5963
if ufw deny from "$ip"; then
60-
log "Blocked $ip successfully, updating pending flag"
61-
tmp_file=$(mktemp)
62-
jq --arg ip "$ip" 'map(if .ip == $ip then .pending = false else . end)' "$FILE" > "$tmp_file" \
63-
&& mv "$tmp_file" "$FILE"
64+
blocked_success+=("$ip")
6465
else
6566
log "Failed to block $ip via ufw"
6667
fi
6768
fi
6869
done
6970

70-
# Remove UFW rules for inactive IPs
71-
for ip in $inactive_ips; do
71+
# Reload UFW once after all block actions
72+
if ((${#blocked_success[@]} > 0)); then
73+
log "Reloading UFW after block actions"
74+
ufw reload
75+
fi
76+
77+
# --- UNBLOCK: Process each inactive IP individually ---
78+
for ip in "${inactive_ips[@]}"; do
7279
mapfile -t rules < <(ufw status numbered | grep "$ip" | grep "DENY IN" | tac)
7380
for rule in "${rules[@]}"; do
7481
rule_number=$(echo "$rule" | awk -F'[][]' '{print $2}')
75-
log "Removing UFW rule #$rule_number for IP: $ip"
76-
ufw --force delete "$rule_number"
82+
if [[ -n "$rule_number" ]]; then
83+
log "Removing UFW rule #$rule_number for IP: $ip"
84+
ufw --force delete "$rule_number"
85+
fi
7786
done
7887
done
7988

80-
# Clean up JSON by removing inactive entries
89+
# --- JSON Update: pending=false for blocked_success, remove inactive entries ---
8190
tmp_file=$(mktemp)
82-
jq 'map(select(.active != false))' "$FILE" > "$tmp_file" && mv "$tmp_file" "$FILE"
91+
BLOCK_JSON=$(printf '%s\n' "${blocked_success[@]:-}" | jq -R . | jq -s .)
92+
jq --argjson ips "$BLOCK_JSON" '
93+
map(
94+
if (.ip as $ip | $ips | index($ip)) then .pending = false else . end
95+
)
96+
| map(select(.active != false))
97+
' "$FILE" > "$tmp_file" && mv "$tmp_file" "$FILE"
8398

8499
# Set ownership and permissions
85100
chown www-data:www-data "$FILE"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# Fail2Ban-Report-cronscript.sh
3+
4+
# change dir for cronjobs
5+
cd /opt/Fail2Ban-Report/Backend
6+
7+
LOGFILE="/opt/Fail2Ban-Report/cronjobs.log"
8+
9+
echo "----- cronrun start ------ $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOGFILE"
10+
11+
# Step 1: JSON generation
12+
echo "πŸ“ Step 1: Generating JSON from Fail2Ban logs..." >> "$LOGFILE"
13+
./fail2ban_log2json.sh >> "$LOGFILE" 2>&1
14+
sleep 1
15+
16+
# Step 2: Check for updates
17+
echo "πŸ”Ž Step 2: Checking for updates..." >> "$LOGFILE"
18+
./download-checker.sh >> "$LOGFILE" 2>&1
19+
DOWNLOAD_STATUS=$?
20+
21+
# Step 3: Run firewall & sync only if updates available
22+
if [ $DOWNLOAD_STATUS -eq 0 ]; then
23+
echo "βœ… Updates found, running sync cycle..." >> "$LOGFILE"
24+
./firewall-update.sh >> "$LOGFILE" 2>&1
25+
./syncback.sh >> "$LOGFILE" 2>&1
26+
else
27+
echo "ℹ️ No Updates, firewall & syncback skipped" >> "$LOGFILE"
28+
fi
29+
30+
# Step 4: Always mark end of cronrun
31+
echo "----- cronrun done! ------ $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOGFILE"

β€ŽBackend/multi/config.envβ€Ž

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# === Shared Fail2Ban Report Client Config ===
2+
3+
# Authentication
4+
CLIENT_USER="MyClientName"
5+
CLIENT_PASS="MyPassword"
6+
CLIENT_UUID="MyUUID"
7+
8+
# Server URLs
9+
ENDPOINT_URL="https://my.server.tld/Fail2Ban-Report/endpoint/index.php"
10+
UPDATE_URL="https://my.server.tld/Fail2Ban-Report/endpoint/update.php"
11+
DOWNLOAD_URL="https://my.server.tld/Fail2Ban-Report/endpoint/download.php"
12+
BACKSYNC_URL="https://my.server.tld/Fail2Ban-Report/endpoint/backsync.php"
13+
14+
# Local Paths
15+
OUTPUT_JSON_DIR="/opt/Fail2Ban-Report/archive/fail2ban"
16+
BLOCKLIST_DIR="/opt/Fail2Ban-Report/archive/blocklists"
17+
CLIENT_LOG="/var/log/fail2ban-report-client.log"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# === Config laden ===
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
source "$SCRIPT_DIR/config.env"
7+
8+
mkdir -p "$BLOCKLIST_DIR"
9+
10+
# --- Update-Check ---
11+
response=$(curl -s -X POST "$UPDATE_URL" \
12+
-F "username=$CLIENT_USER" \
13+
-F "password=$CLIENT_PASS" \
14+
-F "uuid=$CLIENT_UUID")
15+
16+
echo "Server Response:"
17+
echo "$response"
18+
19+
updates=$(echo "$response" | jq -r '.updates | length')
20+
21+
if [ "$updates" -eq 0 ]; then
22+
echo "ℹ️ No updates available."
23+
exit 1
24+
fi
25+
26+
echo "βœ… Updates available: $updates blocklist(s)."
27+
28+
# --- download Blocklists ---
29+
for FILE in $(echo "$response" | jq -r '.updates[]'); do
30+
echo "⬇️ Downloading $FILE ..."
31+
curl -s -X POST "$DOWNLOAD_URL?file=$FILE" \
32+
-d "username=$CLIENT_USER" \
33+
-d "password=$CLIENT_PASS" \
34+
-d "uuid=$CLIENT_UUID" \
35+
-o "$BLOCKLIST_DIR/$FILE"
36+
37+
if [ $? -eq 0 ] && [ -s "$BLOCKLIST_DIR/$FILE" ]; then
38+
echo "βœ… $FILE downloaded successfully."
39+
else
40+
echo "❌ Failed to download $FILE"
41+
fi
42+
done
43+
44+
echo "πŸŽ‰ All blocklists processed."
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/bin/bash
2+
# fail2ban_log2json.sh
3+
set -euo pipefail
4+
5+
# === Config laden ===
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
source "$SCRIPT_DIR/config.env"
8+
9+
# === Lokale Variablen ===
10+
LOGFILE="/var/log/fail2ban.log"
11+
TODAY=$(date +"%Y-%m-%d")
12+
TODAY_SHORT=$(date +"%Y%m%d")
13+
OUTPUT_JSON_FILE="$OUTPUT_JSON_DIR/fail2ban-events-$TODAY_SHORT.json"
14+
mkdir -p "$OUTPUT_JSON_DIR"
15+
16+
echo "[" > "$OUTPUT_JSON_FILE"
17+
18+
# Grep all relevant Events
19+
grep -E "(Ban|Unban)" "$LOGFILE" | awk -v today="$TODAY" '
20+
{
21+
timestamp = $1 " " $2;
22+
if (index(timestamp, today) != 1) next;
23+
24+
action = "";
25+
ip = "";
26+
if ($0 ~ /Increase Ban/) {
27+
action = "Increase Ban";
28+
match($0, /Increase Ban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
29+
if (m[1]) ip = m[1];
30+
} else if ($0 ~ /Ban/) {
31+
action = "Ban";
32+
match($0, /Ban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
33+
if (m[1]) ip = m[1];
34+
} else if ($0 ~ /Unban/) {
35+
action = "Unban";
36+
match($0, /Unban ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, m);
37+
if (m[1]) ip = m[1];
38+
}
39+
40+
# Extract jail from first non-numeric bracketed section
41+
text = $0;
42+
c = 0;
43+
delete arr;
44+
while (match(text, /\[[^]]+\]/)) {
45+
content = substr(text, RSTART+1, RLENGTH-2);
46+
c++;
47+
arr[c] = content;
48+
text = substr(text, RSTART + RLENGTH);
49+
}
50+
51+
jail = "unknown";
52+
for(i=1; i<=c; i++) {
53+
if (arr[i] !~ /^[0-9]+$/) {
54+
jail = arr[i];
55+
break;
56+
}
57+
}
58+
59+
if (ip != "") {
60+
printf " {\n \"timestamp\": \"%s\",\n \"action\": \"%s\",\n \"ip\": \"%s\",\n \"jail\": \"%s\"\n },\n", timestamp, action, ip, jail;
61+
}
62+
}
63+
' >> "$OUTPUT_JSON_FILE"
64+
65+
if [ -s "$OUTPUT_JSON_FILE" ]; then
66+
sed -i '$ s/},/}/' "$OUTPUT_JSON_FILE"
67+
fi
68+
echo "]" >> "$OUTPUT_JSON_FILE"
69+
echo "βœ… JSON created: $OUTPUT_JSON_FILE"
70+
71+
# === Upload JSON to Server ===
72+
upload_file() {
73+
local file=$1
74+
echo "πŸ”„ Uploading $file ..."
75+
76+
response=$(curl -s -w "\n%{http_code}" -X POST "$ENDPOINT_URL" \
77+
-F "username=$CLIENT_USER" \
78+
-F "password=$CLIENT_PASS" \
79+
-F "uuid=$CLIENT_UUID" \
80+
-F "file=@$file" || true)
81+
82+
http_code=$(tail -n1 <<< "$response")
83+
body=$(sed '$d' <<< "$response")
84+
85+
if [ "$http_code" -eq 0 ]; then
86+
echo "$(date '+%Y-%m-%d %H:%M:%S') ❌ Connection failed to $ENDPOINT_URL" | tee -a "$CLIENT_LOG"
87+
return 1
88+
fi
89+
90+
echo "$(date '+%Y-%m-%d %H:%M:%S') HTTP Status: $http_code" | tee -a "$CLIENT_LOG"
91+
echo "$(date '+%Y-%m-%d %H:%M:%S') Response Body: $body" | tee -a "$CLIENT_LOG"
92+
93+
if [ "$http_code" -ne 200 ]; then
94+
echo "$(date '+%Y-%m-%d %H:%M:%S') ❌ Upload failed (HTTP $http_code)" | tee -a "$CLIENT_LOG"
95+
return 1
96+
fi
97+
98+
success=$(echo "$body" | jq -r '.success // empty')
99+
if [ "$success" != "true" ]; then
100+
message=$(echo "$body" | jq -r '.message // empty')
101+
echo "$(date '+%Y-%m-%d %H:%M:%S') ❌ Endpoint rejected the file: $message" | tee -a "$CLIENT_LOG"
102+
return 1
103+
fi
104+
105+
echo "$(date '+%Y-%m-%d %H:%M:%S') βœ… Upload succeeded for $file" | tee -a "$CLIENT_LOG"
106+
}
107+
108+
upload_file "$OUTPUT_JSON_FILE"
109+
echo "βœ… Upload completed."
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
set -euo pipefail
44

55
# --- Configuration ---
6-
BLOCKLIST_DIR="/path/to/web/archive"
7-
LOGFILE="/var/log/Fail2Ban-Report.log"
6+
BLOCKLIST_DIR="/opt/Fail2Ban-Report/archive/blocklists" # Or Webarchive when local
7+
LOGFILE="/opt/Fail2Ban-Report/Firewall.log"
88
LOGGING=true # Set to true to enable logging
99

1010
# --- Set PATH ---

0 commit comments

Comments
Β (0)