Building Linux Firewalls With Good Old Iptables: Part 2

3050

When last we met we reviewed some iptables fundamentals. Now you’ll have two example firewalls to study, one for a single PC and one for a LAN. They are commented all to heck to explain what they’re doing.

This is for IPv4 only, so I’ll write up some example firewalls for IPv6 in a future installment.

I leave as your homework how to configure these to start at boot. There is enough variation in how startup services are managed in the various Linux distributions that it makes me tired to think about it, so it’s up to you figure it out for your distro.

Lone PC Firewall

Use the lone PC firewall on laptop, desktop, or server system. It filters incoming and outgoing packets only for the host it is on.

#!/bin/bash

# iptables single-host firewall script

# Define your command variables
ipt="/sbin/iptables"

# Define multiple network interfaces
wifi="wlx9cefd5fe8f20"
eth0="enp0s25"

# Flush all rules and delete all chains
# because it is best to startup cleanly
$ipt -F
$ipt -X 
$ipt -t nat -F
$ipt -t nat -X
$ipt -t mangle -F 
$ipt -t mangle -X 

# Zero out all counters, again for 
# a clean start
$ipt -Z
$ipt -t nat -Z
$ipt -t mangle -Z

# Default policies: deny all incoming
# Unrestricted outgoing

$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ipt -t nat -P OUTPUT ACCEPT 
$ipt -t nat -P PREROUTING ACCEPT 
$ipt -t nat -P POSTROUTING ACCEPT 
$ipt -t mangle -P PREROUTING ACCEPT 
$ipt -t mangle -P POSTROUTING ACCEPT

# Required for the loopback interface
$ipt -A INPUT -i lo -j ACCEPT

# Reject connection attempts not initiated from the host
$ipt -A INPUT -p tcp --syn -j DROP

# Allow return connections initiated from the host
$ipt -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# If the above rule does not work because you
# have an ancient iptables version (e.g. on a 
# hosting service)
# use this older variation instead
$ipt -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Accept important ICMP packets. It is not a good
# idea to completely disable ping; networking
# depends on ping
$ipt -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT

# The previous lines define a simple firewall
# that does not restrict outgoing traffic, and
# allows incoming traffic only for established sessions

# The following rules are optional to allow external access
# to services. Adjust port numbers as needed for your setup

# Use this rule when you accept incoming connections
# to services, such as SSH and HTTP
# This ensures that only SYN-flagged packets are
# allowed in
# Then delete '$ipt -A INPUT -p tcp --syn -j DROP'
$ipt -A INPUT p tcp ! --syn -m state --state NEW -j DROP

# Allow logging in via SSH
$ipt -A INPUT -p tcp --dport 22 -j ACCEPT

# Restrict incoming SSH to a specific network interface
$ipt -A INPUT -i $eth0 -p tcp --dport 22 -j ACCEPT

# Restrict incoming SSH to the local network
$ipt -A INPUT -i $eth0 -p tcp -s 192.0.2.0/24 --dport 22 -j ACCEPT

# Allow external access to your HTTP server
# This allows access to three different ports, e.g. for
# testing. 
$ipt -A INPUT -p tcp -m multiport --dport 80,443,8080 -j ACCEPT

# Allow external access to your unencrypted mail server, SMTP,
# IMAP, and POP3.
$ipt -A INPUT -p tcp -m multiport --dport 25,110,143 -j ACCEPT

# Local name server should be restricted to local network
$ipt -A INPUT -p udp -m udp -s 192.0.2.0/24 --dport 53 -j ACCEPT
$ipt -A INPUT -p tcp -m udp -s 192.0.2.0/24 --dport 53 -j ACCEPT

You see how it’s done; adapt these examples to open ports to your database server, rsync, and any other services you want available externally. One more useful restriction you can add is to limit the source port range. Incoming packets for services should be above port 1024, so you can allow in only packets from the high-numbered ports with --sport 1024:65535, like this:

$ipt -A INPUT -i $eth0 -p tcp --dport 22 --sport 1024:65535 -j ACCEPT

LAN Internet Connection-Sharing Firewall

It is sad that IPv4 still dominates US networking, because it’s a big fat pain. We need NAT, network address translation, to move traffic between external publicly routable IP addresses and internal private class addresses. This is an example of a simple Internet connection sharing firewall. It is on a device sitting between the big bad Internet and your LAN, and it has two network interfaces, one connecting to the Internet and one that connects to your LAN switch.

#!/bin/bash

# iptables Internet-connection sharing 
# firewall script

# Define your command variables
ipt="/sbin/iptables"

# Define multiple network interfaces
wan="enp0s24"
lan="enp0s25"

# Flush all rules and delete all chains
# because it is best to startup cleanly
$ipt -F
$ipt -X 
$ipt -t nat -F
$ipt -t nat -X
$ipt -t mangle -F 
$ipt -t mangle -X 

# Zero out all counters, again for 
# a clean start
$ipt -Z
$ipt -t nat -Z
$ipt -t mangle -Z

# Default policies: deny all incoming
# Unrestricted outgoing

$ipt -P INPUT DROP
$ipt -P FORWARD DROP
$ipt -P OUTPUT ACCEPT
$ipt -t nat -P OUTPUT ACCEPT 
$ipt -t nat -P PREROUTING ACCEPT 
$ipt -t nat -P POSTROUTING ACCEPT 
$ipt -t mangle -P PREROUTING ACCEPT 
$ipt -t mangle -P POSTROUTING ACCEPT

# Required for the loopback interface
$ipt -A INPUT -i lo -j ACCEPT

# Set packet forwarding in the kernel
sysctl net.ipv4.ip_forward=1

# Enable IP masquerading, which necessary for NAT
$ipt -t nat -A POSTROUTING -j MASQUERADE

# Enable unrestricted outgoing traffic, incoming
# is restricted to locally-initiated sessions only
$ipt -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ipt -A FORWARD -i $wan -o $lan -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$ipt -A FORWARD -i $lan -o $wan -j ACCEPT

# Accept important ICMP messages
$ipt -A INPUT -p icmp --icmp-type echo-request  -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
$ipt -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT

Stopping the Firewall

Take the first sections of the example firewall scripts, where it flushes, zeroes, and sets all default policies to ACCEPT, and put them in a separate script. Then use this script to turn your iptables firewall “off”.

The documentation on netfilter.org is ancient, so I recommend using man iptables. These examples are basic and should provide a good template for customizing your own firewalls. Allowing access to services on your LAN through your Internet firewall is good subject for another day, because thanks to NAT it is a huge pain and needs a lot of rules and explaining.

Learn more about Linux through the free “Introduction to Linux” course from The Linux Foundation and edX.