Information Technology Grimoire

Version .0.0.1

IT Notes from various projects because I forget, and hopefully they help you too.

ufw Bash Script for Dynamic Rule Updates

If you are a road warrior and need access to your servers how do you update your firewall rules? A simple bash script like this one will update your UFW firewall rules so you can access your server wherever you are.

Let me start by saying we didn’t write this, but one of our techs found it from This Tech Blog and we use it so frequently, we want to document it for others in case it disappears. We did add a few other minor things from the original script (putting date time stamp in log, adding comments), but the idea belongs to the creator linked above.

Option 1: Update UFW by Hand

    sudo ufw allow from 1.2.3.4 to any port 22 comment 'some comment here'

We are going to assume you have UFW running and enabled for the rest of this article!

Option 2: Automate UFW Updates With a Script!!

  • Register a Dynamic Hostname
  • Write the Bash Script
  • Create Dynamic Host Object for UFW
  • Add Cron Job
  • Verify Owner/Permissions
  • Install Dynamic Host Updater on Client
  • Verify Your Script is Working on Server

The rest of this article will detail the steps above to automate your UFW bash script to update firewall rules based on your dynamic host.

Register a Dynamic Hostname

There are several free services for dynamic DNS. The way we do it is to register at least two dynamic hosts. One dynamic host is tied to the router, so when we are in the office, that IP is registered. If that IP happens to change, it will be updated for all of the computers at that location. The second dynamic host is used by installing the client on the laptop. Now when that laptop uses any hotspot, it is also allowed into the network. We are using Dyn DNS but you can use any service so long as it’s regularly updated and easy to use.

We use Dyn DNS because the routers are typically setup to use it as a default Dynamic DNS.

You’ll need to install the client on your travel machine so it can register whenever the laptop travels to a new network. DYN DNS CLIENT

Your server will also need “dig” installed. If it’s not installed “which dig”, then you can install it easily:

    sudo apt-get install dnsutils

Write the Bash Shell Script

    cat /usr/local/sbin/ufw-dynhostupdate.sh
    #!/bin/bash

    start=$(date +"%Y-%m-%d %T")

    HOSTS_ALLOW=/etc/ufw-dynamic-hosts.allow
    IPS_ALLOW=/var/tmp/ufw-dynamic-ips.allow

    add_rule() {
      local proto=$1
      local port=$2
      local ip=$3
      local comment=$4

      local regex="${port}\/${proto}.*ALLOW.*IN.*${ip}"
      local rule=$(/usr/sbin/ufw status numbered | grep $regex)
      if [ -z "$rule" ]; then
          /usr/sbin/ufw allow proto ${proto} from ${ip} to any port ${port} comment "${comment}"
          echo "${start} ADDED: ${proto} from ${ip} to any ${port} comment '${comment}'"
      else
            echo "${start} EXISTS: ${proto} ${ip} to ${port} (${comment})"
      fi
    }

    delete_rule() {
      local proto=$1
      local port=$2
      local ip=$3
      local comment=$4

      local regex="${port}\/${proto}.*ALLOW.*IN.*${ip}"
      local rule=$(/usr/sbin/ufw status numbered | grep $regex)
      if [ -n "$rule" ]; then
            /usr/sbin/ufw delete allow proto ${proto} from ${ip} to any port ${port}
            echo "${start} DELETED: ${proto} ${ip} to ${port} (${comment})"
      else
            echo "${start} NO DELETE: rule does not exist"
      fi
    }

    sed '/^[[:space:]]*$/d' ${HOSTS_ALLOW} | sed '/^[[:space:]]*#/d' | while read line
    do
        proto=$(echo ${line} | cut -d: -f1)
        port=$(echo ${line} | cut -d: -f2)
        host=$(echo ${line} | cut -d: -f3)
        comment=$(echo ${line} | cut -d: -f4)

        if [ -f ${IPS_ALLOW} ]; then
          old_ip=$(cat ${IPS_ALLOW} | grep ${host} | cut -d: -f2)
        fi

        ip=$(dig +short $host | tail -n 1)

        if [ -z ${ip} ]; then
            if [ -n "${old_ip}" ]; then
                delete_rule $proto $port $old_ip
               # echo "${start} ${proto} ${port} ${old_ip} removed"
            fi
            echo "${start} FAIL: Failed to resolve the ip address of ${host}." 1>&2
            exit 1
        fi

        if [ -n "${old_ip}" ]; then
            if [ ${ip} != ${old_ip} ]; then
                delete_rule $proto $port $old_ip $comment
            fi
        fi
        add_rule $proto $port $ip $comment
        if [ -f ${IPS_ALLOW} ]; then
          sed -i.bak /^${host}*/d ${IPS_ALLOW}
        fi
        echo "${host}:${ip}" >> ${IPS_ALLOW}
    done

