One of those challenges in operations is the constant threat of cyberattacks from different parts of the world. One way to mitigate this risk is by utilizing iptables, as its natively installed on most systems and quite easy to use.

How it Works

By using GeoIP databases, iptables can be configured to identify the originating country of an IP address. With this information, rules can be crafted to block or allow traffic from entire countries. This kind of country-based blocking can be extremely useful in some scenarios.

Why it’s Good to Have

  1. Targeted Security: If your system or network is experiencing targeted attacks from specific countries, blocking those countries entirely can provide an immediate mitigation strategy.

  2. Compliance and Regulation: Certain legal and regulatory frameworks may require you to limit or block access to or from particular geographic locations. Using iptables in conjunction with GeoIP databases makes this process straightforward.

  3. Performance Optimization: If your services are intended for a specific region, blocking unnecessary countries can reduce unwanted traffic and optimize the performance of your applications.

  4. Customization: The iptables block script is highly customizable, allowing for complex rules that can fit specific security needs.

How to Implement It

Implementing a country-based block with iptables is relatively simple. The basic steps include:

  1. Installing the necessary GeoIP modules.
  2. Updating the GeoIP database.
  3. Crafting the iptables rules using the country codes you want to block or allow.
  4. Monitoring and tweaking the rules as needed.

A Note of Caution

While powerful, country-based blocking should be used with care. Overly broad rules can inadvertently block legitimate users, impacting your service’s accessibility and potentially harming your business or reputation. A comprehensive understanding of your network’s needs and careful implementation are essential.

This one I have used is fairly robust and easy to use when the need has arised: nixCraft Linux Iptables Just Block By Country

iptables-country-block.sh

The script below is the modified version with a more robust action set, thnx nixcraft and David Picard.

#!/bin/bash
#
# iptables-country-block  Add country blocking rules to iptables firewall
#
# chkconfig: 2345 09 91
# description:	Adds and removes country blocking rules to iptables firewall
#
# config: /etc/sysconfig/iptables-cblock-config
#
# Origin from http://www.cyberciti.biz/faq/block-entier-country-using-iptables
# Modified by David Picard of PSInd, LLC - dpicard [a-t] psind [d-ot] com - to
# include ability to cache the address list, create chains by subnet thus
# optimizing rule execution, clean up the rules without wiping the existing
# tables, include port overrides to allow traffic deemed 'safe', and leverage
# iptables-restore to optimize rules loading.

# Source function library.

# The following values may be overridden in the config
### Block all traffic from AFGHANISTAN (af), CHINA (CN) and KOREA(kr).
### Use ISO code ###
ISO="af cn au jp id"
IPTCBRESTORE="/etc/sysconfig/iptables.cb"
IPTCBDEVICE=ens4
ALLOWPORTS=80,443
ALLOWSUBNET=10.128.0.0/255.255.0.0
MAXZONEAGE=7
#DLROOT="https://www.ipdeny.com/ipblocks/data/countries"
DLROOT="https://www.ipdeny.com/ipblocks/data/aggregated"
IPTABLES_VERBOSE="no"
IPTABLES_STATUS_NUMERIC="yes"
IPTABLES_STATUS_VERBOSE="yes"
IPTABLES_STATUS_LINENUMBERS="yes"

### Set PATH ###
IPT=/sbin/iptables
WGET=/usr/bin/wget
EGREP=/bin/egrep
IPTABLES_CB_CONFIG=/etc/sysconfig/iptables-cblock-config

### No editing below ###
IPTABLESCB=`basename $0`
CBLIST="countrydrop"
IPTCBLOG=/var/log/iptables_country_block.log
IPTCBLOAD=`mktemp`
IPTCBTMP=`mktemp`
ZONEROOT="/var/iptables"

# Load country blocking firewall configuration.
[ -f "$IPTABLES_CB_CONFIG" ] && . "$IPTABLES_CB_CONFIG"

cleanOldRules(){
    $IPT -L $CBLIST > /dev/null 2>&1
    if [ $? = 0 ] ; then
	$IPT -D INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
	$IPT -D FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
	$IPT -D OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST
    fi
    $IPT -F $CBLIST
    $IPT -X $CBLIST

    for i  in `$IPT -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$CBLIST'`
    do
	$IPT -F ${i}
	$IPT -X ${i}
    done
}

