Wednesday, June 14, 2023

Use Dynamic IP Addresses in firewalld


Using ipset

If you want to use dynamic IP-Adresses (dyndns) in firewalld rules, you can use ipset(s).

With the following script we create one ipset per configured ddns hostname. The script automatically create/update all ddns ipset entries with the actually resolved IP-address of the ddns hostnames. 
You can use the ipset names (same as the hostname) in your firewalld rules then. See comment in script.

Example cron script. Enable in /etc/cron.d/. Tested in Rocky 8.
/usr/local/bin/firewall_ddns_update.sh:

#!/usr/bin/env bash

# Bash script to query for current IP address of given Dynamic-DNS
# hosts and create or update firewalld ipset(s).
# You are then able to use the ipset in firewalld rules.
# Obtain the current ipset(s) with:
# firewall-cmd --permanent --get-ipsets
#
# This script is intended to be run from a cron job with root privileges.
# The script will only update the ipset if the IP address has changed.
# based on
# https://www.baeldung.com/linux/allowing-traffic-from-dynamic-ip-address

# Why ipset?
# firewalld lacks support for comments in rules, so it is hard to keep
# track of what IP address belongs to what hostname.
# ipset allows you to create a set of IP addresses and use that set in
# firewalld rules.
# ipset also allows you to add a description to each IP address, so you
# can keep track of what IP address belongs to what hostname.
# ipset IP-adresses are independent of firewall rules, so you can update
# the ipset without reconfiguring rules.

# Using an ipset in a firewall rule:
# firewall-cmd --permanent --zone=public --add-source=ipset:my_ip_set_name
# firewall-cmd --reload

#######################################
# Config section
#######################################
# Set the DDNS hostnames you want to update its IP-adresses for
DDNS_HOSTNAMES="myexampledynhost1.ddnss.org myexampledynhost2.ddns.de"

# work
#######################################
for DDNS_HOSTNAME in $DDNS_HOSTNAMES; do
# Get the DDNS hostname IP address
DDNS_IP=$(dig +short "${DDNS_HOSTNAME}")

# Get the ipset for this ddns host
firewall-cmd --permanent --ipset="$DDNS_HOSTNAME" --get-entries >/dev/null 2>&1
if [ $? != 0 ]; then
# create ipset
firewall-cmd --permanent \
--new-ipset="$DDNS_HOSTNAME" \
--type=hash:ip \
--set-description "$DDNS_HOSTNAME dyndns" >/dev/null 2>&1
fi

# Get the OLD IP from the ipset
OLD_IP=$(firewall-cmd --permanent --ipset="${DDNS_HOSTNAME}" --get-entries)

# Check if the DDNS hostname IP address is valid
if [[ "${DDNS_IP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
# Check if the old IP is valid / Exists
if [[ "${OLD_IP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
# Check if the DDNS service IP address is different from the old IP address
if [[ "${DDNS_IP}" == "${OLD_IP}" ]]; then
# NOT CHANGED
echo "IP address ${DDNS_IP} hasn't changed for ${DDNS_HOSTNAME}."
continue
else
# UPDATE
echo "Delete entry ${OLD_IP} from pool $DDNS_HOSTNAME"
firewall-cmd --permanent --ipset="${DDNS_HOSTNAME}" --remove-entry="${OLD_IP}"
echo "Add entry ${DDNS_IP} to ipset $DDNS_HOSTNAME"
firewall-cmd --permanent --ipset="${DDNS_HOSTNAME}" --add-entry="${DDNS_IP}"
fi
else
# NEW HOST
echo "Add IP ${DDNS_IP} to new ipset ${DDNS_HOSTNAME}"
firewall-cmd --permanent --ipset="${DDNS_HOSTNAME}" --add-entry="${DDNS_IP}"
echo "*******************************************************************************"
echo "**** Add ipset ${DDNS_HOSTNAME} to your firewall rules as needed. Example: "
echo "**** firewall-cmd --permanent --zone=public --add-source=ipset:${DDNS_HOSTNAME}"
echo "*******************************************************************************"
fi
echo "Activating.."
firewall-cmd --reload
else
echo "DDNS IP address is not valid for ${DDNS_HOSTNAME}"
firewall-cmd --permanent --delete-ipset="${DDNS_HOSTNAME}" >/dev/null 2>&1
firewall-cmd --reload >/dev/null 2>&1
fi
done

echo "Available ipsets:"
firewall-cmd --permanent --get-ipsets

# end of script.



Add to cron

Create /etc/cron.d/firewall_update_ddns:
# Update DDNS host IPs in firewall ipset(s)

*/5 * * * * root /usr/local/bin/firewall_ddns_update.sh > /var/log/firewall_ddns_update.log