- complete rewrite of banIP to support nftables - all sets are handled in a separate nft table/namespace 'banIP' - for incoming blocking it uses the inet input hook, for outgoing blocking it uses the inet forward hook - full IPv4 and IPv6 support - supports nft atomic set loading - supports blocking by ASN numbers and by iso country codes - 42 preconfigured external feeds are available, plus local allow- and blocklist - supports local allow- and blocklist (IPv4, IPv6, CIDR notation or domain names) - auto-add the uplink subnet to the local allowlist - provides a small background log monitor to ban unsuccessful login attempts in real-time - the logterms for the log monitor service can be freely defined via regex - auto-add unsuccessful LuCI, nginx, Asterisk or ssh login attempts to the local blocklist - fast feed processing as they are handled in parallel as background jobs - per feed it can be defined whether the input chain or the forward chain should be blocked (default: both chains) - automatic blocklist backup & restore, the backups will be used in case of download errors or during startup - automatically selects one of the following download utilities with ssl support: aria2c, curl, uclient-fetch or wget - supports a 'allowlist only' mode, this option restricts internet access from/to a small number of secure websites/IPs - provides comprehensive runtime information - provides a detailed set report - provides a set search engine for certain IPs - feed parsing by fast & flexible regex rulesets - minimal status & error logging to syslog, enable debug logging to receive more output - procd based init system support (start/stop/restart/reload/status/report/search) - procd network interface trigger support - ability to add new banIP feeds on your own - add a readme with all available options/feeds to customize your installation to your needs - a new LuCI frontend will be available in due course Signed-off-by: Dirk Brenken <dev@brenken.org>
193 lines
5.7 KiB
Bash
Executable file
193 lines
5.7 KiB
Bash
Executable file
#!/bin/sh
|
|
# banIP main service script - ban incoming and outgoing ip adresses/subnets via sets in nftables
|
|
# Copyright (c) 2018-2023 Dirk Brenken (dev@brenken.org)
|
|
# This is free software, licensed under the GNU General Public License v3.
|
|
|
|
# (s)hellcheck exceptions
|
|
# shellcheck disable=all
|
|
|
|
ban_action="${1}"
|
|
ban_starttime="$(date "+%s")"
|
|
ban_funlib="/usr/lib/banip-functions.sh"
|
|
[ -z "$(command -v "f_system")" ] && . "${ban_funlib}"
|
|
|
|
# load config and set banIP environment
|
|
#
|
|
f_conf
|
|
f_log "info" "start banIP processing (${ban_action})"
|
|
f_genstatus "processing"
|
|
f_tmp
|
|
f_fetch
|
|
f_getif
|
|
f_getdev
|
|
f_getsub
|
|
f_mkdir "${ban_backupdir}"
|
|
f_mkfile "${ban_blocklist}"
|
|
f_mkfile "${ban_allowlist}"
|
|
|
|
# firewall check
|
|
#
|
|
if [ "${ban_action}" != "reload" ]; then
|
|
if [ -x "${ban_fw4cmd}" ]; then
|
|
cnt=0
|
|
while [ "${cnt}" -lt "10" ] && ! /etc/init.d/firewall status | grep -q "^active"; do
|
|
cnt="$((cnt + 1))"
|
|
sleep 1
|
|
done
|
|
if ! /etc/init.d/firewall status | grep -q "^active"; then
|
|
f_log "err" "nft based firewall/fw4 not functional"
|
|
fi
|
|
else
|
|
f_log "err" "nft based firewall/fw4 not found"
|
|
fi
|
|
fi
|
|
|
|
# init nft namespace
|
|
#
|
|
if [ "${ban_action}" != "reload" ] || ! "${ban_nftcmd}" -t list table inet banIP >/dev/null 2>&1; then
|
|
if f_nftinit "${ban_tmpfile}".init.nft; then
|
|
f_log "info" "nft namespace initialized"
|
|
else
|
|
f_log "err" "nft namespace can't be initialized"
|
|
fi
|
|
fi
|
|
|
|
# handle downloads
|
|
#
|
|
f_log "info" "start banIP download processes"
|
|
if [ "${ban_allowlistonly}" = "1" ]; then
|
|
ban_feed=""
|
|
else
|
|
json_init
|
|
if ! json_load_file "${ban_basedir}/ban_feeds.json" >/dev/null 2>&1; then
|
|
f_log "err" "banIP feed file can't be loaded"
|
|
fi
|
|
[ "${ban_deduplicate}" = "1" ] && printf "\n" >"${ban_tmpfile}.deduplicate"
|
|
fi
|
|
|
|
cnt="1"
|
|
for feed in allowlist ${ban_feed} blocklist; do
|
|
# local feeds
|
|
#
|
|
if [ "${feed}" = "allowlist" ] || [ "${feed}" = "blocklist" ]; then
|
|
for proto in MAC 4 6; do
|
|
[ "${feed}" = "blocklist" ] && wait
|
|
(f_down "${feed}" "${proto}") &
|
|
[ "${feed}" = "blocklist" ] || { [ "${feed}" = "allowlist" ] && [ "${proto}" = "MAC" ]; } && wait
|
|
hold="$((cnt % ban_cores))"
|
|
[ "${hold}" = "0" ] && wait
|
|
cnt="$((cnt + 1))"
|
|
done
|
|
wait
|
|
continue
|
|
fi
|
|
|
|
# read external feed information
|
|
#
|
|
if ! json_select "${feed}" >/dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
json_objects="url_4 rule_4 url_6 rule_6 flag"
|
|
for object in ${json_objects}; do
|
|
eval json_get_var feed_"${object}" '${object}' >/dev/null 2>&1
|
|
done
|
|
json_select ..
|
|
# handle IPv4/IPv6 feeds with the same/single download URL
|
|
#
|
|
if [ "${feed_url_4}" = "${feed_url_6}" ]; then
|
|
if [ "${ban_protov4}" = "1" ] && [ -n "${feed_url_4}" ] && [ -n "${feed_rule_4}" ]; then
|
|
(f_down "${feed}" "4" "${feed_url_4}" "${feed_rule_4}" "${feed_flag}") &
|
|
feed_url_6="local"
|
|
wait
|
|
fi
|
|
if [ "${ban_protov6}" = "1" ] && [ -n "${feed_url_6}" ] && [ -n "${feed_rule_6}" ]; then
|
|
(f_down "${feed}" "6" "${feed_url_6}" "${feed_rule_6}" "${feed_flag}") &
|
|
hold="$((cnt % ban_cores))"
|
|
[ "${hold}" = "0" ] && wait
|
|
cnt="$((cnt + 1))"
|
|
fi
|
|
continue
|
|
fi
|
|
# handle IPv4/IPv6 feeds with separated download URLs
|
|
#
|
|
if [ "${ban_protov4}" = "1" ] && [ -n "${feed_url_4}" ] && [ -n "${feed_rule_4}" ]; then
|
|
(f_down "${feed}" "4" "${feed_url_4}" "${feed_rule_4}" "${feed_flag}") &
|
|
hold="$((cnt % ban_cores))"
|
|
[ "${hold}" = "0" ] && wait
|
|
cnt="$((cnt + 1))"
|
|
fi
|
|
if [ "${ban_protov6}" = "1" ] && [ -n "${feed_url_6}" ] && [ -n "${feed_rule_6}" ]; then
|
|
(f_down "${feed}" "6" "${feed_url_6}" "${feed_rule_6}" "${feed_flag}") &
|
|
hold="$((cnt % ban_cores))"
|
|
[ "${hold}" = "0" ] && wait
|
|
cnt="$((cnt + 1))"
|
|
fi
|
|
done
|
|
wait
|
|
|
|
# start domain lookup
|
|
#
|
|
f_log "info" "start detached banIP domain lookup"
|
|
(f_lookup "allowlist") &
|
|
hold="$((cnt % ban_cores))"
|
|
[ "${hold}" = "0" ] && wait
|
|
(f_lookup "blocklist") &
|
|
|
|
# tidy up
|
|
#
|
|
f_rmset
|
|
f_rmdir "${ban_tmpdir}"
|
|
f_genstatus "active"
|
|
f_log "info" "finished banIP download processes"
|
|
rm -rf "${ban_lock}"
|
|
|
|
# start log service
|
|
#
|
|
if [ -x "${ban_logreadcmd}" ] && [ -n "${ban_logterm%%??}" ]; then
|
|
f_log "info" "start detached banIP log service"
|
|
|
|
nft_expiry="$(printf "%s" "${ban_nftexpiry}" | grep -oE "([0-9]+[h|m|s]$)")"
|
|
[ -n "${nft_expiry}" ] && nft_expiry="timeout ${nft_expiry}"
|
|
|
|
# read log continuously with given logterms
|
|
#
|
|
"${ban_logreadcmd}" -fe "${ban_logterm%%??}" 2>/dev/null |
|
|
while read -r line; do
|
|
# IPv4 log parsing
|
|
#
|
|
ip="$(printf "%s" "${line}" | "${ban_awkcmd}" 'BEGIN{RS="(([0-9]{1,3}\\.){3}[0-9]{1,3})+"}{if(!seen[RT]++)printf "%s ",RT}')"
|
|
ip="$(f_trim "${ip}")"
|
|
ip="${ip##* }"
|
|
[ -n "${ip}" ] && proto="v4"
|
|
if [ -z "${proto}" ]; then
|
|
# IPv6 log parsing
|
|
#
|
|
ip="$(printf "%s" "${line}" | "${ban_awkcmd}" 'BEGIN{RS="([A-Fa-f0-9]{1,4}::?){3,7}[A-Fa-f0-9]{1,4}"}{if(!seen[RT]++)printf "%s ",RT}')"
|
|
ip="$(f_trim "${ip}")"
|
|
ip="${ip##* }"
|
|
[ -n "${ip}" ] && proto="v6"
|
|
fi
|
|
if [ -n "${proto}" ] && ! "${ban_nftcmd}" get element inet banIP blocklist"${proto}" "{ ${ip} }" >/dev/null 2>&1; then
|
|
f_log "info" "suspicious IP found '${ip}'"
|
|
log_raw="$("${ban_logreadcmd}" -l "${ban_loglimit}" 2>/dev/null)"
|
|
log_count="$(printf "%s\n" "${log_raw}" | grep -c "found '${ip}'")"
|
|
if [ "${log_count}" -ge "${ban_logcount}" ]; then
|
|
if "${ban_nftcmd}" add element inet banIP "blocklist${proto}" "{ ${ip} ${nft_expiry} }" >/dev/null 2>&1; then
|
|
f_log "info" "added IP '${ip}' (${nft_expiry:-"-"}) to blocklist${proto} set"
|
|
if [ "${ban_autoblocklist}" = "1" ] && ! grep -q "^${ip}" "${ban_blocklist}"; then
|
|
printf "%-42s%s\n" "${ip}" "# added on $(date "+%Y-%m-%d %H:%M:%S")" >>"${ban_blocklist}"
|
|
f_log "info" "added IP '${ip}' to local blocklist"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# start no-op service loop
|
|
#
|
|
else
|
|
f_log "info" "start detached no-op banIP service (logterms are missing)"
|
|
while :; do
|
|
sleep 1
|
|
done
|
|
fi
|