No one ever do iptables because it's supposed to be impossible to manage and complicated when it's not. Most of admins and devs often tell me "Yeah I know but, it's difficult." and "I doubt our partners will be trained enough to do that".

Well, I've got a good news: **If you are running GNU/Linux then it's not that difficult. **

Isolation, especially the host firewall is maybe the most underrated defense in the modern age of computing, but is one mandatory step for an efficient security : defense in depth.

In this post i'll give the tips I use to build my firewalls rules, efficiently and in minutes.
A Firewall configured in 10 lines ? Here we go !

iptables

The GNU/Linux statefull firewall engine is iptables.

As a sysadmin, my firewall rules are a guarantee that everybody can access just what they need and will not sabotage my work by some unwanted/malicious changes. However using the local firewall to allow only certain flows (templating) will ease overall administration. Defining a standard means less work at the end of the day, it's easier to maintain and debug and leaves less space for errors.

You can see the firewall template as the UML schema of the sysadmin, the security it brings is just a bonus.

Using local firewall rules will help you to protect from other infected computers and attackers regardless whether they are on the internet or already in your network.

I'll skip the syntax here because it's pretty straight forward, and thus not really necessary.
If you feel like you need to read the basics about the syntax you'll find a section at the bottom of the article -> jump bottom

You also might want to have a look to man iptables and man iptables-extensions.

The State Module

iptables support a stateful TCP filtering engine, this enable very generic rules.

The following keywords are used :

  • State module
    • NEW : The packet is the first of the connection
    • ESTABLISHED : Any packet after the first.
    • RELATED : A new connection but related to an existing connection
      #Exemple FTP data transfert, ICMP errors, ...

My Workstation iptables ruleset

  1. Refuse external solicitations
  2. Connect to internet
  3. Allows answers
sudo iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Loopback ACCEPT 
sudo iptables -A INPUT -i lo -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# Default Policy
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP

My iptables Server Template

  1. Don't Connect to internet
  2. Receive external solicitations
  3. Allows answers
sudo iptables -A INPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Update management 
# (using the owner module to allow root to get things from internet)
    ## dns
sudo iptables -A OUTPUT  -m owner --uid-owner 0 -p udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
    ## http(s)
sudo iptables -A OUTPUT -m owner --uid-owner 0 -p tcp --dport 80 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -m owner --uid-owner 0 -p tcp --dport 443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT

# Loopback ACCEPT 
sudo iptables -A OUTPUT -o lo -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# Default Policy
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP

This version is a generic version, which is not the one i use on Production. For more details see a-better-server-version.

Please note that this config only have 8 lines, so you can add your specific needs (DB/syslog/whatever) within the 10 lines constraint.
A lot of middlewares ? iptables can handle more than 10 lanes ;).
The owner module can be of help in certain cases. (K.I.S.S.)

Wait what !

Is that really all ???
-> Yeah, so complicated eh ?!
-> For most usages this policy is enough.

So we basically open nearly everything, how can it be securing anything ?
Well let's take the server example.

Assume the web server is vulnerable and an attacker manages to take control. He will likely use a web shell, but once the attacker tries to interact with the local network (ping, scan, ...) the connection will be refused, so he can't compromize more servers.
The same will occur if he drops a malicious payload, the malware will not be able to reach internet (updates, remote execution, ...)

More with iptables

Basics

List rules
sudo iptables -L -vn

Flush Rules
sudo iptables -F

Reopen Policies
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT

