Modern Progress

In a Debian update for iptables 1.8.4-1 on 2019-12-04 the release notes included this.

Also, please note that iptables is no longer Priority: important. This
means it is not installed by default in every system. It has been replaced
by nftables.

So if you can’t find iptables at all, that’s a clue where to look. Basically the netfilter project.

General Firewall Information

Maybe you don’t really need to use iptables which is very fancy and can be somewhat complex. Maybe you need TCPWrappers which is a lot easier to do simple things like banning certain services or certain hosts on a specific machine.

Distro Specific Bloat

Oh god… Red Hat is trying to make iptables obsolete. Thanks a bunch. Here’s an article comparing "iptables vs firewalld".

The bowels of Ubuntu have excreted its own iptables interface replacement. I’m always annoyed at crap like this: "Developed to ease iptables firewall configuration, ufw provides a user friendly way…" Did Ubuntu ask themselves why the iptables devs hate users so much that they went to elaborate lengths to infuse their software with gratuitous unfriendliness? Could it be that they did not do that? Hmm. If you want to check firewalls the Ubuntu way, try this.

sudo ufw status verbose # My god, this is so damn EASY.

Rather than the sickeningly easy process of learning about a superfluous tool and iptables, you can just do this.

sudo ufw disable

Or continue to use iptables and ignoring seems to work too.

DenyHosts

More Automatic blacklist via DenyHosts. This helps cut down attempts from known ranges without even giving them the chance even at a slow rate. DenyHost website.

  • Sshguard - TCPWrappers, iptables, others.

  • fail2ban - situation specific control of TCPWrappers or iptables.

  • sshblack - for Perl fans.

  • fwknowp - encrypted port knocking.

Basic Operation Of iptables

Preparation

First you might want to make sure that your kernel has netfilter configured. This is the part of the kernel that actually applies rules and affects the networking traffic. Check with something like:

lsmod | grep iptable

Look for things like nf_conntrack if you want to do things that understand the state of traffic as opposed to reacting blindly to each packet with no context to what other packets may or should have an associated influence. Also if you’re creating a router, you’ll want to see some iptable_nat kind of stuff.

If your kernel is ready for iptables, then you’ll need the userspace part which is iptables itself. This sets and manages the rules that the kernel’s netfilter applies. Check rpm -qi iptables.

Checking Rules

Before getting to crazy with making your own rules, you’ll want to know what rules are already there. I used to use iptables -L, but I found that to be more confusing than it should be. It lists the rules in a format that is supposed to be easy to read (I guess) but it leaves off certain key pieces of information like what interface the rule applies to. So now I try to use:

iptables -S

This lists the rules as you would need to re-enter them in order to replicate the chains as they currently are. There is also:

iptables-save

This really could more properly be named "iptables-dump" since I don’t think it really saves anything.

Structure

The kernel manages network traffic in different phases which have their own "tables". These are generally mangle, nat, and filter. To see how they work check out this diagram. The main important table for most people doing most things is the filter table since the other two are more for specific traffic organizing.

In the filter table there are usually three default "chains". These are "INPUT", "OUTPUT", and "FORWARD". The INPUT chain is applied before traffic is sent to user (non-kernel) software (like your sshd or web server). The OUTPUT chain is applied after traffic originates from user processes (like ping or ssh clients). The FORWARD chain is modified in the kernel right away with the traffic not going to or emerging from any user process.

CentOS

CentOS (and probably the whole Red Hat/Fedora gang) does things like this:

/sbin/service iptables save

This writes the current rule set to /etc/sysconfig/iptables (making a backup at /etc/sysconfig/iptables.save or ...old). On next boot, the /sbin/iptables-restore command is executed (this takes the saved file as stdin). The /sbin/iptables-save command dumps a thorough listing of the iptables rules suitable for reconstruction. This output can be scooped up and applied to other machines with the same firewall requirements.

Simple Blocking

Sometimes something is happening on your network in real time that you’d just like to shut down. For example, let’s say that you want to block a naughty server from naughty children. Here’s the minimal intervention which I’ve had success with.

ss | grep -v 'https\|ssh' # Looking for a game server.
sudo iptables -A INPUT  -s 69.197.42.49 -j DROP # Block it.

Professor Killian’s Rate Limiting Iptables Rules

So this is my [Chip’s] basic setup:

-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 --source 137.0.0.0/8 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m limit --limit 20/h --limit-burst 5 -j ACCEPT
-A INPUT -i lo -j ACCEPT

So I rate-limit the new ssh connections to 20/hour, with a burst of 5. To make sure I can get in during an attack, I whitelist some addresses. Packets on established connections are accepted by the first rule.

Other ssh brute force blocking ideas