updateZoneFiles() {
    ZONEARCH=${ZONEROOT}/arch
    mkdir -p ${ZONEARCH}
    find ${ZONEROOT} -maxdepth 1 -mindepth 1 -ctime +${MAXZONEAGE} -exec mv {} ${ZONEARCH} \;

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c-aggregated.zone

	if [ -f $tDB ] ; then
	    printf "Zone file %s is new enough - no update required.\n" $tDB >>${IPTCBLOG}
	else
	    # get fresh zone file if it is newer than MAXZONEAGE days
	    $WGET -O $tDB $DLROOT/$c-aggregated.zone
	fi
    done
    oldzones=`find ${ZONEROOT} -mindepth 1 -maxdepth 1 -type f -exec basename {} \; | cut -f 1 -d '.'`
    # Archive old zones no longer blocked
    for z in $oldzones ; do
	archme=${c}
	for c  in $ISO ; do
	    if [ $c = $z ] ; then archme="X"; fi
	done
	if [ $archme = $z ] ; then
	    mv ${archme} ${ZONEARCH}
	else
	    printf "Working from previous zone file for %s\n" ${z} >>${IPTCBLOG}
	fi
    done
}

createIPTLoadFile() {
    printf "# Generated by %s on" $0 > ${IPTCBLOAD}
    printf "%s " `date` >> ${IPTCBLOAD}
    printf "\n*filter\n" >> ${IPTCBLOAD}
    # Create CBLIST chain
    printf ":$CBLIST - [0:0]\n" >> ${IPTCBLOAD}
    printf "%s INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST\n" "-I" > ${IPTCBTMP}
    printf "%s OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST\n" "-I"  >> ${IPTCBTMP}
    printf "%s FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST\n" "-I" >> ${IPTCBTMP}

    printf "%s $CBLIST -m state --state RELATED,ESTABLISHED -j ACCEPT\n" "-I">> ${IPTCBTMP}
    if [ "Z${ALLOWPORTS}" = "Z" ] ; then
	printf "Blocking all traffic from country - no ports allowed\n" >>${IPTCBLOG}
    else
	printf "%s $CBLIST -p tcp -m multiport --dports ${ALLOWPORTS} -j RETURN\n" "-I">> ${IPTCBTMP}
    fi

    if [ "Z${ALLOWSUBNET}" = "Z" ] ; then
	printf "Blocking all traffic from country - no subnets excluded\n" >>${IPTCBLOG}
    else
	printf "%s $CBLIST -s ${ALLOWSUBNET} -j RETURN\n" "-I">> ${IPTCBTMP}
    fi

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c-aggregated.zone

	# country specific log message
	SPAMDROPMSG="iptables: ${c}-Country-Drop: "

        # Create drop chain for identified packets
	CBLISTDROP=${c}-${CBLIST}-DROP
	printf ":${CBLISTDROP} - [0:0]\n" >> ${IPTCBLOAD}
	printf "%s ${CBLISTDROP} -j LOG --log-prefix \"$SPAMDROPMSG\"\n" "-A" >> ${IPTCBTMP}
	printf "%s ${CBLISTDROP} -j DROP\n" "-A" >> ${IPTCBTMP}

	# Load IP ranges into chains correlating to first octet
	BADIPS=$(egrep -v "^#|^$" $tDB)
	for ipblock in $BADIPS
	do
	    topip=`echo $ipblock | cut -f 1 -d '.'`
	    chainExists=`grep -c :${topip}-${CBLIST} ${IPTCBLOAD}`
	    if [ $chainExists = 0 ] ; then
		printf "Creating chain for octet %s\n" ${topip} >>${IPTCBLOG}
		printf ":$topip-$CBLIST - [0:0]\n" >> ${IPTCBLOAD}
		sip=${topip}.0.0.0/8
		printf "%s $CBLIST -s ${sip} -j $topip-$CBLIST\n" "-A" >> ${IPTCBTMP}
	    fi
	    printf "  Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} >>${IPTCBLOG}
	    printf "%s $topip-$CBLIST -s $ipblock -j ${CBLISTDROP}\n" "-A" >> ${IPTCBTMP}
	done
    done
    cat ${IPTCBTMP} >> ${IPTCBLOAD} && rm -f ${IPTCBTMP}
    printf "COMMIT\n# Completed on " >> ${IPTCBLOAD}
    printf "%s " `date` >> ${IPTCBLOAD}
    printf "\n" >> ${IPTCBLOAD}
}