You can use protocols instead of ports numbers (# defined in /etc/services).
sudo iptables -A INPUT -p tcp --dport https

Reboot

Rules don't survive reboot, use :

sudo iptables-save > ~/fw-rules
sudo iptables-restore ~/fw-rules

# or iptables-apply if you want the ability to roll back changes
    ## Applied
sudo iptables-apply ~/fw-rules
Applying new iptables rules from '/home/demo/fw-rules'... done.
Can you establish NEW connections to the machine? (y/N) y
... then my job is done. See you next time.
    ## Abort changes
sudo iptables-apply ~/fw-rules
Applying new iptables rules from '/home/demo/fw-rules'... done.
Can you establish NEW connections to the machine? (y/N) N
No affirmative response! Better play it safe...
Reverting to old iptables rules... done.

Modules

iptables is a wonderful tool and can be used for various uses, from MASQUERADING and managings lists to blacklistbots and even to handle Network QOS.

You may want to have a look in man iptables-extensions
(search are expressed in vim mode)

  • /comment
  • Q.O.S. - D.D.O.S. Protection -> /(conn|hash)?limit
  • I.P.S. -> /strings
  • White/Black Listing -> /recent
  • Bridging/NAT -> /MASQUERADE
  • Quotas -> /quota
  • user/permission based rules -> /owner
  • Multiple ports -> /multiport
  • Many others -> /.*

A Better Server Version

If -like me- you don't want to enable root to be allowed to reach any directions. // Mainly because i've seen some tomcats and postgres running as root *BLARF* (so compromised servers can reach internet ...)

It's better to get the static IPs rather to allow 'any'.
Note You can combine Fixed IP with the owner module.

cat /etc/resolv.conf
nameserver 192.168.42.250
nameserver 192.168.42.251

sudo iptables -A OUTPUT -d 192.168.42.250,192.168.42.251 -p udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT

I also did simplify flows within my network using a local debian mirroir rather than pointing on internet. (And having 200 outgoing flows when it's not needed).

cat /etc/apt/sources.list
deb https://192.168.42.42/debian main contrib non-free

sudo iptables -A OUTPUT -d 192.168.42.42 -p tcp --dport 443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT

So the full script is :

sudo iptables -A INPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Update management 
    ## DNS servs
sudo iptables -A OUTPUT -d 192.168.42.250,192.168.42.251 -p udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
    ## https mirror
sudo iptables -A OUTPUT -d 192.168.42.42 -p tcp --dport 443  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT

# Loopback ACCEPT 
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT

# Default Policy
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP

Toward perfection in 22 lines ?

If you also apply common sense (aka good pratices) you might want to change a few things in your rules.
* Accept ssh from the bastion only on the LAN interface
* Template exposed services on internet (/ports)
* Template exposed services on LAN (/ports)
* Template reached services on LAN (/ports)
* Use static(/virtual) ips form infrastructure services
* internet independant ((local) proxy services)

Stuck in the 80's with only one interface ?
iptables handle ip ranges !
(Adapt my values to match your range, and please no /8)
Replace :
-i eth1 by -s 192.168.42.0/24
-o eth1 by -d 192.168.42.0/24

Example with two interfaces:

# Allow replies
sudo iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

# Allow ssh from bastion
sudo iptables -A INPUT -i eth1 -s 192.168.42.1 -p tcp --dport 22  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "ssh from Bastion"

sudo iptables -A INPUT -i eth0 -p tcp --dport 443  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Expose https on www"


# Update management 
    ## DNS servs (values from resolv.conf)
sudo iptables -A OUTPUT -o eth1 -d 192.168.42.250,192.168.42.251 -p udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "DNS Server"
    ## https mirror (values from sources.list(.d))
sudo iptables -A OUTPUT -o eth1 -d 192.168.42.42 -p tcp --dport 443  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "DNS Server"
        ## Update mirror from deep-thought.local to ftp.debian.org
sudo iptables -A OUTPUT -o eth0 -s 192.168.42.42 -d 199.232.174.132 -p tcp -m multiport --dports 443,80  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "[20220301] Open mirror updates from deep-thought.local to ftp.debian.org"

# Loopback ACCEPT 
sudo iptables -A OUTPUT -o lo -j ACCEPT -m comment --comment "loopback"
sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment "loopback"

# SERVICES TEMPLATE 
    ## MySQL
sudo iptables -A INPUT -i eth1  -p tcp --dport 3306  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template mySQL"
sudo iptables -A OUTPUT -o eth1  -p tcp --dport 3306  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template mySQL"
    ## Syslog
sudo iptables -A INPUT -i eth1  -p udp --dport 514  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template syslog"
sudo iptables -A OUTPUT -o eth1  -p udp --dport 514  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template syslog"
    ## LDAPS
sudo iptables -A INPUT -i eth1  -p tcp --dport 636  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template LDAPS"
sudo iptables -A OUTPUT -o eth1  -p tcp --dport 636  -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template LDAPS"
    ## ALT HTTPS
sudo iptables -A INPUT -i eth1  -p tcp -m multiport --dports 8443,9443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template ALT-HTTPS"
sudo iptables -A OUTPUT -o eth1  -p tcp -m multiport --dports 8443,9443 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT -m comment --comment "Template ALT-HTTPS"

# OPTIONAL monitor/log drops 
sudo iptables -A INPUT -i eth1 -j LOG --log-prefix "firewall-log " --log-uid -m limit --limit 1000/hour
sudo iptables -A OUTPUT -j LOG --log-prefix "firewall-log " --log-uid -m limit --limit 1000/hour

# Nice, quick answers for eth1, drop is harsh
sudo iptables -A INPUT -i eth1 -j REJECT 
sudo iptables -A OUTPUT -o eth1 -j REJECT

# Default Policy
sudo iptables -P INPUT DROP
sudo iptables -P OUTPUT DROP

Which gives the following rule set:

$ iptables-saves
# Generated by iptables-save v1.8.7 on Tue Mar  1 12:12:01 2022
*filter
:INPUT DROP [2:64]
:FORWARD ACCEPT [0:0]
:OUTPUT DROP [142:8634]
-A INPUT -s 192.168.42.1/32 -i eth1 -p tcp -m tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "ssh from Bastion" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Expose https on www" -j ACCEPT
-A INPUT -i lo -m comment --comment loopback -j ACCEPT
-A INPUT -i eth1 -p tcp -m tcp --dport 3306 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template mySQL" -j ACCEPT
-A INPUT -i eth1 -p udp -m udp --dport 514 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template syslog" -j ACCEPT
-A INPUT -i eth1 -p tcp -m tcp --dport 636 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template LDAPS" -j ACCEPT
-A INPUT -i eth1 -p tcp -m multiport --dports 8443,9443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template ALT-HTTPS" -j ACCEPT
-A INPUT -i eth1 -m limit --limit 1000/hour -j LOG --log-prefix "firewall-log " --log-uid
-A INPUT -i eth1 -j REJECT --reject-with icmp-port-unreachable
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -d 192.168.42.250/32 -o eth1 -p udp -m udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "DNS Server" -j ACCEPT
-A OUTPUT -d 192.168.42.251/32 -o eth1 -p udp -m udp --dport 53 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "DNS Server" -j ACCEPT
-A OUTPUT -d 192.168.42.42/32 -o eth1 -p tcp -m tcp --dport 443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "DNS Server" -j ACCEPT
-A OUTPUT -s 192.168.42.42/32 -d 199.232.174.132/32 -o eth0 -p tcp -m multiport --dports 443,80 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "[20220301] Open mirror updates from deep-thought.local to ftp.debian.org" -j ACCEPT
-A OUTPUT -o lo -m comment --comment loopback -j ACCEPT
-A OUTPUT -o eth1 -p tcp -m tcp --dport 3306 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template mySQL" -j ACCEPT
-A OUTPUT -o eth1 -p udp -m udp --dport 514 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template syslog" -j ACCEPT
-A OUTPUT -o eth1 -p tcp -m tcp --dport 636 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template LDAPS" -j ACCEPT
-A OUTPUT -o eth1 -p tcp -m multiport --dports 8443,9443 -m state --state NEW,RELATED,ESTABLISHED -m comment --comment "Template ALT-HTTPS" -j ACCEPT
-A OUTPUT -m limit --limit 1000/hour -j LOG --log-prefix "firewall-log " --log-uid
-A OUTPUT -o eth1 -j REJECT --reject-with icmp-port-unreachable
COMMIT
# Completed on Tue Mar  1 12:12:01 2022

And displays like :
21053_Capture%20d%E2%80%99%C3%A9cran%20de%202022-03-02%2011-51-06

⚠️ ADDITIONAL NOTE ON PUBLIC MIRRORS ⚠️
Sometimes event with the ip or domain of the mirror, it's not working.
Can you guess why ?
-> The answer is : 302 Redirects !
Make sure you are pointing to the final endpoint (or allow them as well) :

wget deb.debian.org/debian 
--2022-03-01 01:17:03--  http://deb.debian.org/debian
Résolution de deb.debian.org (deb.debian.org)… 199.232.178.132, 2a04:4e42:2::644
Connexion à deb.debian.org (deb.debian.org)|199.232.178.132|:80… connecté.
requête HTTP transmise, en attente de la réponse… 302 Found
Emplacement : http://ftp.debian.org/debian/ [suivant]
--2022-03-01 01:17:03--  http://ftp.debian.org/debian/
Résolution de ftp.debian.org (ftp.debian.org)… 151.101.10.132, 2a04:4e42:2::644
Connexion à ftp.debian.org (ftp.debian.org)|151.101.10.132|:80… connecté.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : 6411 (6,3K) [text/html]
Sauvegarde en : « debian »

debian                                     100%[=====================================================================================>]   6,26K  --.-KB/s    ds 0,001s  

2022-03-01 01:17:03 (5,63 MB/s) — « debian » sauvegardé [6411/6411]

Conclusion

As you can see iptables efficient rules can be set in minutes.

iptables is a powerful tool that can be used for basic system and network administration but it can be leveraged to put in place more advanced concepts such as load balancing, rate limiting ...

Local isolation is also great to keep running most of the servers running when only one is damaged, iptables can also reduce the impact of a ransomware or worm really easily.

At the end this is a great tool to simplify the work of everyone while improving the overall I.T. performance.

"you've shown how simple it can be to manage and implement iptables so no there is no excuse ...." - Proofreader voice over the shoulder

Glider
"Keep RTFMs" -
twp_zero, your dedicated wobbulator.

iptables syntax

  • Table modfication switches

    • -I [TABLE] (index) : Insert rule (above or at index)
    • -A [TABLE] : Append rule (below)
    • -P [TABLE] [POLICY] : Define the standard policy
  • Source and dest filtering

    • -d [ip] : Destination
    • -s [IP] : Source
  • Protocol and ports specifications

    • -p [PROTO]
      • --dport [port]
      • --sport [port]
  • Interface specifications

    • -i [iface] : Input interface
    • -o [iface] : Output interface
  • Action to apply

    • -j [ACTION] : (jump)/apply action keywords
  • Generic TABLE keywords

    • INPUT : Incoming packet
    • OUTPUT : Outgoing packet
  • Generic PROTO keywords

    • icmp
    • tcp
    • udp
  • iptables ACTION/POLICY keywords

    • DROP : Drop the packet at kernel level
    • ACCEPT : Accept the packet
    • REJECT : Deny the packet but reply politely

Example:

# Create first rule
    # ACCEPT INPUT ssh from eth1 (admin iface)
sudo iptables -A INPUT -i eth1 -p tcp --dport 22 -j ACCEPT
# Insert a rule before the previous 
    # INSERT before all others lines
    # ACCEPT INPUT port 443 
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
# Set the second line
    # INSERT at line number 2
    # ACCEPT INPUT port 80 
sudo iptables -I INPUT 2 -p tcp --dport 80 -j ACCEPT
# Add a 4th line
    # Append line at the end
    # REJECT INPUT ssh from eth0 
sudo iptables -A INPUT -i eth0 -p tcp --dport 22 -j REJECT
# Drop by default
sudo iptables -P INPUT DROP

# Result 
iptables -L
Chain INPUT (policy DROP)
target     prot opt source               destination         
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:https
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:http
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
REJECT     tcp  --  anywhere             anywhere             tcp dpt:ssh reject-with icmp-port-unreachable

Want to resume your reading ? -> jump top