iptables

There are 2 ways to go about setting up the built-in firewall in RHEL/CentOS/SL 7: the command line interface (CLI) with iptables and the graphical user interface (GUI) with firewall-cmd. The latter is a front-end that uses the former to actually set up the filtering rules. We shall see how to set up the filtering rules straight with iptables and when we're done, we'll see how to do the same with the GUI in the next section.

To learn about iptables we will examine a set of filtering rules to see what the syntax is like.

# cat /etc/iptables.sh
PATH=/usr/sbin:/usr/bin
.
# set default policy for 3 chains
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
.
# flush all rules for all chains
iptables -F
.
# delete any user-defined chains
iptables -X

In these first few lines we set the default policy to ACCEPT for the INPUT, OUTPUT & FORWARD chains with the -P option. Then we flush all rules (-F) and delete any user-defined chain (-X).

When we flush or delete, we can specify a chain name (INPUT, OUTPUT, FORWARD, user-defined) so that that flush or delete operation only applies to it (e.g. "input -F INPUT") or we can leave the chain name out so that it applies to all of them.

The structure of the rules tends to always be the same:

iptables  [ A | D | I | R | L | S | F | N | X | P | E ]  chain-name  rule-specification

Let's look at the 2nd half of the rules file:

# accept any incoming packets from the loopback interface
iptables -A INPUT -i lo -j ACCEPT
.
# accept any incoming packets from already established connections
iptables -A INPUT -m conntrack -m state --state ESTABLISHED,RELATED -j ACCEPT
.
# accept any new incoming packets from 192.168.0.0/24 for port 22 using tcp
iptables -A INPUT -s 192.168.0.0/24 --ipv4 -p tcp -m state --state NEW --dport 22 -j ACCEPT
.
# accept pings from local network
iptables -A INPUT -s 192.168.0.0/24 -p icmp --icmp-type echo-request -j ACCEPT
.
# drop any other incoming packets but log them for analysis
iptables -A INPUT -j LOG --log-prefix "firewall denied: " --log-level 6
iptables -P INPUT DROP
.
# reject any forward requests sending error back
iptables -A FORWARD -j LOG --log-prefix "firewall forward: " --log-level 6
iptables -A FORWARD -j REJECT --reject-with icmp-host-prohibited

The comments above are quite self explanatory but let's go through the commands just in case.

When we say "INPUT -A" we are adding a new rule at the end of the existing INPUT rule set. So order matters a lot!

With the -i flag we are stating that a rule only applies to packets arriving to a particular interface (i.e. "-i lo" only applies to packets arriving to the loopback interface). We can use the -o flag to write rules for packets exiting a particular interface (e.g. "-o eth0" matches only packets exiting through eth0).

The -j flag states what action to take with the packets that match the rule. The 4 most important actions (there are a few more!) are ACCEPT, REJECT, LOG and DROP. The difference between a REJECT and DROP is that the former notifies the source of the packet about its rejection and the reason why. The latter just drops the packet without further ado.

The -s and -d flags (and their aliases --src, --source, --dst and --destination) specify the source and destination IP or network the rule applies to. For example "-s 192.168.0.0/16" would apply to all that class B network whereas "-d 10.10.32.45" would only apply to that specific destination IP.

The -4 and -6 flags (or their respective aliases --ipv4 --ipv6) are used to filter by IP protocol.

The -p flag is used to filter by protocol. One or more of the following protocols can be specified: tcp, udp, udplite, icmp, icmpv6, esp, ah, sctp, mh or the special keyword "all".

Additionally, if we specify tcp/udp/udplite/dccp/sctp as protocols, then we can also filter the source & destination ports with --source-port, --sport, --destination-port and --dport. For example...

iptables -A INPUT -s 192.168.0.0/24 --ipv4 -p tcp -m state --state NEW --dport 22 -j ACCEPT

... will only match TCP IPv4 packets, coming from the given C class network, establishing new connections and with the destination port 22.