Set Execute Permissions On The Script

We need this Bash script to execute at regular intervals. In order for to execute, we need our script to have execute permissions (and later we’ll add a cron job too).

    $ chmod +x /usr/local/sbin/ufw-dyhostupdate.sh

As for the group and owner, only root can normally update UFW. So, unless you modify your wheel group or otherwise give your user permissions to specifically edit UFW, you’ll need to use root as the owner of the script and all files. For security practices, this isn’t the greatest idea and will fail some audits. More importantly, it would be better to update your user to have UFW permissions and then run all of the job, file permissions etc as that owner.

An even better practice is to give a special user priveledges to ONLY touch UFW or automate certain tasks and then run said tasks as that owner. To keep it simple for documentation purposes, we’ll pretend that root is fine for our audits.

Create Dynamic Host Object for UFW

This object is simply a file storing the most recently found IP of your dynamic host. We’ll create and preload it with the data it needs, but this isn’t necessary:

    $ cat /var/tmp/ufw-dynamic-ips.allow
    yourdynamichost.selfip.com:1.2.3.4

In addition, make sure the object is readable and writable so our UFW script can make changes to it:

    $ ls /var/tmp/ufw-dynamic-ips.allow
    -rw-r--r-- 1 root root 42 Oct 27 12:35 /var/tmp/ufw-dynamic-ips.allow

Create the Dynamic Host Database

This is a simple text file stating what ports to open for each dynamic host. You can have multiple lines in it, 1 line per host. In the following example we are saying “TCP 22 should be open from yourdymanichost.selfip.com”:

    $ cat /etc/ufw-dynamic-hosts.allow
    tcp:22:yourdynamichost.selfip.com:your_comment_here

Test the Script Manually

You should be able to run the script manually at this point and then verify your UFW logs and rules to see them update.

You can view your UFW rules with the following command before and after testing your script:

    $ ufw status numbered

Before:

ufw status numbered

After:

ufw status numbered

Add Cron Job

Now that you have a scripts, temporary objects, and a dynamic host database all working… you need to setup the script to run on autopilot. Use the crontab -e command to edit the cronjob. Remember it must as a job on the user that owns the script and that user must have permission to edit UFW rules!

You can verify the job is setup in cron by looking at the cron jobs. We are hiding the messages and keeping the errors to syslog using the reroute to dev null:

    $ crontab -l
    */5 * * * * /usr/local/sbin/ufw-dynhostupdate.sh >/dev/null 2>&1
    # or if you want to log the changes that happen/don't happen:
    */5 * * * * /usr/local/sbin/ufw-dynhostupdate.sh >>/var/log/ufwdynhost.log 2>&1

Verify Your Script is Working on Server

If it’s all working you should get an updated UFW status, and see an entry in syslog about the UFW changes:

    $ ufw status numbered

To verify recent CRON jobs, view your syslog:

    $ cat /var/log/syslog | grep CRON
    Oct 28 09:10:01 somedomain CRON[11113]: (root) CMD (/usr/local/sbin/ufw-dynhostupdate.sh >/dev/null 2>&1)
Last updated on 12 Dec 2018
Published on 12 Dec 2018