Brent's Firewall

The Internet is a scary place, and so putting a host directly onto the Internet comes with some responsibility. Any such hosts should be carefully guarded with strict policies for what they can do, and who can do what to them. What this really means is you need to be careful what services you allow hosts from the Internet to reach, and if they can, controlling how that interaction occurs. For me, this boils down to a few main things:

  1. As few as possible services running directly on my router with a default deny ruleset of packet filters controlling its inbound and outbound traffic.<
  2. For ssh, either lock down what source IPs can reach it (if you have a few trusted hosts) and block everything else, or if you want it open to the entire Internet require public key authentication and disable logging in as well-known accounts like root.
  3. For any other services that you enable, consider whether they need to be accessible to the entire Internet. FTP is a good example. Can it be locked down to just a few hosts that are allowed to access it?

Iptables Configuration Script

Here's my somewhat carefully cultivated iptables ruleset for my home Linux router (currently a SolidRun Clearfog Base running Armbian). I'm lazy and run this out of /etc/rc.local. I've been growing it over the years, and some parts of it are inconsistent - but it works for me. I left in stuff that I have commented out in the past because it might be useful to somebody.

The basic ideas are:


# external interface with public address
# internal interface. If multiple, change to a loop below
# Only use EXTIP for WAN static IP, DHCP uses masquerading

# list of IPs and hostnames to allow inbound

# Enable IP forwarding in the kernel
echo 1 > /proc/sys/net/ipv4/ip_forward
# Enable reverse path filtering
echo 1 > /proc/sys/net/ipv4/conf/eth0/rp_filter
echo 1 > /proc/sys/net/ipv4/conf/eth1/rp_filter

# Flush/purge everything - in case this gets rerun after boot.
# Be careful not to make any mistakes later on that could cut off
# necessary access to the device remotely after flushing.
iptables -X
iptables -F
iptables -t nat -F

# REJECT for INPUT to be polite at the end of the script - start with drop
iptables -P INPUT DROP
# FORWARD is just used for SNAT, and that traffic should not be filtered
# Anything outbound should be dropped unless an exception is made
iptables -P OUTPUT DROP

# Only act on NEW connections - if established or related it was already
# approved at a previous time
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Try this if the above line doesn't work - this changed at some point
#iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Trust everything over loopback...
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# ... and trust local network too. Consider removing this and adding
# specific things to allow.
iptables -A INPUT -i $INTIF -j ACCEPT
iptables -A OUTPUT -o $INTIF -j ACCEPT

# use MASQUERADE if IP is dynamic, otherwise use SNAT if static IP from ISP
iptables -t nat -A POSTROUTING -o $EXTIF -j MASQUERADE
#iptables -t nat -A POSTROUTING -o $EXTIF -j SNAT --to $EXTIP

# allow DHCP from this host via external interface - this can be disabled
# if you have a static IP from your ISP.
# If someone somehow breaks into your router, this rule would allow them
# to contact internet hosts using UDP 67:68 - but firewalling DHCP is hard
iptables -A OUTPUT -o $EXTIF -p udp --dport 67 --sport 68 -j ACCEPT
iptables -A INPUT -i $EXTIF -p udp --dport 68 --sport 67 -j ACCEPT

# Allow SSH+mosh from everywhere. Check sshd config that root cannot login
# remotely and that key auth is required from internet. If users can
# auth via passwords, consider moving inside of TRUSTED_ALLOW block
iptables -A INPUT -p tcp -i $EXTIF --dport ssh -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp -i $EXTIF --dport 60000:61000 -m conntrack --ctstate NEW -j ACCEPT

    iptables -A INPUT -p tcp -i $EXTIF -s $HOST --dport http -m conntrack --ctstate NEW -j ACCEPT
    iptables -A INPUT -p tcp -i $EXTIF -s $HOST --dport https -m conntrack --ctstate NEW -j ACCEPT

