3

I have disabled PasswordAuthentication for SSH and am using a public-private key for logging in. However, a number of guides (in particular, this one) I have seen suggest only allowing SSH access from my particular IP address.

/etc/ssh/sshd_config:
    AllowUsers deploy@(your-ip) deploy@(another-ip-if-any)

ufw allow from {your-ip} to any port 22
  • Is there any point in restrictring by IP address within sshd_config AND adding a firewall rule?

  • Assuming I have a dynamic IP address, then such rules would appear useless, as I have to use the hosting providers console terminal, log into to root to allow access from my home IP. In which case: what is the suggested route?

1: Disallow all logins by SSH unless I first use the hosting providers terminal, log in a root to allow access from my home IP to SSH?

2: Keep the SSH port open and trust in the public-private key for logging in.

3: Any other suggestions?

1

Well, I've done plenty of digging and found nothing. So, I've set up my home computer with a dynamic dns service and have created the following bash script, hopefully it will be useful to somebody else.

Instructions:

  • This script is not specific to SSH and is a way of allowing a dynamnic host permanent access to a target port using ufw - irrelevant of what service is listening on that port. So, using port 22 would (by default) allow permanent access from the dynamic host to SSH. Likewise, you could use this script to ensure permanent access from your dynamic host to something like FTP (port 21).

  • All that is required is modifying the RemoteHostName and PortNumber variables at the top of the file. Obviously the RemoteHostName is the hostname provided by the dynamic DNS provider and the PortNumber is the port that we are allowing access to.

  • If the IP that is resolved from the RemoteHostName already exists within the ufw with access to the target port, then the script exits, as there is nothing to change.

  • If it doesn't exist, then all entries with the target port number within the ufw are deleted, and a new entry is then added using the updated IP address and target port. In short, do not try to use ufw for other rules involving the port in the script, as the script will walk all over them.

  • If this script is called by a cron job or something, then it should in theory always keep the IP address updated within ufw.

  • The script outputs the following:

    1) Standard output: Could not resolve the IP address of the RemoveHostName XYZ
    2) Standard output: No change necessary as the IP address XYZ already has access to port XYZ.
    3) Standard output: Deleting old ufw rule: XYZ
    4) Standard output: Adding new ufw rule: XYZ
    5) Error output: Could not resolve the IP address of the RemoveHostName XYZ
    
  • Please note though, I am not very familiar with bash scripting and just as unfamiliar with regular expressions. So if you use this script, do yourself a favour (and me!) and have a look over it. If you notice any bugs, please leave a comment and or a new answer.

Script:

#!/bin/bash


##############################################################
#Variables
##############################################################    
RemoteHostName=someremotehost.dyndnsorsomething.com
PortNumber="22"


##############################################################
#Check if the dynamic IP address exists within ufw.
##############################################################
DynamicIPAddress=$(host $RemoteHostName | awk '/has address/ { print $4 }')
if [[ $DynamicIPAddress = "" ]]
then
    echo $(date -u -Iseconds): Could not resolve the IP address of the RemoveHostName $RemoteHostName >&2
    exit
fi

UFWRules=$(ufw status numbered)
REGEX=" $PortNumber (.*)ALLOW IN(.*)$DynamicIPAddress$"
while read -r line; do
    if [[ $line =~ $REGEX ]]
    then
        echo $(date -u -Iseconds): No change necessary as the IP address $DynamicIPAddress already has access to port $PortNumber.
        exit
    fi
done <<< "$UFWRules"


##############################################################
#Remove all entries with the given port number.
##############################################################
while true
do

    UFWRules=$(ufw status numbered)
    REGEX="(.*) $PortNumber (.*)ALLOW IN(.*)"
    Matched="No"
    while read -r line; do
        if [[ $line =~ $REGEX ]]
        then
            echo $(date -u -Iseconds): Deleting old ufw rule: $line
            RuleNumber=$(echo $line | (cut -d "[" -f2 | cut -d "]" -f1))
            ufw --force delete $RuleNumber
            Matched="Yes"
            break
        fi
    done <<< "$UFWRules"

    if [ $Matched = "No" ]
    then
        break
    fi

done


##############################################################
#Add in access from the dynamic IP address.
##############################################################
echo $(date -u -Iseconds): Adding new ufw rule: "ufw allow from $DynamicIPAddress to any port $PortNumber".
ufw allow from $DynamicIPAddress to any port $PortNumber
0

If you are running a web server on the same host, which means PHP is also probably installed, then you can install a tool that I wrote, use, and actively maintain:

https://github.com/cubiclesoft/web-knocker-firewall-service/

Clients that need SSH access run the client portion as a system service on their OS of choice. The client keeps the server's SSH port and any other protected ports (e.g. POP3/IMAP) open with infrequent, encrypted packets. It is a pretty nice fire-and-forget solution. I've personally had zero issues with the software, but you might want to test it on a DigitalOcean droplet or similar service before deploying it to your production environment. Ports are kept open for up to 30 minutes (the default) unless renewal packets are sent (the client attempts a renewal every 10 minutes by default), which allows for IP addresses to change with minimal downtime. When ports are opened for an IP address, e-mail notifications are optionally sent to whoever is configured to receive such notifications.

The goal is to dramatically limit the attack surface by closing sensitive port(s) to everyone who does not have the encryption keys for clients on dynamic IPs where setting up a complex VPN is not an option. Even if an attacker winds up on the same unlocked IP address (e.g. a laptop on a coffee shop's WiFi), they still have to get through SSH, but at least Russia, China, and North Korea aren't able to even see the port.

By the way, I don't use ufw. It pollutes iptables with a bunch of unnecessary chains, makes iptables rules way more complicated than they need to be, and more rules equates to slower packet processing through iptables. I wrote a post not too long ago on writing elegant iptables rules:

http://cubicspot.blogspot.com/2016/06/elegant-iptables-rules-for-your-linux.html

It's actually a lot easier to use iptables directly and is less awkward than ufw when you can copy-pasta good, clean rulesets. Plus, you never know when you'll be stuck on a CentOS box.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.