...making Linux just a little more fun!

<-- prev | next -->

Lock It Down With Arno's iptables-firewall

By S. Keeling

Executive Summary

This describes my experiences in transitioning from a homebuilt iptables firewall scriptii to "Arno's iptables-firewall" (AIF)v, from Arno van Amersfoort (). AIF can produce a robust iptables based firewall, even when wielded by a relative newcomer to iptables firewalls. The time between reading the documentation and dropping it into place could be as little as an hour.

Caveats

AIF is described in Arno's README as "Arno's iptables firewall - Single- & multi-homed firewall script with DSL/ADSL support". It is (C) Copyright 2001-2005 by Arno van Amersfoort. It's free software, licenced under the GNU General Public License.

If you decide you want to try using this, I strongly recommend you READ THE FAQ on Arno's website, especially if you join the mailing list in hope of support. Arno points out in there that if you haven't done so, your pleas for assistance will be cheerfully ignored until you do.

Of course, considering this is the Twenty-first Century:

  I AM NOT ADVISING YOU TO DO THIS!  IF YOU DO THIS AND IT BREAKS
  SOMETHING, YOU GET TO KEEP BOTH PIECES.  I EXPRESSLY, AND
  VIGOROUSLY, DENY ANY RESPONSIBILITY FOR ANY NEGATIVE RESULTS YOU OR
  ANYONE ELSE MAY SUFFER FROM YOUR HAVING DONE THIS.  THE FOLLOWING IS
  WRITTEN FOR ITS ENTERTAINMENT VALUE ONLY!  TELL YOUR MOTHER YOU'RE
  ABOUT TO DO THIS PRIOR TO DOING IT. 

