#!/bin/bash # info: add firewall ipset # options: NAME [SOURCE] [IPVERSION] [AUTOUPDATE] [REFRESH] # # example: v-add-firewall-ipset country-nl "https://raw.githubusercontent.com/ipverse/rir-ip/master/country/nl/ipv4-aggregated.txt" # # This function adds new ipset to system firewall #----------------------------------------------------------# # Variables & Functions # #----------------------------------------------------------# ip_name=${1} data_source=${2} ip_version=${3:-v4} autoupdate=${4:-yes} refresh=${5:-no} # Includes # shellcheck source=/etc/hestiacp/hestia.conf source /etc/hestiacp/hestia.conf # shellcheck source=/usr/local/hestia/func/main.sh source $HESTIA/func/main.sh # load config file source_conf "$HESTIA/conf/hestia.conf" #----------------------------------------------------------# # Verifications # #----------------------------------------------------------# check_args '1' "$#" 'NAME [SOURCE] [IPVERSION] [AUTOUPDATE] [FORCE]' is_format_valid 'ip_name' is_boolean_format_valid "$autoupdate" 'Automatically update IP list (yes/no)' is_boolean_format_valid "$refresh" 'Refresh IP list (yes/no)' is_system_enabled "$FIREWALL_SYSTEM" 'FIREWALL_SYSTEM' # Perform verification if read-only mode is enabled check_hestia_demo_mode # Define variables for ipset configuration ipset_hstobject='../../data/firewall/ipset' IPSET_BIN="$(command -v ipset)" IPSET_PATH="$HESTIA/data/firewall/ipset" # Ensure ipset is installed if [ -z "$IPSET_BIN" ]; then if [ -f '/etc/redhat-release' ]; then dnf install -q -y ipset > /dev/null else apt-get --quiet --yes install ipset > /dev/null fi check_result $? "Installing IPset package" IPSET_BIN="$(which ipset)" check_result $? "IPset binary not found" fi # Ensure ipset configuration path and master file exist before attempting to parse mkdir -p "$IPSET_PATH" if [ ! -f "$HESTIA/data/firewall/ipset.conf" ]; then touch $HESTIA/data/firewall/ipset.conf fi if [ -z "$data_source" ]; then if [ ! -f "${IPSET_PATH}.conf" ] || [[ ! $(grep "LISTNAME='$ip_name'" "${IPSET_PATH}.conf") ]]; then check_args '2' "$#" 'NAME SOURCE [IPVERSION] [AUTOUPDATE] [FORCE]' fi data_source="$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$SOURCE')" ip_version="$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$IP_VERSION')" else is_object_new "$ipset_hstobject" 'LISTNAME' "$ip_name" fi if [ "$ip_version" != "v4" ] && [ "$ip_version" != "v6" ]; then check_result "$E_INVALID" "invalid ip version, valid: (v4|v6)" fi if ! echo "$data_source" | egrep -q '^(https?|script|file):'; then check_result "$E_INVALID" "invalid ipset source, valid: (http[s]://|script:|file:)" fi IPSET_FILE="${ip_name}.${ip_version}" IPSET_MIN_SIZE=10 #----------------------------------------------------------# # Action # #----------------------------------------------------------# # Generate ip lists file if missing or required refresh if [ ! -f "${IPSET_PATH}/${IPSET_FILE}.iplist" ] || [ "$refresh" = "yes" ]; then iplist_tempfile=$(mktemp) if [[ "$data_source" =~ ^https?:// ]]; then wget --tries=3 --timeout=15 --read-timeout=15 --waitretry=3 --no-dns-cache --quiet "$data_source" -O "$iplist_tempfile" check_result $? "Downloading ip list" # Advanced: execute script with the same basename for aditional pre-processing # ex: if [ -x "${IPSET_PATH}/${IPSET_FILE}.sh" ]; then if [ -e '/etc/redhat-release' ]; then preprocess_output="$(cat "$iplist_tempfile" | setpriv --clear-groups --reuid nobody --regid nobody -- ${IPSET_PATH}/${IPSET_FILE}.sh "$ip_name" "$iplist_tempfile")" else preprocess_output="$(cat "$iplist_tempfile" | setpriv --clear-groups --reuid nobody --regid nogroup -- ${IPSET_PATH}/${IPSET_FILE}.sh "$ip_name" "$iplist_tempfile")" fi check_result $? "Preprocessing script failed (${IPSET_FILE}.sh)" [[ "$preprocess_output" ]] && echo "$preprocess_output" > "$iplist_tempfile" fi elif [[ "$data_source" =~ ^script:/ ]]; then # Generate the ip list file trough a external script # ex: compiling a ip list from multiple sources on demand if [ -x "${data_source#script:}" ]; then if [ -e '/etc/redhat-release' ]; then setpriv --clear-groups --reuid nobody --regid nobody -- ${data_source#script:} "$ip_name" > "$iplist_tempfile" else setpriv --clear-groups --reuid nobody --regid nogroup -- ${data_source#script:} "$ip_name" > "$iplist_tempfile" fi check_result $? "Running custom ip list update script" fi elif [[ "$data_source" =~ ^file:/ ]]; then # Use a external ip-list file managed by other apps # ex: Using a ip list that is continuously updated [ -f "${data_source#file:}" ] && cp -f "${data_source#file:}" "$iplist_tempfile" fi # Cleanup ip list sed -r -i -e 's/[;#].*$//' -e 's/[ \t]*$//' -e '/^$/d' "$iplist_tempfile" if [[ $ip_version == 'v4' ]]; then sed -i -r -n -e '/^((1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])/p' "$iplist_tempfile" elif [[ $ip_version == 'v6' ]]; then sed -i -r -n -e '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}/p' "$iplist_tempfile" fi # Validate iplist file size iplist_size=$(sed -r -e '/^#|^$/d' "$iplist_tempfile" | wc -l) [[ "$iplist_size" -le "$IPSET_MIN_SIZE" ]] && check_result "$E_INVALID" "IP list file too small (<${IPSET_MIN_SIZE}), ignoring" mv -f "$iplist_tempfile" "${IPSET_PATH}/${IPSET_FILE}.iplist" fi # Load ipset in kernel inet_ver="inet" [ "$ip_version" == "v6" ] && inet_ver="inet6" $IPSET_BIN -quiet create -exist "$ip_name" hash:net family $inet_ver $IPSET_BIN -quiet destroy "${ip_name}-tmp" $IPSET_BIN create "${ip_name}-tmp" -exist hash:net family $inet_ver maxelem 1048576 $IPSET_BIN flush "${ip_name}-tmp" sed -rn -e '/^#|^$/d' -e "s/^(.*)/add ${ip_name}-tmp \\1/p" "${IPSET_PATH}/${IPSET_FILE}.iplist" | $IPSET_BIN -quiet restore check_result $? "Populating ipset table" $IPSET_BIN swap "${ip_name}-tmp" "${ip_name}" $IPSET_BIN -quiet destroy "${ip_name}-tmp" # Generating timestamp time_n_date=$(date +'%T %F') time=$(echo "$time_n_date" | cut -f 1 -d \ ) date=$(echo "$time_n_date" | cut -f 2 -d \ ) if [ ! -f "${IPSET_PATH}.conf" ] || [ -z "$(get_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$LISTNAME')" ]; then # Concatenating rule str="LISTNAME='$ip_name' IP_VERSION='$ip_version' SOURCE='$data_source'" str="$str AUTOUPDATE='$autoupdate' SUSPENDED='no'" str="$str TIME='$time' DATE='$date'" echo "$str" >> $HESTIA/data/firewall/ipset.conf log_type="added" elif [ "$refresh" = "yes" ]; then # Update iplist last regen time update_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$TIME' "$time" update_object_value "$ipset_hstobject" 'LISTNAME' "$ip_name" '$DATE' "$date" log_type="refreshed" fi # Changing permissions chmod 660 $HESTIA/data/firewall/ipset.conf chmod 660 "${IPSET_PATH}/${IPSET_FILE}.iplist" # Install ipset daily cron updater if ! grep --silent --no-messages "v-update-firewall-ipset" $HESTIA/data/queue/daily.pipe; then cmd="$BIN/v-update-firewall-ipset yes" echo "$cmd" >> $HESTIA/data/queue/daily.pipe fi #----------------------------------------------------------# # Hestia # #----------------------------------------------------------# # Logging $BIN/v-log-action "system" "Info" "Firewall" "IPset IP list ${log_type:-loaded} (Name: $ip_name, IP version: $ip_version, Autoupdate: $autoupdate)." log_event "$OK" "$ARGUMENTS" exit