Limit incoming SSH attempts to a low number. In my case I limit to 2 connections in 60 seconds. I can tighten it even more but this did a lot to kill brute force attempts.

iptables -I INPUT -p tcp -i vlan1 --dport 2242 -j DROP
iptables -I INPUT -p tcp -i vlan1 --dport 2242 -m state --state NEW -m limit --limit 2/min -j ACCEPT
iptables -I INPUT -p tcp -i vlan1 --dport 2242 -m state --state RELATED,ESTABLISHED -j ACCEPT
  • Explore "TARPIT" instead of "DROP"

Xed’s Firewall Clearing Script

This script basically takes out any firewall rules and resets network access to be completely unrestricted. One use of this is to put this in a cron job every so often (or an at job) so that while you’re working on sketchy rule changes, you don’t lock yourself out.

firewall_reset
#!/bin/bash
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Written by Chris X Edwards 11.07.28
# Script to clear firewall rules. Use this in a cron job to ensure you
# don't lock yourself out while working on rule changes.
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
VERBOSE=1
IPTABLES="/sbin/iptables" # iptables path and command

function rule_setup {
    message $FUNCNAME
    # Try global flush/delete to clear anything already here.
    $IPTABLES --table filter --flush  # Try to get any
    $IPTABLES --table filter --delete-chain  # Try to get any user-def'd
    # flush any existing rules in built-in chains
    $IPTABLES --table filter --flush INPUT
    $IPTABLES --table filter --flush OUTPUT
    # set default policies
    $IPTABLES --policy INPUT ACCEPT
    $IPTABLES --policy OUTPUT ACCEPT
    $IPTABLES --policy FORWARD ACCEPT
    return
} # end function rule_setup

function message {
    if [ $VERBOSE ]; then echo " + Xed Custom Firewall: Executing $1";
    fi
} # end function message

rule_setup

Here’s a smoother way to recover from bold firewall rule changes that are made remotely. Used with the previous code, it should reset things if you lose your connection.

#!/bin/bash
SCRIPT=$$
function check4user {
    WAITFOR=60 # seconds
    function timeout {
        sleep $WAITFOR
        echo
        echo "You seem locked out. Resetting the firewall rules now."
        clearall
        kill $SCRIPT
    } # End function timeout.
    timeout &
    TIMEOUTID=$!
    read -p "Input something to indicate you are not locked out. "
    disown $TIMEOUTID
    kill $TIMEOUTID
} # End function check4user.

echo "Executing reckless firewall rule changes now."
echo "This could lock you out!"

check4user
echo "Everything must have gone ok."

And of course if that is all too complicated, just clear the firewall in 10 minutes in case things don’t work out.

echo /see_script_above/firewall_reset | at now + 10min
sleep 600 && /see_script_above/firewall_reset # In screen/tmux.

Deleting A Firewall Rule

To get rid of a rule that is mistakenly added, you can run the same command that added the rule but change the -I to -D. This is useful if you just inserted a rule that, in immediate retrospect, doesn’t make sense.

Added:

iptables -I OUTPUT -m state --state NEW -p tcp -d 10.0.0.111 --dport 636 -j ACCEPT

Ooops:

iptables -D OUTPUT -m state --state NEW -p tcp -d 10.0.0.111 --dport 636 -j ACCEPT

Now it’s gone.

A tougher problem is if there is a rule there that you didn’t put there and you need to weed it out. In this case, it’s often easiest to just delete it by it’s rule number. That brings up the next question of how do you know the rule number. Do this:

iptables -vnL --line-numbers

Then delete the offending rule with something like this:

iptables -D INPUT 1

Careful to get the right rule in the right chain because if you don’t, you can lock yourself out. Ahem…

Xed’s Simple Firewall Rules For Hosts

Here is a template for a simple set of rules that can be deployed to do the normal things done by iptables on a single machine. This includes blocking certain services, rate limiting connection attempts, logging, etc.

#!/bin/bash
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Simple Single Machine Firewall
# Written by Chris X Edwards 11.07.28
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#==========================DEFINITIONS==========================
IPTABLES="echo /sbin/iptables" # iptables path and command

# ---- DNS
DNS1="172.19.0.252" # ns.example.edu
DNS2="172.19.1.51" #  ns0.example.edu
DNS3="172.19.16.2" #   ns1.example.edu
DNS="${DNS1} ${DNS2} ${DNS3}"

# ---- FRIENDS
# SP Offices
VLAN89="172.19.138.160/27 172.19.138.160/27"

# SC Office
VLAN79="172.19.243.96/27"

# Machine Room VLAN 325
VLAN325="172.19.63.60 172.19.63.61 172.19.63.62 172.19.63.63 172.19.63.64"