[fscking lawyers (no, that's not a typo)... Sorry for shouting.]

When I started writing this, I was working with version 1.8.2, which is apparently over a year old. At Arno's suggestion, I've since upgraded to the latest release, version 1.8.3-RC3 (April 9, 2005). So, if I've missed anything important in updating this, that's why it may appear confused. Certain example listings were left alone, but I've tried to cover the important bits of the latest version.

Some of Arno's comments on my original article are as follows:

   Thanks for the article. I like it a lot.
   
   May I suggest to have a look at the latest 1.8.3RC3 (instead of 1.8.2a
   stable, which is over 1 year old)? It has a lot (and I mean A LOT)
   [of] improvements like:
  • 80-character long README & config file (one of your bothers). Furthermore the config file layout is improved a lot.
  • DMZ support (ie. to shield WiFi nets).
  • Multiple external (internet) interfaces.
  • Multiroute NAT (for load balancing internet connections).
  • MAC address filtering.
  • Full (transparent) proxy support.
  • The execute bit isn't set anymore on this version's config file either. ;-)
  • ...and...
  • version 1.8.4-stable, with even more improvements, will be out soon.

Nice guy, that Arno. When I started on this, I (mis)corrected his usage of "DRDOS" to "DDoS" (or "Distributed Denial of Service"xi), and that was the wrong thing to do. He actually means "Distributed Reflection Denial Of Service", something I'd never heard of. I took his advice. For me, just getting config files with eighty character long lines is a terrific improvement.

Many thanks to the helpful and patient staff at LG for all their support in producing this article, and the same to Arno van Amersfoort for having made any of it possible.

Preface

My situation is this (which is not to suggest you need to be doing something similar):

Previously, I'd used a handcrafted set of iptables commands which pretty much ruthlessly, and blindly, locked everything out through sheer brute force. Anything the firewall saw that wasn't connected to an ESTABLISHED or RELATED IP process previously initiated by my actions, was simply logged and dropped.

This is generally not difficult to do, but at times, it is. How to let NTP servers' replies back in? I struggled with that for months. Googling for help on that produced so many conflicting examples (some of which worked, some not), it was infuriating. I was always on the lookout for an alternative. I wanted something somewhat like my existing setup, but more robust, more suspicious, more dynamic, and smarter overall about networking than I am.

Over the years, I've tried things others suggested, but they always seemed a bit wrong. I don't want the thing to fire up an X Window GUI forcing me to point and click my way through the configuration. If I already knew everything there was to know about TCP/IP and networking, that might be helpful, but I don't. Besides, that's just not how I do things. I want something with few moving parts, all of which have their own home directories, and which can be expected to stay there. I don't want the thing to fail some morning because somebody changed some obscure library I'd never heard ofvi.

On "Sun Jan 9 12:18:45 MST 2005"i, I found mention of "Arno's iptables-firewall" (AIF). Now that I've finally taken some time to get it working and try it out, I'm happy to say it appears to be just what I was looking for. Arno's system is intended for far more complicated setups than my own (for instance, it supports NAT and VPNs), but it easily scales down to my needs. It thinks of an external (connected to the internet) and an internal (LAN) interface. In fact, the latest release has been updated to handle multiple external interfaces.

Installation

It's not difficult. You should be able to get the mechanics of this done in half an hour, depending on how careful you want to be. Budget an hour for the job since there's some reading to do beforehand.

Go to his download page and grab it. It's ~54 kilobytes. It'll be in the form of a tarball (gzipped tar archive). cd to somewhere safe, make a new directory to hold it, and extract it:

   cd
   mkdir ~/dwn
   cd ~/dwn
   tar xzf /path/to/arno-iptables-firewall-1.8.3-rc3.tgz

That creates a new directory, ~/dwn/arno-iptables-firewall-1.8.3-RC3:

   total 276
   drwxrwxr-x    2 keeling  keeling      4096 Apr  9 06:20 ./
   drwxr-xr-x   27 keeling  keeling      4096 Apr 18 10:50 ../
   -rw-r--r--    1 keeling  keeling     20063 Apr  9 06:20 CHANGELOG
   -rwxr-xr-x    1 keeling  keeling     13580 Apr  9 06:20 fwfilter*
   -rw-r--r--    1 keeling  keeling     18010 Apr  9 06:20 gpl_license.txt
   -rw-rw-r--    1 keeling  keeling      1467 Apr  9 06:20 iana-reserved-nets.txt
   -rw-rw-r--    1 keeling  keeling     31674 Apr  9 06:20 iptables-firewall.conf
   -rw-r--r--    1 keeling  keeling     29755 Apr  9 06:20 iptables-firewall.conf.example
   -rwxr-xr-x    1 keeling  keeling    112070 Apr  9 06:20 rc.iptables*
   -rw-r--r--    1 keeling  keeling     16887 Apr  9 06:20 README
   -rw-r--r--    1 keeling  keeling      2318 Apr  9 06:20 syslog.conf.Debian
   -rw-r--r--    1 keeling  keeling      1202 Apr  9 06:20 syslog.conf.RedHat

They're all flat ASCII text files. Now, read the README. It starts out by mentioning it's free software, licenced under the GNU General Public License. It goes on to explain what the various files are for. One of the most welcome things to me in the latest version is Arno has chopped the line length in most of his stuff to a much more readable eighty characters or so. He appears to like lines that are about a hundred characters long, and displaying them in an eighty character wide xterm (or console) made them very difficult to read. Mostly, that's no longer a problem. You'll still see remnants of this, in his actual firewall script for example.

Following the file descriptions, he lists "Some IMPORTANT (security) information". Following that is a "Quick setup" section, a section on WHAT TO DO IN THE CONFIG FILE, and some info you may need in rebuilding your kernel if it doesn't already have iptables support (:-O really?!? I haven't seen any distributions supplying pre-compiled kernels without iptables support, or maybe this is in case you're having trouble rolling your own... I'll have to go back and re-read that bit sometime).