The -m keyword allows more fine-grained filtering and its options depend on the protocols we are filtering.

  • For tcp, the -m keyword gives us the option to filter packets with certain flags. If we use --tcp-flags [ SYN, ACK, FIN, RST, URG, PSH, ALL, NONE ] then we can filter out packets with those flags set. Or we can use --syn to match packets with the SYN bit set but ACK, RST and FIN cleared. This last option might come in handy when we want to let in just new connections but filter out any dodgy connection requests.

  • For udp the only available filters are source & destination port.

  • For icmp we can filter by icmp type specifying one or more of the following:

    • • any

    • • echo-reply (pong)

    • • destination-unreachable

      • network-unreachable

      • host-unreachable

      • protocol-unreachable

      • port-unreachable

      • fragmentation-needed

      • source-route-failed

      • network-unknown

      • host-unknown

      • network-prohibited

      • host-prohibited

      • TOS-network-unreachable

      • TOS-host-unreachable

      • communication-prohibited

      • host-precedence-violation

      • precedence-cutoff

    • • source-quench

    • • redirect

      • network-redirect

      • host-redirect

      • TOS-network-redirect

      • TOS-host-redirect

    • • echo-request (ping)

    • • router-advertisement

    • • router-solicitation

    • • time-exceeded (ttl-exceeded)

      • ttl-zero-during-transit

      • ttl-zero-during-reassembly

    • • parameter-problem

      • ip-header-bad

      • required-option-missing

    • • timestamp-request

    • • timestamp-reply

    • • address-mask-request

    • • address-mask-reply

 

Let us review some of the examples laid out before to understand the last paragraphs:

iptables -A INPUT -m conntrack -m state --state ESTABLISHED,RELATED -j ACCEPT
.
iptables -A INPUT -s 192.168.0.0/24 --ipv4 -p tcp -m state --state NEW --dport 22 --syn -j ACCEPT
.
iptables -A INPUT -s 192.168.0.0/24 -p icmp --icmp-type echo-request -j ACCEPT

The second rule will only let through TCP (-p tcp) IPv4 (--ipv4) packets, originating in 192.168.0.0/24 (-s 192.168.0.0/24), meant for port 22 (--dport 22), with the SYN bit set and ACK/RST/FIN clear (--syn) and without an established connection (--state NEW).

All SSH packets related to already established connections will be caught by the first rule.

The 3rd rule will let in ICMP packets originated in 192.168/24 but only of type echo-request. Any other ICMP packet type will be dropped as per the default policy:

iptables -P INPUT DROP

The -m conntrack option enables us to track and follow the state of connections to the system, and the -m state is a subset of the conntrack module.

We see that just before specifying a default INPUT policy of DROP at the end, we log all packets for auditing/security reasons:

iptables -A INPUT -j LOG --log-prefix "firewall denied: " --log-level 6

Log entries will be sent to the kernel log or syslog with the log level 6 and will start with the string "firewall denied" to make them easily "grepable". To avoid filling the filesystem with entries of failed connections we can (or should?!) limit the number of log entries generated.

iptables -A INPUT -j LOG --log-prefix "firewall denied: " --log-level 6 --limit 1/second

We can set the limit to any number of seconds, minutes, hours or days.

We can also limit the number of parallel connections originating from a certain IP block. For instance...

iptables -A INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-above 4 -j REJECT

... would drop any attempts to have more than 4 SSH connections per client. We could achieve the same with the rule...

iptables -A INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-upto 4 -j ACCEPT

We can also limit the number of parallel connections per network block...

iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 24 --connlimit-mask 24 -j REJECT

 

In the examples above we have just seen how to append (-A), flush (-F), delete chains (-X) and set default policies (-P). We can also check (-C), delete (-D), insert (-I) and replace (-R) chains/rules but these 4 actions are more meant to change things on-the-fly which I am generally wary of. I would rather plan and lay out the rules in a file to execute them sequentially.

Additionally, we have not covered the PREROUTING, POSTROUTING and FORWARD chains which are more advanced topics and are used less often.

The iptables-extensions offer us far more possibilities than what we have explained so far and I would encourage you to dig further via the Internet or by buying the "Linux iptables pocket reference" by Gregor N Purdy for a few bucks. But just to give you an idea of what you can do with iptables:

• Specify source & destination IP ranges instead of IPs or networks.

• Create OUTPUT chain rules that apply only to certain cgroups (jobs/tasks).

• Set up load-balancing across multiple interfaces.

• Add comments to rules to explain their raison d'ētre to other members of staff.

• Change the priority or QoS of connections that use a lot of bandwidth.

• Specify MAC addresses instead of IPs for INPUT, PREROUTING and FORWARD chains' rules.

• Specify rules for OUTPUT and POSTROUTING chains that apply to certain UIDs (e.g. block certain IP ranges for a certain user?).

• Create rules that look for strings within packets.

• Configure validity timeframes for rules (e.g. 08:00 - 19:00 from Monday - Friday).

• and a long etcetera...

<< tcp wrappers          firewalld >>