FRIENDS="${VLAN890} ${VLAN796} ${VLAN3252}"

function rule_setup {
    message $FUNCNAME
    # Try global flush/delete to clear anything already here.
    $IPTABLES --table filter --flush  # Try to get any
    $IPTABLES --table filter --delete-chain  # Try to get any user-def'd
    # flush any existing rules in built-in chains
    $IPTABLES --table filter --flush INPUT
    $IPTABLES --table filter --flush OUTPUT
    # set default policies
    $IPTABLES --policy INPUT DROP
    $IPTABLES --policy OUTPUT DROP
    return
} # end function rule_setup

# This controls packets sent to and from firewall itself.
function core_rules {
    # Rules that are a good idea in most cases.
    message $FUNCNAME
    # Always allow loopback
    $IPTABLES -A INPUT --in-interface lo -j ACCEPT
    $IPTABLES -A OUTPUT --out-interface lo -j ACCEPT
    # Allow all firewall pings for the moment
    $IPTABLES -A INPUT --protocol icmp -j ACCEPT
    $IPTABLES -A OUTPUT --protocol icmp -j ACCEPT
    # Allow machine to get portage cache with rsync
    $IPTABLES -A OUTPUT --protocol tcp --dport 873 -j ACCEPT
    $IPTABLES -A INPUT --protocol tcp ! --syn --sport 873 -j ACCEPT

    # Allow machine to get and receive DNS
    for D in DNS
    do
        $IPTABLES -A INPUT  --protocol tcp ! --syn --src $D --sport domain -j ACCEPT
        $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $D --dport domain -j ACCEPT
        $IPTABLES -A INPUT  --protocol udp --src $D --sport domain -j ACCEPT
        $IPTABLES -A OUTPUT --protocol udp --dst $D --dport domain -j ACCEPT
    done
    return
} # end function core_rules

Xed’s Firewall Machine Template

Here’s a simple firewall script that will serve as a template for creating a dedicated firewall machine which can arbitrate connections for other machines. The nice thing about this format is that it can be scaled to complex applications in an organized way..

#!/bin/bash
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Simple Custom Firewall Server Rules
# Written by Chris X Edwards 08.12.17
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#==========================DEFINITIONS==========================
VERBOSE=1  # Comment this out to hide debugging information
IPTABLES="/sbin/iptables" # iptables path and command

# ---- DNS
DNS1="132.239.0.252" # ns0.ucsd.edu
DNS2="128.54.16.2" #   ns1.ucsd.edu

# ---- FRIENDS
XED="10.0.243.108"
PRO="10.0.50.3 10.0.50.2 10.0.50.1"

FRIENDS="${PRO} ${XED}"

function rule_setup {
    message $FUNCNAME
    # Try global flush/delete to clear anything already here.
    $IPTABLES --table filter --flush  # Try to get any
    $IPTABLES --table filter --delete-chain  # Try to get any user-def'd
    # flush any existing rules in built-in chains
    $IPTABLES --table filter --flush INPUT
    $IPTABLES --table filter --flush OUTPUT
    # set default policies
    $IPTABLES --policy INPUT DROP
    $IPTABLES --policy OUTPUT DROP
    return
} # end function rule_setup

# This controls packets sent to and from firewall itself.
function core_rules {
    # Rules that are a good idea in most cases.
    message $FUNCNAME
    # Always allow loopback
    $IPTABLES -A INPUT --in-interface lo -j ACCEPT
    $IPTABLES -A OUTPUT --out-interface lo -j ACCEPT
    # Allow all firewall pings for the moment
    $IPTABLES -A INPUT --protocol icmp -j ACCEPT
    $IPTABLES -A OUTPUT --protocol icmp -j ACCEPT
    # Allow machine to get portage cache with rsync
    $IPTABLES -A OUTPUT --protocol tcp --dport 873 -j ACCEPT
    $IPTABLES -A INPUT --protocol tcp ! --syn --sport 873 -j ACCEPT
    # Allow machine to get and receive DNS
    $IPTABLES -A INPUT --protocol tcp ! --syn --src $DNS1 --sport domain -j ACCEPT
    $IPTABLES -A INPUT --protocol tcp ! --syn --src $DNS2 --sport domain -j ACCEPT
    $IPTABLES -A INPUT --protocol udp --src $DNS1 --sport domain -j ACCEPT
    $IPTABLES -A INPUT --protocol udp --src $DNS2 --sport domain -j ACCEPT
    $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $DNS1 --dport domain -j ACCEPT
    $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst $DNS2 --dport domain -j ACCEPT
    $IPTABLES -A OUTPUT --protocol udp --dst $DNS1 --dport domain -j ACCEPT
    $IPTABLES -A OUTPUT --protocol udp --dst $DNS2 --dport domain -j ACCEPT
    return
} # end function core_rules

