forked from apache2046/fail2drop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fail2drop.sh
executable file
·203 lines (194 loc) · 4.97 KB
/
fail2drop.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env bash
# fail2drop.sh - The 'once' and 'noaction' functionality of fail2drop in bash
# Usage: fail2drop.sh [-n|--noaction | CONFIGFILE]
# -n/--noaction: No system changes, just show what would be logged (check)
# If CONFIGFILE is not given, then fail2drop.yml in the current directory
# will be used if present, otherwise /etc/fail2drop.yml.
# Required: sudo[or privileged user] grep nftables(nft)[0.8.2+ work for sure]
version=0.14.8
configfile=fail2drop.yml
nft=/usr/sbin/nft
Err(){ # 1:msg
echo "$1" >&2
}
(($#>2)) &&
Err "Error: Too many arguments, only -n/--noaction / CONFIGFILE allowed" &&
exit 1
[[ ! -f $configfile ]] &&
configfile=/etc/$configfile
check=0
if [[ $1 ]]
then
[[ $1 = -n || $1 = --noaction || $1 = -c || $1 = --check ]] &&
check=1 ||
configfile=$1
fi
[[ ! -f $configfile ]] &&
Err "Error: Configfile not found: $configfile" &&
exit 2
# Analyze the configfile
whitelist=0 set=
while read -er line
do
[[ ${line:0:1} = '#' || ${#line} = 0 ]] &&
continue
case $line in
varlog:*) # Entry varlog
whitelist=0 set=
set -- $line
shift
varlog=${@%%#*}
#echo "varlog: $varlog"
;;
whitelist:)
whitelist=1 set=
#echo "whitelist:"
;;
'- '*) # IP address of whitelist
((!whitelist)) &&
Err "Error: Stray list item, not in whitelist: '$line'" &&
exit 3
okips+=(${line#- })
#echo "- ${line#- }"
;;
*:) # Set header
whitelist=0
[[ $set ]] &&
Err "Error: Incomplete set: $set" &&
exit 4
set=${line%:}
#echo "$set:"
;;
logfile:*)
whitelist=0
[[ -z $set ]] &&
Err "Error: logfile attribute not part of a set" &&
exit 5
[[ $logfile ]] &&
Err -e "Error: Previous logfile attribute unused: $logfile\nLine: $line" &&
exit 6
set -- $line
shift
logfile=${@%%#*} logfile=${logfile#\'} logfile=${logfile%\'} logfile=${logfile#\"} logfile=${logfile%\"}
#echo " logfile: $logfile"
;;
tag:*)
whitelist=0
[[ -z $set ]] &&
Err "Error: tag attribute not part of a set" &&
exit 7
[[ $tag ]] &&
Err -e "Error: Previous tag attribute unused: $tag\nLine: $line" &&
exit 8
set -- $line
shift
tag=${@%%#*} tag=${tag#\'} tag=${tag%\'} tag=${tag#\"} tag=${tag%\"}
#echo " tag: $tag"
;;
ipregex:*)
whitelist=0
[[ -z $set ]] &&
Err "Error: ipregex attribute not part of a set" &&
exit 9
[[ $ipregex ]] &&
Err -e "Error: Previous ipregex attribute unused: $ipregex\nLine: $line" &&
exit 10
set -- $line
shift
ipregex=${@%%#*} ipregex=${ipregex#\'} ipregex=${ipregex%\'} ipregex=${ipregex#\"} ipregex=${ipregex%\"}
#echo " ipregex: $ipregex"
;;
bancount:*)
whitelist=0
[[ -z $set ]] &&
Err "Error: bancount attribute not part of a set" &&
exit 11
[[ $bancount ]] &&
Err -e "Error: Previous bancount attribute unused: $bancount\nLine: $line" &&
exit 12
set -- $line
shift
bancount=${@%%#*} bancount=${bancount#\'} bancount=${bancount%\'} bancount=${bancount#\"} bancount=${bancount%\"}
#echo " bancount: $bancount"
;;
*)
Err "Error: Unrecognized entry: $line"
exit 13
esac
if [[ $set && $logfile && $tag && $ipregex && $bancount ]]
then
sets+=("$set")
logfiles+=("$logfile")
tags+=("$tag")
ipregexs+=("$ipregex")
bancounts+=("$bancount")
set= logfile= tag= ipregex= bancount=
fi
done <"$configfile"
[[ $set ]] &&
Err "Error: incomplete set '$set'" &&
exit 14
# Exit if nothing to process
((${#sets[@]})) || exit
sudo=
((EUID)) &&
sudo=sudo
if ((!check))
then # Set up nftable fail2drop
tmp=$(mktemp)
v=$($nft -v) c=
[[ ${v//[^.0-9]} > 0.9.4 ]] && c=' counter;'
$sudo $nft delete table inet fail2drop 2>/dev/null
cat <<-EOF >"$tmp"
table inet fail2drop {
set badip {type ipv4_addr;$c};
set badip6 {type ipv6_addr;$c};
chain FAIL2DROP {
type filter hook prerouting priority -300; policy accept;
ip saddr @badip counter packets 0 bytes 0 drop;
ip6 saddr @badip6 counter packets 0 bytes 0 drop;
}
}
EOF
$sudo $nft -f "$tmp"
rm -- "$tmp"
fi
declare -A ipcount
for i in ${!sets[@]}
do # Process each set
ips=$(grep "${tags[$i]}" "${logfiles[$i]}" |
grep -o "${ipregexs[$i]}" |
grep -o '[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*\.[1-9][0-9]*')
ip6s=$(grep "${tags[$i]}" "${logfiles[$i]}" |
grep -o "${ipregexs[$i]}" |
grep -o '[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}')
stamp="$(date +'%Y/%m/%d %H:%M:%S') [fail2drop.sh v$version]"
for ip in $ips
do # Process ipv4
for w in ${okips[@]}
do # Check whitelist
[[ $w = $ip ]] && continue 2
done
[[ -z ${ipcount[$ip]} ]] &&
ipcount[$ip]=1 ||
((++ipcount[$ip]))
((ipcount[$ip] == bancounts[$i])) &&
Err "$stamp '${sets[$i]}' ban $ip" &&
((!check)) &&
$sudo $nft add element inet fail2drop badip "{$ip}"
done
for ip in $ip6s
do # Process ipv6
for w in ${okips[@]}
do # Check whitelist
[[ $w = $ip ]] && continue 2
done
[[ -z ${ipcount[$ip]} ]] &&
ipcount[$ip]=1 ||
((++ipcount[$ip]))
((ipcount[$ip] == bancounts[$i])) &&
Err "$stamp '${sets[$i]}' ban $ip" &&
((!check)) &&
$sudo $nft add element inet fail2drop badip6 "{$ip}"
done
done