# If this router is a DNS server or doesn't use a local DNS server
# via $INTIF, it needs to be able to contact its upstream caches. If it
# doesn't uses caches and instead is a recursive nameserver, you'll
# need to open this up to allow all outbound DNS
#for HOST in $DNS_SERVERS; do
#    iptables -A OUTPUT -o $EXTIF -p udp --dport domain -d HOST -m conntrack --ctstate NEW -j ACCEPT
#    iptables -A OUTPUT -o $EXTIF -p tcp --dport domain -d HOST -m conntrack --ctstate NEW -j ACCEPT

# Allow outbound IMAP and SMTP connections to Gmail. Attempts to be any more
# specific than this will only end in tears. This is needed in order to use
# something like pine or mutt directly on this router.
#for SVR in $MAIL_SERVERS; do
#    iptables -A OUTPUT -p tcp -o $EXTIF -d $SVR --dport 993 -j ACCEPT
#    iptables -A OUTPUT -p tcp -o $EXTIF -d $SVR --dport 587 -j ACCEPT
#    iptables -A OUTPUT -p tcp -o $EXTIF -d $SVR --dport 465 -j ACCEPT

# Allow all outbound access to NTP - the pool addresses change and
# I've had unreliable results hardcoding specific servers
iptables -A OUTPUT -p udp -o $EXTIF --dport ntp -j ACCEPT
iptables -A OUTPUT -p tcp -o $EXTIF --dport ntp -j ACCEPT

# Enable for emergency DNS.
#iptables -A OUTPUT -p udp -o $EXTIF -d --dport 53 -j ACCEPT

# Allow outbound ICMP ping and some special UDP (for traceroute)
# Not needed, but handy. If you plan to give out shell accounts to
# friends, not doing this will annoy them.
iptables -A OUTPUT -p icmp --icmp-type 8 -o $EXTIF -j ACCEPT
iptables -A OUTPUT -p udp -o $EXTIF --dport 33434:33534 -j ACCEPT

# Respond to incoming ping requests - not needed, but polite
iptables -A INPUT -i $EXTIF -p icmp --icmp-type 8 -j ACCEPT

# Be polite to the internet and send an appropriate reject packet.
# Without this, default deny policy would just drop packets.
# It may be possible to specify this as the default policy of the
# INPUT chain rather than just as the last rule, but last time I
# tried this failed. Do it last.
iptables -A INPUT -j REJECT

OpenSSH Daemon Configuration

Here's my entire sshd configuration. It's mostly stock Debian, but with a few changes I'll point out below.

# Package generated configuration file
# See the sshd_config(5) manpage for details

# What ports, IPs and protocols we listen for
Port 22
# Use these options to restrict which interfaces/protocols sshd will bind to
#ListenAddress ::
Protocol 2
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
#Privilege Separation is turned on for security
UsePrivilegeSeparation yes

# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 1024

# Logging
SyslogFacility AUTH
LogLevel INFO

# Authentication:
LoginGraceTime 120 
PermitRootLogin no
StrictModes yes

RSAAuthentication yes 
PubkeyAuthentication yes 
#AuthorizedKeysFile %h/.ssh/authorized_keys

# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes 
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes

# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no

# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no

# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes

# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes

X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes 
#UseLogin no

#MaxStartups 10:30:60
#Banner /etc/

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

Subsystem sftp /usr/lib/openssh/sftp-server

AllowUsers blood

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin yes".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM no

Match Address
    PasswordAuthentication yes
    PermitRootLogin yes
    AllowUsers blood root

Things to note:

I've gone through many iterations of devices to provide NAT for my home network over the years - in the past running:

I think I've settled on a pretty basic Linux setup though running Debian (well, Armbian - but same thing). Why? Well, Linux is for me the least hassle, while still being flexible enough to do whatever I want. I trust the packages for common software, and the hardware support is pretty fantastic, while having minimal requirements.