directLoadTables() {
    # Create CBLIST chain
    $IPT -N $CBLIST
    # Block inbound packets on specified network device
    $IPT -I INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
    # The following rules are useful only in multi-NIC systems / gateways
    $IPT -I FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
    $IPT -I OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST

    if [ "Z${ALLOWPORTS}" = "Z" ] ; then
	printf "Blocking all traffic from country - no ports allowed\n" >>${IPTCBLOG}
    else
	$IPT -I $CBLIST -p tcp -m multiport --dports ${ALLOWPORTS} -j RETURN
    fi

    if [ "Z${ALLOWSUBNET}" = "Z" ] ; then
	printf "Blocking all traffic from country - no subnets allowed\n" >>${IPTCBLOG}
    else
	$IPT -I $CBLIST -s ${ALLOWSUBNET} -j RETURN
    fi

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c.-aggregatedzone

	# country specific log message
	SPAMDROPMSG="$c Country Drop"

        # Create drop chain for identified packets
	CBLISTDROP=${c}-${CBLIST}-DROP
	$IPT -N ${CBLISTDROP}
	$IPT -A ${CBLISTDROP} -j LOG --log-prefix "$SPAMDROPMSG"
	$IPT -A ${CBLISTDROP} -j DROP

	# Load IP ranges into chains correlating to first octet
	BADIPS=$(egrep -v "^#|^$" $tDB)
	for ipblock in $BADIPS
	do
	    topip=`echo $ipblock | cut -f 1 -d '.'`
	    $IPT -L $topip-$CBLIST > /dev/null 2>&1
	    if [ $? = 1 ] ; then
		printf "Creating chain for octet %s\n" ${topip} >>${IPTCBLOG}
		$IPT -N $topip-$CBLIST
		sip=${topip}.0.0.0/8
		$IPT -A $CBLIST -s ${sip} -j $topip-$CBLIST
	    fi
	    printf "  Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} >>${IPTCBLOG}
	    $IPT -A $topip-$CBLIST -s $ipblock -j ${CBLISTDROP}
	done
    done
}

loadTables() {
    createIPTLoadFile
    ${IPT}-restore -n ${IPTCBLOAD}
    if [ ! "z${IPTCBRESTORE}" = "z" ] ; then
	RESTORE_DIR=`dirname ${IPTCBRESTORE}`
	[ -d ${RESTORE_DIR} ] && mv -f ${IPTCBLOAD} ${IPTCBRESTORE}
    fi
    #directLoadTables
    printf "Country block instituted for: %s\n" "$ISO" >>${IPTCBLOG}
}

status() {
    NUM=
    [ "x$IPTABLES_STATUS_NUMERIC" = "xyes" ] && NUM="-n"
    VERBOSE=
    [ "x$IPTABLES_STATUS_VERBOSE" = "xyes" ] && VERBOSE="--verbose"
    COUNT=
    [ "x$IPTABLES_STATUS_LINENUMBERS" = "xyes" ] && COUNT="--line-numbers"

    for table in "filter" ; do
	echo $"Table: $table"
	$IPT -t $table --list $CBLIST $NUM $VERBOSE $COUNT
    done

    return 0
}

stop() {
    echo -n $"${IPTABLESCB}: Removing cb firewall rules: "

    # clean old rules
    cleanOldRules

    if [ $? -eq 0 ]; then
	success; echo
    else
	failure; echo; return 1
    fi
}

start() {
    echo -n $"${IPTABLESCB}: Applying cb firewall rules: "

   # create a dir
    [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT

    # update zone files as needed
    updateZoneFiles

    # create a new iptables list
    loadTables

    if [ $? -eq 0 ]; then
	success; echo
    else
	failure; echo; return 1
    fi
}

restart() {
    stop
    start
}

#[ ! "x$IPTABLES_VERBOSE" = "xyes" ] && exec 1>/dev/null
case "$1" in
    start)
	[ -f "$VAR_SUBSYS_IPTABLES" ] && exit 0
	start
	RETVAL=$?
	;;
    stop)
	[ "x$IPTABLES_SAVE_ON_STOP" = "xyes" ] && save
	stop
	RETVAL=$?
	;;
    restart|force-reload)
	restart
	RETVAL=$?
	;;
    status)
	status
	RETVAL=$?
	;;
    *)
	echo $"Usage: ${IPTABLESCB} {start|stop|restart|condrestart|status}"
	RETVAL=2
	;;
esac

exit $RETVAL


Buy Me a Coffee