IPTables is a very powerful tool for creating a firewall on your Linux system. However, all the rules are based on IP addresses. For example, you can open a port to a specific source IP address.
What if you have a client that needs to connect to a service that does not have a static IP address? The client would need to monitor changes to their IP address, send you the new IP, and you would then have to manually update your iptables rules to allow them access. There has to be a better way.
This was my thought when I had a customer that needed SSH access to his server from his home. He has a cable internet connection that changes his pubic IP address at least once a month (sometimes once a week). I could just open SSH to the world and allow him to connect, but that is not very secure. The solution we worked out is to use Dynamic DNS to tie his IP address to a hostname, then use IP Sets and a simple Bash script to automatically update iptables.
NOTE: Configuring the client of Dyamic DNS is outside the scope of this tutorial. There are several methods to accomplish this. Let us know in the comments if you need help.
Configuring IP Sets with iptables
IP Sets is a framework that allows you to create "sets" of IP addresses, MAC address, networks, port numbers and more. These sets can then be used inside of iptables rules. This may seem complicated, however, it is very simple. With just a few commands you can configure an IP Set. Then we will create a simple bash script to update the set.
For this example, let's assume the client has the following dynamic DNS name setup:
client1.example.com
Creating Your IP Set
The first step is to create your IP Set to hold the IP address of your client.
[mcherisi@putor ~]$ sudo ipset create ssh-allowed hash:ip
Here we are telling the ipset utility to create a set called "ssh-allowed" with the type of "hash:ip". This will be used to store the IP addresses of systems allowed to SSH into our server.
We can now view this IP Set and see that is has no members. This is expected since we have not yet added any IP addresses. In the next steps we will configure iptables to use this IP Set. Then create a Bash script to add our clients IP address based of their Dynamic DNS name.
[mcherisi@putor ~]$ sudo ipset list ssh-allowed
Name: ssh-allowed
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 200
References: 0
Number of entries: 0
Members:
You can manually add IP addresses to the set for testing.
[mcherisi@putor ~]$ sudo ipset add ssh-allowed 192.168.100.197
Now if we list the "ssh-allowed" IP Set, we will see we have one member.
[mcherisi@putor ~]$ sudo ipset list ssh-allowed
Name: ssh-allowed
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 248
References: 1
Number of entries: 1
Members:
192.168.100.197
Using the IP Set to Create an iptables Rule
Now that we have our IP Set created, let's create a rule in iptables that tells it to allow SSH traffic from addresses inside this IP Set.
sudo iptables -I INPUT -p tcp --dport 22 -m set --match-set ssh-allowed src -j ACCEPT
NOTE: You may want to add a comment to these rules for documentation.
Now iptables is configured to check the "ssh-allowed" IP set for source IP addresses for incoming SSH connections.
[mcherisi@putor ~]$ sudo iptables -L -vn
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 match-set ssh-allowed src
...OUTPUT TRUNCATED...
Any IP address that is a member of the "ssh-allowed" IP Set is now permitted to connect to SSH on destination port 22.
Creating a Bash Script to Automatically Update IP Tables by Hostname
Our client has Dynamic DNS configured to update the hostname client1.example.com
with their IP address automatically. All that remains is to set up a script to update our "ssh-allow" IP set with the IP address of that hostname.
NOTE: You must have the dig utility installed.
The script below will use the dig utility to find the IP address of client1.example.com
and set the ip
variable with it. It will then flush the IP Set to remove any old IP addresses, then insert the new IP address allowing it access to the server via SSH.
#!/bin/bash
# Find IP address and store it in $ip
ip=`dig +short client1.example.com`
# Flush old IP addresses from ssh-allowed IP Set
ipset flush ssh-allowed
# Add new IP address to ssh-allowed IP Set
ipset add ssh-allowed $ip
We can place this script in /etc/cron.daily to run every day, or create a basic cron job to run it as often as we like. As the client automatically updates Dynamic DNS, this script will ensure they still have access to the server.
Conclusion
Using IP sets allows you to create an easy to manage list of IP addresses that iptables can use in it's rules. As we have shown when paired with a simple Bash script it can also help to create rules from a hostname or domain name.
There is a full tutorial in the works for ipset. Sign up for our newsletter to keep up with all our new releases.
Resources and Links
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
6 Comments
Join Our Newsletter
Categories
- Bash Scripting (17)
- Basic Commands (50)
- Featured (7)
- Just for Fun (5)
- Linux Quick Tips (98)
- Linux Tutorials (65)
- Miscellaneous (15)
- Network Tools (6)
- Reviews (2)
- Security (32)
The problem is that ip=`dig +short client1.example.com`
may bring more than one IP address in several rows and it shields an error, I'm trying to use to allow LAN users PCs access to Windows10 updates, I must add rules in my IPTABLES file to
allow the list of domains associated to Windows OS uptdate that is about 13 different domains, testing with the first one i.e. windowsupdate.microsoft.com dig +short yields all this :
windowsupdate.redir.update.microsoft.com.nsatc.net. redir.update.microsoft.com.nsatc.net. 52.185.71.28
This is weird, dig is not showing a pure IP address, this I suppose don't work in a IPSET list.
Any help will be much appreciated
Honestly I am not following your issue. You are using a Linux PC to push updates to Windows 10 clients? That is odd, and I am sure there is a better choice for this job (SCCM, WSUS, etc..).
However, in your example above dig is acting exactly how it is expected. There are CNAME records, so when you query windowsupdate.microsoft.com it is showing you the two CNAME records and the eventual A record.
I don't think you can tell dig to ignore the CNAME records. BUT, you can manipulate the output.
[savona@putor ~]$ dig +short windowsupdate.microsoft.com | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
52.137.90.34
In the above example I just used grep to pull only the IP address.
Read more here: https://www.putorius.net/grep-an-ip-address-from-a-file.html
For hostnames that possibly is a CNAME and possibly resolved to multiple IPs:
jumphosts=$(dig +noall +answer jumphosts.example.com | awk '$4 == "A" {print $5}')
[ -z "$jumphosts" ] && exit 1 # exit early so you don't flush but don't add any jump hosts
ipset flush ssh-allowed
for ip in $jumphosts
do ipset add ssh-allowed $ip
done
Many thanks for this guide. Everything works well, but how could I add a second IP to the set of allowed hostnames? My try...
#!/bin/bash
ip=`dig +short client1.example.com`
ip=`dig +short client2.example.com`
ipset flush ssh-allowed
ipset add ssh-allowed $ip
... doesn't work as only the IP of the second dig line is added to the ipset.
Many thanks for your support.
This is because you are using the same variable for both dig commands. When the second one runs, it overwrites the first. You can do something like this:
#!/bin/bash
# Find IP address and store it in variable
ip1=`dig +short client1.example.com`
ip2=`dig +short client2.example.com`
ip3=`dig +short client3.example.com`
# Flush old IP addresses from ssh-allowed IP Set
ipset flush ssh-allowed
# Add new IP addresses to ssh-allowed IP Set
ipset add ssh-allowed $ip{1..3}
I have created a python script to do the same. With a scheduler to be able to launch this without crontab but as a service.
import pydig
import subprocess
from apscheduler.schedulers.blocking import BlockingScheduler
sched = BlockingScheduler()
@sched.scheduled_job('interval', seconds=10)
def update_ipset_example_com():
# Get DNS record for fqdn
res = pydig.query('example.com', 'A')
# Get actual ipset Members entries
actual_ipset = [i for i in subprocess.check_output('ipset list example_com', shell=True).decode("utf-8").split('Members:')[1].split('\n') if i]
# Update ipset Members entries
# Add new ip address
for i in res:
if i not in actual_ipset:
cmd = 'ipset add example_com ' + i
subprocess.run(cmd, shell=True)
# Remove outdated Members entries
for i in actual_ipset:
if i not in res:
cmd = 'ipset del example_com ' + i
subprocess.run(cmd, shell=True)