function special_rules {
    # Rules that are specific to the mission of this host.
    message $FUNCNAME
    # Allow httpd proxy server to access the web
    ##$IPTABLES -A OUTPUT --protocol tcp --dport 80 -j ACCEPT
    ##$IPTABLES -A INPUT --protocol tcp ! --syn --sport 80 -j ACCEPT
    ##$IPTABLES -A OUTPUT --protocol tcp --dport 443 -j ACCEPT
    ##$IPTABLES -A INPUT --protocol tcp ! --syn --sport 443 -j ACCEPT
    #Allow a connection to Xed's personal machine for external testing.
    # Uncomment if other protocols besides ssh are needed.
        #$IPTABLES -A INPUT --protocol tcp --src $XED -j ACCEPT
        #$IPTABLES -A OUTPUT --protocol tcp --dst $XED -j ACCEPT
        #$IPTABLES -A INPUT --protocol udp --src $XED -j ACCEPT
        #$IPTABLES -A OUTPUT --protocol udp --dst $XED -j ACCEPT
    return
} # end function special_rules

function rules_for_friends {
    if [ $1 ]; then local MACHINE=$1; fi
    message "$FUNCNAME on $MACHINE"
    # Allow ssh connections from the machine
    $IPTABLES -A INPUT --protocol tcp --src ${MACHINE} --dport ssh -j ACCEPT
    $IPTABLES -A OUTPUT --protocol tcp --dst ${MACHINE} --sport ssh -j ACCEPT
    $IPTABLES -A INPUT --protocol udp --src ${MACHINE} --dport ssh -j ACCEPT
    $IPTABLES -A OUTPUT --protocol udp --dst ${MACHINE} --sport ssh -j ACCEPT
    # Allow this host to ssh to the machine
    $IPTABLES -A OUTPUT --protocol tcp --dst ${MACHINE} --dport ssh -j ACCEPT
    $IPTABLES -A INPUT --protocol tcp ! --syn --src ${MACHINE} --sport ssh -j ACCEPT
    # Allow httpd proxy server access from this machine
    $IPTABLES -A INPUT --protocol tcp --src ${MACHINE} --dport 8080 -j ACCEPT
    $IPTABLES -A OUTPUT --protocol tcp ! --syn --dst ${MACHINE} --sport 8080 -j ACCEPT
    return
} # end rules_for_friends

# This is a super simple test of basic firewall functionality.
# This can be used to troubleshoot firewall problems and to assist
# in initial configuration. Once everything proves that it works,
# make sure this is disabled by making it false:
TEST=false
#TEST=true
function simple_test_of_rules {
    message $FUNCNAME
    # For a simple test, reset the policies to be very permissive
    $IPTABLES --policy INPUT ACCEPT
    $IPTABLES --policy OUTPUT ACCEPT
    #$IPTABLES --policy FORWARD ACCEPT
    # Test the logging. Ping packets should be logged.
    #$IPTABLES -A FORWARD --protocol all -j LOG --log-prefix "| FORWARD |"
    #$IPTABLES -A INPUT --protocol all -j LOG --log-prefix "| I |"
    #$IPTABLES -A OUTPUT --protocol all -j LOG --log-prefix "| O |"
    # These test rules exclude SSH so you can test stuff over SSH without
    # filling up the logs with your own connection.
    $IPTABLES -A INPUT --protocol tcp --dport ! 22  -j LOG --log-prefix "| I |"
    $IPTABLES -A OUTPUT --protocol tcp --sport ! 22  -j LOG --log-prefix "| O |"
    $IPTABLES -A INPUT --protocol udp --dport ! 22  -j LOG --log-prefix "| I |"
    $IPTABLES -A OUTPUT --protocol udp --sport ! 22  -j LOG --log-prefix "| O |"
    return
} # end function simple_test_of_rules

function message {
    if [ $VERBOSE ]; then echo " + Xed Custom Firewall: Executing $1"; fi
} # end function message
#==========================MAIN SCRIPT==========================
# Clean up built-in rules and set policy
rule_setup

# Provide a function to do basic testing of iptables functionality
if $TEST ; then simple_test_of_rules; exit; fi

# Establish rules for this host
core_rules
special_rules

# Set up rules for each FRIEND who needs access
for X in $FRIENDS
do
    rules_for_friends $X;
done

VLAN Identification

This isn’t really related to firewalls per se, but if you are managing VLANs it can be tricky to know what VLAN a live jack is on. Here is the technique I used to figure it out.

sudo tcpdump -nn -v -i eth0 -s 1500 -c 1 "ether[20:2] == 0x2000" | grep -i vlan