The old version flatly refused to work if you were using a 2.2.x kernel and ipchains. The latest version detects 2.2.x and ipchains and continues (I haven't tested this).

The README used to suggest installation instructions you might use, but perhaps he's blended those into the other sections? I notice there's also a bit at the top of the firewall script itself. However, I suggest you use common sense and figure out how you need it done for your system. They're just a couple of scripts and config files. How difficult can it be? :-)

On the other hand...

What I Did - Configuration

As root:

   $ cp rc.iptables /etc/init.d
   $ vi /etc/init.d/rc.iptables

A few lines into the file, you'll see CONFIG_FILE. Change that to say:

   CONFIG_FILE=/etc/firewall/iptables-firewall.conf

Now make it executable (Arno suggests this should be 700; you decide):

   $ chmod 744 /etc/init.d/rc.iptables

If necessary:

   $ chown root:root /etc/init.d/rc.iptables

Now make a home for the config file(s):

   $ mkdir /etc/firewall
   $ chmod 755 /etc/firewall
   $ cp iptables-firewall.conf /etc/firewall
   $ chmod 600 /etc/firewall/iptables-firewall.conf
   $ vi /etc/firewall/iptables-firewall.conf

If you're going to use them, you now want to create iptables-blocked-hosts, iptables-custom-rules, and iptables-mac-addresses (make sure those are the names mentioned, down at the bottom, in the config file). I notice they're commented out in the latest version's config file. These need to be at least one blank line long, so vi them, insert a carriage return, save, and exit.

Now go back to the top of iptables-firewall.conf, inserting the important stuff telling AIF how it should do things for your system. This part gets a little hairy, so you might like to peruse the mailing list archives to see if you can find explanations of how to do this correctly. The FAQ also mentions a few little syntactical nits that it might help to know. I went with Arno's suggestion and changed as little as I could. There are lots of comments explaining what things do and when you might want to use them.

Yes, that is a little vague. Sorry, but this bit is the so-called kernel of what you need to learn about iptables for AIF to work for you the way you want it to work. What is it you want to do, and what is there that you have to work with? DSL/ADSL? Dialup ppp? WiFi from a broadband modem? What ports do you want to open up to the crackers? Do you know what you're doing with iptables, or are you a dilettante like me?

You do want to tell it what your external interface is:

   EXT_IF="ppp+"
in my case ("ppp+" covers ppp0, ppp1, ppp2, etc.) Then:
   INT_IF="eth0"
takes care of my NIC along with:
   INTERNAL_NET="192.168.1.0/24"

One of the things mentioned in the FAQ is you don't want or need "127.0.0.1" (the loopback interface) mentioned anywhere. "Ah, so I guess I don't need to do anything about my caching nameserver? Uhh..." Well, the MaraDNS man page has a good section on this. Later.

Also, you might like to take my chmod commands above with a grain of salt. I don't see a great deal of point in locking things down so group and other can't read them. However, that is how Arno suggests they be. You make up your own mind. To me, if a config file doesn't contain any sensitive information, it's a little silly to make it owner:group read-only.

What I Did - Implementation

Once you've solved the config file problem, you then need to figure out how you want it executed. If you want it to come up at boot time, you'll need init script links in /etc/rcN.d directories. Arno mentions how to do this at the top of the firewall script itself. You can do it by hand, but most distributions have some sort of tool you can use to manage those links.

For my purposes, I wanted it controlled by interface initialization, and that fits well with Debian's /etc/ppp/ip-up.d/ system. Any script in that directory will be run when the interface is brought on line and configured. There's also /etc/ppp/ip-down.d/ which works similarly in reverse.

Now, take a look in /etc/network. Just like ppp, there are if-up.d "-ish" (among others) directories in there too.

In my case, I created the script /etc/ppp/ip-up.d/00iptables_firewall. It needs only two lines: the "shebang", and a line to call rc.iptables:

   #!/bin/sh
   /etc/init.d/rc.iptables start

Now, "chmod 744 /etc/ppp/ip-up.d/00iptables_firewall". Similarly, you need a script in /etc/ppp/ip-down.d that will take the firewall down when the interface goes down:

   #!/bin/sh
   # if [ ! "$(/sbin/ifconfig | /bin/grep eth0)" ]; then
      /etc/init.d/rc.iptables stop
   # fi

Notice the commented out lines. When I started on this, that did a sanity check first: if, by chance, I happen to have both ppp0 and ethN up and connected, it would be a little silly to take down the firewall if only one of the interfaces was being taken down. However, now that I think about it, that might be a little dumb. I'm still re-thinking that bit.

Make sure that file's executable too.

The last thing you might want to do is tweak /etc/syslog.conf to tell syslogd to send firewall related messages to /var/log/firewall. In the latest version, this is optional. Arno was kind enough to include a couple of examples of syslog.conf that you can work with. I just did this: look for the first instance of "kern.*" and change that to "kern.!=debug", then add a new line that points kern.=debug at /var/log/firewall. Now, I have:

   auth,authpriv.*                 /var/log/auth.log
   *.*;auth,authpriv.none          -/var/log/syslog
   #cron.*                         /var/log/cron.log
   daemon.*                        -/var/log/daemon.log
   kern.!=debug                    -/var/log/kern.log
   lpr.*                           -/var/log/lpr.log
   mail.*                          -/var/log/mail.log
   user.*                          -/var/log/user.log
   uucp.*                          /var/log/uucp.log

   # Logging for iptables
   kern.=debug                     -/var/log/firewall

Once you change /etc/syslog.conf, you need to touch the new logfile (only do this if it doesn't already exist):

   $ touch /var/log/firewall

and you need to cycle syslogd to make it re-read its config file:

   $ kill -HUP $(pidof syslogd)

If you don't have "pidof", you'll have to determine syslogd's PID and insert that there instead. "ps fax | grep syslogd" says this:

   (0) keeling /home/keeling_ ps fax | grep syslogd
     242 ?        S      0:00 /sbin/syslogd
   30307 pts/2    S      0:00          |       \_ grep syslogd

so you need to say "kill -HUP 242"

If you've done the syslog.conf fiddle, make sure you've set "FIREWALL_LOG=/var/log/firewall" and "LOGLEVEL=debug" in /etc/firewall/iptables-firewall.conf. Hopefully, you have logrotate on your system, else the log file will just grow and grow, eventually filling your filesystem.

This is "/etc/logrotate.d/firewall":

   /var/log/firewall {
           rotate 7
           daily
           compress
           notifempty
           create 0640 root adm
           delaycompress
           create
   }

Now, you should be good to go. I would suggest the first (few?) time(s), you should run /etc/init.d/rc.iptables by hand ("chmod 644 /etc/ppp/ip-up.d/00iptables_firewall" first, bring up the connection, then run "/etc/init.d/rc.iptables start"). It spits out quite a few messages mentioning what it's doing. You should peruse that output (see the example below), ensuring it's doing what you want. This is also when you'll get a chance to do something about any errors found in the config file. "iptables -nL | less" will show the firewall rules. "tail -f /var/log/firewall" will show you the effect of those rules.

If you can't figure out how to make AIF do something you did before, Arno has thought about that and made exceptions possible. Take the iptables rule you used before and stuff it into iptables- custom-rules. Of course, be warned that this may compromise whatever AIF is attempting to do for you. However, if you were doing it before, you're probably no worse off now than you were.

In my case, when I was using the older version, I had this in my /etc/firewall/iptables-custom-rules to make chrony/NTP work:

   # man iptables says using a name that must be resolved via remote
   # DNS is a Really Bad Idea.  Sigh.
   # 
   iptables -t filter -I INPUT -s 0.pool.ntp.org -m tcp -p tcp --dport 123 -j ACCEPT
   iptables -t filter -I INPUT -s 1.pool.ntp.org -m tcp -p tcp --dport 123 -j ACCEPT
   iptables -t filter -I INPUT -s 2.pool.ntp.org -m tcp -p tcp --dport 123 -j ACCEPT

With the new version, however, I've commented those out and used "HOST_OPEN_TCP" in the config file instead:

   HOST_OPEN_TCP="0.pool.ntp.org>123 1.pool.ntp.org>123 2.pool.ntp.org>123"

AIF In Action

Take a look at what it's doing. Here's an example of rc.iptables output:

   Arno's IPTABLES Firewall Script v1.8.3-RC3
   ---------------------------------------------------------------
   Sanity checks passed...OK
   Detected IPTABLES module... Loading additional IPTABLES modules:
   All IPTABLES modules loaded!
   Setting default secure policies.
   
   External (internet) interface(s) (EXT_IF)   : ppp+
   ---------------------------------------------------------------
   Configuring /proc/.... settings
   Enabling anti-spoof with rp_filter.
   Disabling the logging of martians.
   Disabling the acception of ICMP-redirect messages.
   Setting the max. amount of simultaneous connections to 4096 (default).
   Enabling reduction of the DoS'ing ability.
   Disabling ECN (Explicit Congestion Notification).
   Using loglevel debug for syslogd.
   Flushing rules in the filter table.

   Setting up firewall rules
   -------------------------
   Accepting packets from the local loopback device.
   Enabling setting the maximum packet size via MSS.
   Enabling mangling TOS.
   Logging of INVALID packets enabled.
   Reading custom IPTABLES rules from /etc/firewall/iptables-custom-rules
   Logging of ICMP flooding enabled.
   Setting up INPUT policy for internal interface(s) eth0
   Logging of stealth scans (nmap probes etc.) enabled.
   Logging of packets with bad TCP-flags enabled.
   Logging of fragmented packets enabled.
   Logging of access from reserved addresses enabled.
   Setting up anti-spoof rules.
   Logging of DHCP broadcasts disabled.
   Logging of probable "lost connections" disabled.
   Logging of explicitly blocked hosts enabled.
   Logging of denied local output connections enabled.
   Logging of denied LAN (forward) output connections enabled.
   Packets will NOT be checked for private source addresses.
   Denying the whole world to send ICMP-requests(ping).
   Logging of dropped ICMP-request(ping) packets enabled.
   Logging of dropped other ICMP packets enabled.
   Logging of possible stealth scans enabled.
   Logging of (other) connection attempts to PRIVILEGED TCP ports enabled.
   Logging of (other) connection attempts to PRIVILEGED UDP ports enabled.
   Logging of (other) connection attempts to UNPRIVILEGED TCP ports enabled.
   Logging of (other) connection attempts to UNPRIVILEGED UDP ports enabled.
   Logging of other IP protocols (non TCP/UDP/ICMP) connection attempts enabled.
   Setting up FORWARD policy for internal interface(s) eth0:
    Allowing all TCP ports
    Allowing all UDP ports
    Allowing all IP protocols
   Security is ENFORCED for the external interface(s) in the FORWARD chain.
   (Re)loading list of BLOCKED hosts (blackhole) from /etc/firewall/iptables-blocked-hosts

   Apr 18 14:47:12 All firewall rules applied.

Woof! You should see the "iptables -nL" output! Some of this stuff, I'd only vaguely heard of. Take a gander into the rc.iptables script itself to see all that it's doing. First, the "Sanity checks", then it modprobes all the necessary kernel modules (again, some of which I'd never heard of). Arno's doing all the obscure:

  echo ${some_integer} > /proc/sys/net/blah/blah

that I've never managed to either take the time to understand or find a decent reference for. Finally, he defines various firewall rulesets.

One of the neat things about this is, instead of the fairly bland:

   Mar 16 07:24:38 localhost kernel: IN=ppp0 OUT= MAC= \
      SRC=xxx.xxx.xx.xx DST=xxx.xxx.xxx.xx LEN=48 TOS=0x00 \
      PREC=0x00 TTL=102 ID=59091 DF PROTO=TCP SPT=3946 DPT=6348 \
      WINDOW=64240 RES=0x00 SYN URGP=0

I now see things like this:

   Apr  6 12:41:06 localhost kernel: Connection attempt (UNPRIV): \
      IN=ppp0 OUT= MAC= SRC=xx.xxx.xxx.xxx DST=xxx.xxx.xxx.xx LEN=48 \
      TOS=0x00 PREC=0x00 TTL=109 ID=28712 DF PROTO=TCP SPT=4194 DPT=15118 \
      WINDOW=65535 RES=0x00 SYN URGP=0 

which is pretty slick. It explains what it is it saw. "UNPRIV" means the destination port (DPT) is higher than port 1024 (see /etc/services).

Other interesting stuff:

   Apr 18 20:22:42 localhost kernel: Possible DRDOS TCP attempt: \
      IN=ppp0 OUT= MAC= SRC=xxx.xxx.xxx.xxx DST=xxx.xxx.xxx.xx \
      LEN=576 TOS=0x00 PREC=0x00 TTL=63 ID=48819 DF PROTO=TCP SPT=110 \
      DPT=4345 WINDOW=5840 RES=0x00 ACK URGP=0 

That came in on pop3/TCP ("grep 110 /etc/services") looking for some port I don't have (4345).

Among the things you get in AIF, Arno includes "fwfilter", a shell script you can use to massage the logfile entries. I'm already running something else called fwlogwatchviii, but I decided to try this anyway. Arno supplies instructions inside the script explaining how it's to be used. I created a short script in /etc/cron.daily/fwfilter:

   #! /bin/sh
   #
   #  /etc/cron.daily/fwfilter - arno's iptables-firewall activity
   #  monitoring script.
   # 

   FWFILTER=/path/to/arno's/fwfilter
   FWLOG=/var/log/firewall
   DAY="$(/bin/date '+%b %e' --date=yesterday)"
   
   if [ -f "${FWFILTER}" -a -f "${FWLOG}" ]; then
      /bin/grep "${DAY}" ${FWLOG} | ${FWFILTER}
   fi

When the system runs its daily cron jobs, this slurps in the firewall log looking for entries from yesterday, pipes them through to Arno's fwfilter script, and the output is mailed to root. The result is quite pretty and informative. Log file entries are colorized by type. The mutt mail program can show ANSI escape sequence color with "set allow_ansi=yes", but you really ought to see the output in (eg.) rxvt or xterm.

Note, depending on your installed version of awk, you may need to fiddle with the fwfilter script prior to using it. See the variable "AWK_BIN". If "which awk" (or better, "dpkg -l | grep awk", or "rpm -qf $(which awk)") mentions "mawk", you'll need to install gawk for fwfilter to work.

D'enouement

I think it was a success. I haven't lost anything, other than a little time. All the things I could do before, I can still do now. I see log records of other, more esoteric events, that I hadn't noticed before. That's the sort of thing I do want to hear about.

I've since had the chance to drop this into the computer I'm rebuilding for a friend as well. It's worked pretty well, hasn't interfered with the two computers' ability to communicate with each other, and was easy to implement. So far, I've nothing but praise for AIF. It's just what I've been hunting for for a long time.

Arno's system is easy to use, can probably scale to fit any situation, and can do it in the hands of a relative newcomer to firewalls. If you can read, you can benefit from Arno's firewall.

If you do try this, and you like it as much as I do, take his advice in the README and donate to his favourite charities.

Rock on, Arno. Bravo. Thank you very much!


 [i] Yes, I do know exactly when it was that I ran across a mention of
    AIF.  I have a short perl script that saves, datestamps, and
    formats as htmls any cuts+pastes that I do during my surfing
    activities ( here).

[ii] My script implementing my old, hand-crafted rules is here.
[iii] The official URL for fauxident is: https://www.alcyone.com/software/fauxident/ Its author is Erik Max Francis https://www.alcyone.com/max/
[iv] MaraDNS is by Sam Trenholme https://www.samiam.org
[v] Arno's iptables-firewall: https://rocky.molphys.leidenuniv.nl/ Freshmeat homepage: https://freshmeat.net/projects/iptables-firewall/?topic_id=151 Arno's mailing list: https://lists.btito.net/mailman/listinfo/firewall
[vi] This happened to me with XCDRoast. All of a sudden, I could no longer do backups. That's why I now make my own archives with afio, use mkisofs to stuff them into an ISO image, followed by cdrecord to burn that to a CD.
[viii] fwlogwatch is from Boris Wesslowski , RUS-CERT https://cert.uni-stuttgart.de
[ix] Debian "stable", at this time (Woody), installs with a 2.2.x kernel. iptables demands 2.4 and up, and the old version of Arno's firewall did too. The latest appears to handle either.
[x] AIF thinks external interface (the Internet) and internal interface (your LAN). AIF only armours the external. It doesn't do much of anything to the internal interface other than making sure anything from it is allowed out. So, testing whether eth0 is up before taking down the firewall is likely the wrong thing to do. I've since disabled that test to verify.
[xi] Of course, everyone knows that "DR-DOS" means "Digital Research Disk Operating System" (a la Gary Kildall), right?

 


[BIO]

In 1990, after about a decade of processing seismic data (first as a computer operator, then as geophysical technician), I sat down to my first personal computer and started teaching myself how to program in C. After a couple of months at it, I decided it would make a fairly challenging and esthetically pleasing career, so I signed up for a programming course. I graduated with an A average.

Since then, work has taken me from Grande Prairie, Alberta to Khartoum, Sudan. I've worked with SunOS, Solaris, HP-UX, AIX, FreeBSD and OpenBSD. I've worked with many different distributions of Linux (SLS, Slackware, Debian (twice), Redhat, and SuSE). I generally program in Perl. I specialize in generic Unix.

Copyright © 2005, S. Keeling. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 114 of Linux Gazette, May 2005

<-- prev | next -->
Tux