Search This Blog

shell script: update ufw rules for the hosts with dynamic ip addresses

  • The shell script to check the ip address for the host and update the ufw rules:
    #!/bin/bash
    
    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 regex="${port}\/${proto}.*ALLOW.*IN.*${ip}"
      local rule=$(ufw status numbered | grep $regex)
      if [ -z "$rule" ]; then
          ufw allow proto ${proto} from ${ip} to any port ${port}
      else
          echo "rule already exists. nothing to do."
      fi
    }
    
    delete_rule() {
      local proto=$1
      local port=$2
      local ip=$3
      local regex="${port}\/${proto}.*ALLOW.*IN.*${ip}"
      local rule=$(ufw status numbered | grep $regex)
      if [ -n "$rule" ]; then
          ufw delete allow proto ${proto} from ${ip} to any port ${port}
      else
          echo "rule does not exist. nothing to do."
      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)
    
        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
            fi
            echo "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
            fi
        fi
        add_rule $proto $port $ip
        if [ -f ${IPS_ALLOW} ]; then
          sed -i.bak /^${host}*/d ${IPS_ALLOW}
        fi
        echo "${host}:${ip}" >> ${IPS_ALLOW}
    done
    
  • The sample hosts file: /etc/ufw-dynamic-hosts.allow:
    tcp:22:yourpc.no-ip.org
    
  • The crontab which execute the script every 5 minutes to update the rules:
    # m h  dom mon dow   command
    */5 * * * * /usr/local/sbin/ufw-dynamic-host-update  2>&1 > /dev/null
    

6 comments:

  1. Yep, brilliant. Thanks for sharing, keep up the good work.

    ReplyDelete
  2. The regex used to select rules has an edge case for similar-ending ports such as 8080 and 80 where when looking for port 80 it will fnd the rule for 8080 and fail. The version below uses a more resilient regex and adds some debugging statements

    #!/bin/bash
    # From: http://notepad2.blogspot.de/2012/06/shell-script-update-ufw-rules-for-hosts.html

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

    #MROY: output timestamp
    echo "Runtime:" `date`
    echo "Running with: $SHELL"

    add_rule() {
    local proto=$1
    local port=$2
    local ip=$3
    local regex="\][ \t]*${port}\/${proto}[ \t]*ALLOW[ \t]*IN[ \t]*${ip}"
    local rule=$(/usr/sbin/ufw status numbered | grep "$regex")
    #echo "DEBUG regex: ${regex}"
    #echo "DEBUG rule: ${rule}"
    if [ -z "$rule" ]; then
    /usr/sbin/ufw allow proto ${proto} from ${ip} to any port ${port}
    #MROY:
    echo "...rule added."
    else
    echo "rule already exists. nothing to do. (proto ${proto} from ${ip} to any port ${port})"
    fi
    }

    delete_rule() {
    local proto=$1
    local port=$2
    local ip=$3
    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}
    #MROY:
    echo "rule deleted. (proto ${proto} from ${ip} to any port ${port})"
    else
    echo "rule does not exist. nothing to do."
    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)

    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
    fi
    echo "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
    fi
    fi
    add_rule $proto $port $ip
    if [ -f ${IPS_ALLOW} ]; then
    sed -i.bak /^${host}*/d ${IPS_ALLOW}
    fi
    echo "${host}:${ip}" >> ${IPS_ALLOW}
    done

    #MROY:
    echo "Reached end of script at" `date`

    ReplyDelete
  3. Oh you awesome person, this is just what I was looking for!

    ReplyDelete
  4. Anyone know the right syntax if I want to allow all traffic from my Dynamic ip?

    ReplyDelete
  5. Seams that Cron is running the script, but UFW did not get new rules. If I run the script manually, it works.

    ReplyDelete