Restricting incoming connections

Geolocation checks for SSH

Even though there is no such thing as perfect security, it certainly doesn't hurt to tighten some screws every now and then. So in addition to my already decently hardened sshd configuration, I've decided to block ssh connections from outside of Austria. My implementation is based on the approach taken in the blog article "Restricting access to ssh using fail2ban and geoip" by reinhard.codes.

Access control files

Access control for incoming requests can be implemented via tcpd, which uses the files /etc/hosts.{allow,deny}. By default, we deny access to ssh for everybody, in/etc/hosts.deny:

sshd: ALL

In the file /etc/hosts.allow, we specify a script that takes the client's IP address (%a) and determines whether or not to allow the client's connection via its exit code:

sshd: ALL: aclexec /usr/local/bin/ssh-ip-check.sh %a

Client IP address checking

The script /usr/local/bin/ssh-ip-check.sh uses geoiplookup and python to check if the client is connecting from an allowed IP address range or country:

#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2026 Max Moser

allowed_countries=("AT")
allowed_ips=("192.168.1.0/24" "127.0.0.0/8")

if  "$#" -ne 1 ; then
    echo "usage: $0 <ip>"
    exit 2
fi

ip="$1"

# use python for subnet checking
function check_ip() {
    python - "$1" "$2" << EOF
import sys
import ipaddress

ip = ipaddress.ip_address(sys.argv[1])
net = ipaddress.ip_network(sys.argv[2], strict=False)
sys.exit(0 if ip in net else 1)
EOF
    return "$?"
}

# first, check the subnet of the IP address
for aip in "${allowed_ips[@]}"; do
    if check_ip "$ip" "$aip"; then
        logger "allowed IP address: $ip"
        exit 0
    fi
done

# afterwards, check the country of the IP address
for ac in "${allowed_countries[@]}"; do
    # the command's output typically looks like:
    # GeoIP Country Edition: AT, Austria
    c="$(geoiplookup "$ip" | sed -E 's/^.*\s([A-Z]+),.*$/\1/')"
    if  "$c" = "$ac" ; then
        logger "allowed country: $c"
        exit 0
    fi
done

# if both checks failed, deny access
logger "denied IP address: $ip"
exit 1

The logger command writes directly to the logging facilities instead of std{out,err}, which may be a bit unexpected when testing the script (e.g. $ ssh-ip-check.sh 192.168.1.166). The logs can be viewed e.g. via journalctl, and will look like:

Apr 26 17:41:49 max-moser.dev root[97845]: allowed IP address: 192.168.1.166