How to Build a FreeBSD-STABLE Firewall with IPFILTER

Applicable to: FreeBSD 5.3 (but will work with remainder of 5.X baseline)
Updated: Jan 17, 2005
Author: Marty Schlacter
Source URL:

This howto walks you through the process of building one of the most stable and secure firewalls available...a FreeBSD-STABLE firewall with IPFILTER. As a part of the installation process, all services will be disabled except OpenSSH, which will have its access controlled via TCP-Wrappers. The firewall will be configured to log through the syslog facility, but will have its own firewall log files (rather than filling up /var/log/messages). We'll also add VESA support into the kernel so that we can use 132x43 screen resolutions. After we add a warning banner to the system, we'll make BASH the default shell for root, perform a rudimentary setup for root's BASH environment, and redirect root's email to your "normal user" account so that the root account on the firewall itself doesn't fill up. Next, we'll install and configure AIDE (Advanced Intrusion Detection Environment) [a tripwire replacement...since tripwire will not compile on FreeBSD 5.X] as well as install cvsup so that your ports collection and source tree stays up to date. And, lastly, we'll modify the /etc/fstab entries so that some of your partitions are mounted 'nosuid', 'noexec', or 'ro' so that your installation is as secure as possible.

This is an all-encompassing how-to, and should take most of a complete day to complete, but when you're finished, you'll not only have a great firewall, but will be better able to compare and contrast FreeBSD/IPFILTER to Linux/IPTABLES or OpenBSD/PF so that you can consider the pros/cons of each on their merits...and that learning process is what all of this about anyway. So, grab a cup of coffee, sit down with that old unused computer, and get ready to broaden your horizons.

Before we start, I'd like to thank Dan O'Connor for the work he put in on his great site, FreeBSD Cheat Sheets, since it was his great site that gave me the motivation to start this howto. You will undoubtedly see some of his tips and tricks sprinkled throughout this document. For those of you that are new to FreeBSD, I highly recommend his site. His site is no longer being updated, due to changing priorities in his life, but the info on his site is still very applicable (more or less) to newer versions of FreeBSD.

In addition, there have been several other people on the Internet who have given me great suggestions & and feedback on this HOWTO. The majority (if not all) of their comments have been incporporated into this document in some form or another. There are too many to list here by name, but (rest assured) the Open Source community has helped to make this the best document it can be.

And, as always, before performing this procedure, I highly recommend that you review the Installing FreeBSD chapter of the FreeBSD Handbook.

Network Schematic & System Configuration

The intent of this document is to show you how to build a firewall for your home network. Just to make sure that we're "working off the same sheet of music" here's a quick ASCII-schematic of what our notional home network will look include device names for the Ethernet interfaces. In addition, I'm including a quick synopsis of the configuration of my own that you can use it as a reference point throughout this procedure.

       Notional Network Schematic      Machine Configuration
       --------------------------      ---------------------

            ISP / Internet             - AMD Athlon XP 2000+ CPU
             (UNTRUSTED)               - 768MB SDRAM (PC133)
                  |                    - 15GB hard drive
                  |                    - Microsoft Intellimouse (PS/2 2-button w/ scroll wheel)
              ---------                - Matrox Millenium G400 video card (32MB)
              | Cable |                - PCI Ethernet cards (two) (generic)
              | Modem |                - CD-ROM drive
              rl0 |
           | xx.xx.xx.xx |
           |             |
           |   FreeBSD   |
           |   Firewall  |
           |             |
           | |
              rl1 |
           | 10/100BaseT |
           |   switch    |
              | | | | |
              | | | | |
          Internal Network

Installing FreeBSD

To build the most stable and security-patched system you can, you'll want to make sure you're running the latest version of FreeBSD-STABLE. For those of you new to FreeBSD, the STABLE branch is the version of the operating system that has all of the latest patches, bugfixes, and enhancements after the previous release was made. In fact, there's actually two different versions of the STABLE that has all of the patches, bugfixes, and enhancements, and a second that only has the bugfixes and patches (no enhancements). The second version is usually more stable than the first, but not always so. For a production firewall, you'll probably want to install the 2nd version of STABLE (without the enhancements), but it's ultimately your call.

If you've installed FreeBSD-5.3 from CD-ROM (either one that your purchased or 'burned' from a downloaded ISO image), you probably installed 5.3-RELEASE, which is (simplistically) nothing more than a version of the 5.X branch that was exhaustively tested, burned to CD-ROM and made available for sale. After the release date of 5.3-RELEASE, the 5.3 tree continued to evolve & be patched (for security reasons) after that point. Since there's no way the folks at can burn & sell CD-ROMs for each day's version of the 5.3 tree, 5.3-RELEASE is the only one made available for sale on CD, and subsequent snapshots of the 5.3 tree are only available on-line and are labelled '5.3-STABLE'. Once 5.3-STABLE is sufficiently enhanced/patched (perhaps 6 months later), the code enters a freeze and will officially become the 'RELEASE' version of the next FreeBSD release (say, 5.4-RELEASE). If you're installing FreeBSD 5.3 well after the release date, you will definitely want to install 5.3-RELEASE, and then immediately update your kernel and binaries to 5.3-STABLE.

So, what are the benefits of upgrading to 5.3-STABLE rather than staying with 5.3-RELEASE? Well, the biggest answer (if you're building a firewall, like we are here) is that all of the security patches have been applied to the O/S and the associated applications. To use a prior baseline (and much older version) of FreeBSD (4.2) as an example, FreeBSD-4.2-RELEASE (which was released in November 2000) uses OpenSSH-2.2.0, which is a great product but also has a remote buffer overflow that wasn't discovered until early February, 2001. If a hacker exploited this vulnerability on your 4.2-RELEASE box, they would gain remote root access and ruin your day. The relevant info on this vulnerability can be found on SecurityFocus' website. When you upgraded to FreeBSD-4.2-STABLE (if you were following this HOWTO a long time ago...say, mid-March of 2001), by comparison, you would have gotten FreeBSD-4.2-RELEASE with all of the patches applied after the November 2000 your system would have OpenSSH-2.3.0 (not OpenSSH-2.2.0) which is not vulnerable to the remote buffer overflow. So upgrading to the latest snapshot from the STABLE branch saves you a lot of time associated with loading individual security-related patches after your OS load is finished. For a complete listing of security-related patches, see the FreeBSD Security Information page.

OK, now that we've talked about the benefits of FreeBSD-STABLE, let's get to work...the installation...

  1. Inventory your computer hardware and ensure that it is compatible with FreeBSD. The latest compatibility list (for the 5.3 baseline) can be found in the FreeBSD 5.3 Hardware Notes.

  2. Verify that you have at least 1.1G available on your hard drive. After the initial install of FreeBSD (the first section of this document), you will have taken up about 350M. After downloading the latest kernel sources, and updating your ports tree, you will have taken up about 650M (depending on the number of ports sections you wish to keep up to date). And, finally, after you finish installing & compiling AIDE and recompiling the kernel, you will have taken up about 1.1G. Which directories are the biggest disk space hogs? /usr/obj (& sub-directories) takes up about 377MB. /usr/src (& sub-directories) takes up about 350MB. /usr/ports (& sub-directores) takes up about 160MB. All other directories take up less than 90MB apiece.

  3. Download the ISO image:

    1. FTP to

    2. Change directory into /pub/FreeBSD/releases/i386/ISO-IMAGES/5.3

    3. Download the 5.3-RELEASE-i386-disc1.iso image.

  4. Burn the ISO image to CD using your favorite CD utility.

  5. On the FreeBSD machine, insert the CD and boot from it.

  6. When the FreeBSD bootloader appears, hit 'Enter' to select the default (Boot FreeBSD)

  7. From the main menu, choose a 'Standard' installation.

  8. In the FDISK Partition Editor, first 'D' delete any disk slices that already exist, then choose 'A' to use the entire disk. This will let FreeBSD take the entire disk and eliminate the need for a bootloader. Press 'Q' to continue.

  9. Now, you will now be presented with the Install Boot Manager for drive... screen. Select 'Standard' to install a standard MBR (no boot manager). After all, you won't be dual-booting this's your firewall. Therefore, you won't need a boot loader.

  10. In the Disklabel Editor, create the following partitions (below), then choose 'Q' to continue...only after you're done. Note that I'm using a 15GB hard drive. You can adjust the sizes of the partitions based on the size of your drive. The /usr/local and /usr/home partitions can go as low as 128MB since this won't be a common-user system and there won't be a lot of user-specific files or binaries...but the /usr partition should never go below 1,000MB since that's where all of your kernel source code and ports tree is located. Here's a partition scheme if you have a 15GB drive:
    1,536MB swap partition (or at least 2x your RAM...I have 768MB)
    256MB file system mounted as /
    1,024MB file system mounted as /tmp
    1,024MB file system mounted as /var
    3,072MB file system mounted as /usr
    1,024MB file system mounted as /usr/local
    6,657MB file system mounted as /usr/home (...the remainder of the hard drive)
  11. Choose "Kern-Developer" as the Distribution you want to install by highlighting it and pressing the 'space' bar. Remember, this is going to become a gateway/firewall system, and you'll need the kernel source code to re-compile/update the kernel...and you don't need (or want) X Windows running on it.

  12. Select "Yes" to install the FreeBSD ports collection.

  13. Arrow back up to "<<< X Exit" and hit the 'space' bar to exit the Distribution Menu

  14. Select CD/DVD as your source.

  15. At the "Last Chance" warning, select "yes".

    (System Installs...depending on the speed of your CD-ROM drive and other system components, this procedure takes 6-8 minutes.)

  16. Miscellaneous configuration:

    1. Would you like to configure any Ethernet or SLIP/PPP network devices? Yes

      1. Select your Ethernet card to configure (e.g. "rl0" in my case).

      2. Select "no" for IPv6 config

      3. Select "yes" for DHCP configuration if your network card is directly connected to your cable modem, etc.  Select "no" if you're on a pre-existing network, then enter your interface configuration information manually - host name, domain name, IPv4 gateway IP address, name server IP address, IPv4 address, and netmask.

    2. Do you want this machine to function as a gateway? Yes

    3. Do you want to configure inetd and simple internet services? No

    4. Would you like to enable SSH login? Yes

    5. Do you want to have anonymous FTP access to this machine? No

    6. Do you want to configure this machine as an NFS Server: No

    7. Do you want to configure this machine as an NFS Client: No

    8. Select "No" when asked to modify the system console configuration.

    9. Select "Yes" when asked "Would you like to set this machine's time zone now?" Then, select "No" when asked if your machine's CMOS clock is set to UTC.  Then select the appropriate time zone - by region, country, and then the applicable time zone.

    10. Select "No" when asked if you'd like to install Linux Binary support.

    11. Select "Yes" when asked if your system has a PS/2, serial, or bus mouse attached to it (unless, of course, it doesn't...)

    12. Make the following configuration changes for the mouse configuration, then enable it & test it, then select "Exit" to return to the previous menu. Note that I have a 2-button PS/2 mouse - that's why I'm using PS/2 and 3-button emulation: 
      Type: Auto
      Port: PS/2
      Flags: -3
    13. When asked to browse the FreeBSD packages collection, select "Yes", and then install the following packages. Note that these package preferences are just my own personal preferences. If you're a firewall 'purist' (which means you take a more minimalistic approach when configuring firewalls - for security reasons) then the only package you'll need to install is cvsup (so that you can get the latest copy of the source & ports, etc.) If you're like me, I like using lynx to access the web, mutt to read email, and bash as my shell. Even though I don't use the firewall as a common-user machine, I consider those three programs "necessities" for me. Your usage patterns will vary. Regardless of what my own preferences are, please substitute, add, or delete as you see's your firewall after all...
      WWW - lynx-2.8.5
      Mail - mutt-
      Net - cvsup-16.1
      Shells - bash-2.0.5b.007_2

      Then tab over and select "Install", select "OK" to confirm your choices

      (Packages are installed...takes about 60 seconds)

    14. Select "Yes" when asked if you want to add any additional user accounts. Since this is a firewall, not a common user machine, we won't need many, but you will need at least one. The main reason we're adding at least one other user account is so that we can set up SSH so that it does not allow remote root logins. Instead, you must SSH to the firewall as the user, and then 'su' to root.

    15. Select "User - Add a new user to the system" on the User and group management dialog box. Then enter the login id, password, and full name. Make sure you put a '0' in the member groups box. This will put your new user in the 'wheel' group so that they can 'su' to root. Also put /usr/local/bin/bash in for their default shell. When finished, select 'OK', and then 'X - Exit'

    16. Set the 'root' password: ******

    17. When asked if you'd like to visit the General Configuration menu to set any last options, select "Yes" and configure the following options:
        - Enable "ntpdate - Select a clock-synchronization server" ... then select a server near you

      Then select Exit and return to the previous menu, and then tab over and select "Exit Install"

    18. Select OK when asked if you're sure you want to exit the install & reboot the system. Remove your CD and your system will reboot.

      (System reboots...)

    19. As the system reboots for the first time, it will ask you to type in a screenful of junk to "set the entropy source". Do so...literally, type in a screenful of junk, and then hit the 'Enter' key. You'll only have to do this once.

Upgrading to -STABLE & Configuring the System

Now that you have FreeBSD-RELEASE installed on the system, we need to spend a few hours upgrading to FreeBSD-STABLE as well as finishing the rest of the configuration. Here's what we're going to do in this section (in no particular order):

  • Configure cvsup and update your source tree & ports collection
  • Upgrade to FreeBSD-STABLE
  • Configure IPFILTER, IPNAT, and IPMON. Specificaly, IPMON will be configured so that it logs to syslog, but then we'll modify syslog so that the firewall messages get their own file. We'll also update newsyslog so that the firewall's logs get rotated
  • Install and configure AIDE
  • Compile VESA support into the kernel and change our screen resolution is 132x43
  • Configure syslogd so that it won't accept connections from other machines (i.e. prevent it from being a 'listening' service)
  • Configure a 2nd Ethernet interface
  • Configure TCP-Wrappers so that access to SSH is locked down to your local network only
  • Configure SSH so that it will only accept SSH sessions from IPv4 systems and rejects connections from users it doesn't have the DSA key for
  • Add a warning banner
  • Make BASH the default shell for 'root' & configure root's BASH environment
  • Redirect root's email to your "normal" account so that it doesn't back up on the firewall.
  • Modify the /etc/fstab so that some of the partitions are mounted 'nosuid', 'noexec', or 'ro' to lock the system down even further.
  • Increase the kernel's security level to "2" (Extreme)

In order to save time, I'm going to do some steps in what will appear to be an "out of order" sequence. This is being done on purpose so that we will minimize the number of re-boots you'll have to do. In fact, the goal is to configure the system, then recompile the kernel & system binaries, and when the system reboots, you're done. That's it.

  1. Log in as your non-priveleged user account. If your login was successful, you should be presented with a 'bash-2.05b$' prompt...indicating that bash was successfully installed. After you log in, then type 'su' to switch user to root. Enter the root password.

  2. Make "bash" the default shell for 'root' and perform an initial set up of root's bash environment.

    1. Use FreeBSD's password file manipulation utility, vipw, to modify root's default shell. At a root prompt, type vipw. A copy of the /etc/passwd file will be displayed. Use standard vi editing commands to change root's default shell from /bin/csh (all of the way at the end of the first line) to /usr/local/bin/bash. While you're already editing the file, go ahead and change root's unofficial name 'Charlie &' to 'Super-User' or any other name. When you get mail from root (e.g. from the cron jobs that run every night), it'll now be maked as coming from 'Super-User' and not 'Charlie &'...just a little bit nicer. Save & exit.

    2. Verify that your manipulation of the password file was successful. Go over to your 2nd virtual terminal by hitting <Alt>-F2. When you're at the 2nd virtual terminal, log in as root. After successfully logged in, verify that you're presented with the 'bash-2.05b#' prompt. If it's successful, then log out and return to the 1st virtual terminal to continue working. If it's not successful, then you need to go back to the previous step and figure out what you did wrong. Remember that bash is working because you logged in as your user account. You must have typed in something wrong, or accidentally removed a ':' (colon), etc. Go back to the first virtual terminal, type 'vipw' and re-edit the password file to fix your mistake.

    3. Create a .bashrc file in root's home directory (/root) and enter the following items (as a starting point).  After the file has been created, chmod 600 on it so that it's only readable & writable by root. Then copy it to your user's home directory (cp /root/.bashrc /usr/home/username/.bashrc). And, lastly, do a chown on the file in your user's directory so that they own the file (not root), by doing a 'chown username:groupname /usr/home/username/.bashrc' (and substitute username & groupname for something appropriate based on the user you created).
      umask 077
      PS1="[\u@\h \W]\\$ "
      alias ls='ls -alFG'
    4. Create a .bash_profile file in root's home directory and enter the following items (as a starting point).  After the file has been created, chmod 600 on it so that it's only readable & writable by root. And, just as in the previous step, copy your new .bash_profile to your user's home directory and change the owner on it so that the user owns it (not root).
      PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin:$HOME/bin; export PATH
      umask 077
      PS1="[\u@\h \W]\\$ "
      alias ls='ls -alFG'
    5. Test your settings by going over your 2nd virtual terminal by hitting <Alt>-F2, then logging in as root. Verify that you're using the bash shell, your cursor line looks different (i.e. it has your userid & current working directory), and that you get colorized directory listings. Close out that session and return to your first virtual termial, log out, and then log back in...and then 'su' to root.

  3. Redirect root's email to your "normal" email account so that it doesn't get backed up the firewall

    1. Use vi to open the /etc/aliases file for editing.

    2. Modify line that says "# root: me@my.domain" by removing the "#" comment at the beginning of the line, and then modifying the "me@my.domain" email address so that it points to your "normal" email address instead. You can either point it to your new user account (so that the email stays on the machine & can be accessed without su'ing to root), or redirect it to your 'normal' email account in the office (so that you don't even have to SSH out to the firewall to see how it's doing each day).

    3. After saving & exiting, then run the command "newaliases" from the command prompt to update the email alias database.

  4. Create & install a warning banner.  Use vi to replace your /etc/motd file with the following text (or some other equivalent legal disclaimer). Make sure that you add a line that says 'update_motd="NO"' at the end of your /etc/rc.conf file when you're done...otherwise your changes will be overwritten each time the system reboots.
    * * * * * * * * * * * * W A R N I N G * * * * * * * * * * * * *
    * * * * * * * * * * * * W A R N I N G * * * * * * * * * * * * *
  5. Copy your warning banner over to your /etc/issue file. This will make the warning banner visible at the console before the login that people consent to monitoring before they even try to log in:
    [root@numa /root]# cp /etc/motd /etc/issue
  6. Configure cvsup and update your source tree & ports collection.

    Note #1: After you configure cvsup and update your source and ports collection, you will want to re-run cvsup every once in a while to ensure your sources & ports collection is up-to-date (in case you want to install any new software). Then, you'll want to recompile your kernel & system binaries to ensure you are using the latest versions with security patches applied.

    Note #2: We are only updating sections of the ports tree that would be "normal" for a firewall. (i.e. we are not updating the ports collection for games, X-windows, etc.). This will save disk space by not wasting it on ports you won't be installing on a firewall. I'll make the assumption that since you installed packages from the WWW, Mail, Net, and Shells section of the packages collection during the installation (i.e. when you installed lynx, etc.), you'll want the same sections of the ports collection kept up to date. In addition, we'll add to other areas with tools that might be useful on a firewall - the "security" and "sysutils" areas. Add whichever areas you want, but be aware that the more you add...the more hard disk space you'll "eat up." To get a list of which sections of the ports collection are available, do a 'more /usr/share/examples/cvsup/ports-supfile' and browse through the listings of individual ports collection names.

    [root@numa /root]# cp /usr/share/examples/cvsup/stable-supfile /etc
    [root@numa /root]# vi /etc/stable-supfile
    - Run the ":set num" command in vi so that you can see the line numbers on each line of the file.
    - Change line 68 of the file so that it points cvsup to a CVS server near you. Section A.5.7. (CVSup Sites) of the FreeBSD Handbook will tell you where the CVSup servers are.
    - On line 73, modify the "tag" variable to correspond to the specific release of the O/S that you want to track. The default value of the tag in the example file is "RELENG_5". This will download the source code for the O/S which will has all of the security updates as well as general bugfixes and feature enhancements. If, however, you're in a production environment and can't afford even the slightest risk of feature enhancements causing problems with your production configuration, there's a different value for this tag that's just for you. In this case, set the tag to "RELENG_5_3". This has ONLY the security feature it's arguably the more stable version of the 5.3-STABLE branch. 95% of sysadmin's should change the tag to "RELENG_5_3" to track the security-related "5.3-STABLE" baseline and not mess with new enhancements which might impact the system's stability. It's your's your call... The official information about tag was disseminated via the FreeBSD Security Advisories mailing list on 11 May 2001 (message subject, "Changes to FreeBSD security support policy").
    - Add these lines at the bottom of the file:
        ports-www tag=.
        ports-mail tag=.
        ports-net tag=.
        ports-shells tag=.
    - ...and other lines for ports collections you want...
    [root@numa /root]# cvsup /etc/stable-supfile

    (Source tree is synchronized with CVS server...should take 30-60 minutes...)

  7. Configure the SSH daemon and your user's DSA key files.

    1. Modify the SSH daemon configuration file, /etc/ssh/sshd_config, so that it reads as follows. The modified lines are in bold red text.
      Port 22
      Protocol 2
      ListenAddress    *** Put your internal interface's address here ***
      #ListenAddress ::    *** Delete this line ***

      # HostKey for protocol version 1
      #HostKey /etc/ssh/ssh_host_key
      # HostKeys for protocol version 2
      #HostKey /etc/ssh/ssh_host_dsa_key

      # Lifetime and size of ephemeral version 1 server key
      #KeyRegenerationInterval 1h
      #ServerKeyBits 768

      # Logging
      #obsoletes QuietMode and FascistLogging
      #SyslogFacility AUTH
      LogLevel VERBOSE

      # Authentication:

      #LoginGraceTime 2m
      PermitRootLogin no
      StrictModes yes

      RSAAuthentication yes
      PubkeyAuthentication yes
      AuthorizedKeysFile .ssh/authorized_keys

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

      # Change to yes to enable built-in password authentication.
      PasswordAuthentication no
      PermitEmptyPasswords no

      # Change to no to disable PAM authentication
      ChallengeResponseAuthentication no

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

      # GSSAPI options
      #GSSAPIAuthentication no
      #GSSAPICleanupCredentials yes

      # Set this to 'no' to disable PAM authentication (via challenge-response)
      # and session processing.
      UsePAM no

      AllowTcpForwarding no
      GatewayPorts no
      X11Forwarding no
      #X11DisplayOffset 10
      #X11UseLocalhost yes
      PrintMotd yes
      PrintLastLog yes
      #TCPKeepAlive yes
      #UseLogin no
      #UsePrivilegeSeparation yes
      #PermitUserEnvironment no
      #Compression yes
      #ClientAliveInterval 0
      #ClientAliveCountMax 3
      #UseDNS yes
      #PidFile /var/run/
      #MaxStartups 10

      # no default banner path
      Banner /etc/motd

      # override default of no subsystems
      Subsystem sftp /usr/libexec/sftp-server
      AllowUsers newuser    *** Substitute 'newuser' with your userid ***
    2. Generate an SSH key (version 2) for your user, by performing the following steps:
      [root@numa /root]# su - testuser          *** substitute your non-privileged userid for 'testuser'
      [testuser@numa testuser]$ ssh-keygen -d   *** then accept the default DSA key name & enter a passphrase (twice)
    3. Add the public copy of your user's version 2 key to their own authorized_keys file by typing the following steps:
      [testuser@numa testuser]$ cd .ssh
      [testuser@numa .ssh]$ cat > authorized_keys
    4. By whatever means you choose (floppy, etc.), copy your user's private & public keys to other systems that you'll be using to SSH to your new firewall from. By default, the private & public key go into a user's '.ssh' directory on those systems. Without the private key on those remote systems, your firewall will not accept connections from them. If you're new to FreeBSD and need to know how to access the floppy drive, follow the following steps.
      [root@numa root]# mkdir /mnt/floppy                   *** This will make an empty mount point to mount the floppy to ***
      [root@numa root]# mount -t msdos /dev/fd0 /mnt/floppy *** Insert a DOS-formatted floppy before you do this ***
      [root@numa root]# cd /mnt/floppy
      [root@numa floppy]# cp /home/testuser/.ssh/id_dsa* .  *** Copies all of your user's ssh key info to the floppy
      [root@numa floppy]# ls                                *** List the contents of the floppy to verify the files are there
      [root@numa floppy]# cd ..
      [root@numa mnt]# umount /mnt/floppy                   *** Unmount the floppy
    5. Now that you've copied your user's private & public keys to another system, remove them from your user's .ssh directory on the firewall. This is only a precaution so that it can't be stolen by a hacker and compromised. If you haven't copied it yet (and plan to do it later), then skip this step until after you've done so.

    6. Open up your /etc/hosts.allow file, delete all of the lines, and ensure that it reads as follows.  Note that is the address space of your internal network in this example.  If you're using a different internal address space (e.g., then make the appropriate modifications. 
      # hosts.allow access control file for "tcp wrapped" applications.
      ALL : localhost : allow
      sshd : : allow
      ALL : ALL : deny

      # If you want to allow a specific computer on the Internet to SSH into your
      # system, replace the 'sshd' line above with one like this...but subsitute
      # the X.X.X.X and subnet mask to suit your needs (e.g. one computer, entire subnet
      # etc.). Also, make sure you allow inbound SSH from that same host/subnet
      # in your /etc/ipf.rules file.
      # sshd : X.X.X.X/ : allow
  8. Install and configure AIDE (Advanced Intrusion Detection Environment)

    1. First, install AIDE:
      [root@numa /root]# cd /usr/ports/security/aide
      [root@numa aide]# make install clean
    2. Copy the sample AIDE configuration file, then initialize the database. Note: The sample AIDE configuration file is fairly complete and protects all the directories that have setuid root binaries (in addition to a lot of additional "stuff"), but you may want to modify it based on your preferences. For the sake of this HOWTO document, we will use it "as is".
      [root@numa aide]# cp /usr/local/etc/aide.conf.sample /var/db/aide/aide.conf
      [root@numa aide]# cd /var/db/aide
      [root@numa aide]# aide --init
      [root@numa aide]# mv databases/ databases/aide.db
    3. Create a cron job to check the integrity of your system every day at 4AM:
      [root@numa /root]# cd /etc
      [root@numa /etc]# vi crontab
      - Add the following line to the file:
         0   4   *   *   *    root   /usr/local/bin/aide --check
    4. To make the system even more secure, you may want to consider moving your aide.db file off the firewall entirely and not checking for system integrity automatically at 4AM. Instead, when you want to verify system integrity, bring it back over to the firewall (via floppy, etc), and then manually copy the file and run "aide --check" against the manually-copied file. After it's complete, remove aide.db from your firewall.

  9. Edit your /etc/rc.conf file so that it's ready for the screen resolution changes and the IPFILTER modifications we'll make in a few minutes.

    1. Add the following lines at the bottom of the file to support 132x43 screen resolution (after we compile VESA support into the kernel, below).
    2. Add the following line at the bottom of the file so that syslog won't log to remote machines, nor will it accept logs from remote machines. This still allows syslog to function, but stops it from being a 'listening' service.
    3. Add the following line at the bottom of the file so that SSHD only listens for IPv4 addresses
    4. Add the following lines at the bottom of the file so that IPFILTER, IPNAT, and IPMON will start up on reboot (after we create the appropriate files, below. The options for ipmon perform the following - D causes it to run as a daemon, s tells it to use the syslog logging facility, v tells it to log the tcp window, ack and sequence fields, and n tells it to map the IP addresses and port numbers back to hostnames and service names (you may or may not want to use the "n" flag...depends on how much traffic your firewall processes, and/or whether you like to see the packet logs in their raw/unresolved state...your call).
    5. Modify the following line so that your 2nd network card is a valid network interface. Sometimes, this line may not be present in the /etc/rc.conf file. If it's not, then add it. Your device names might be different depending on the type of network card you're using.
      network_interfaces="rl0 rl1 lo0"
    6. Add the following line so that your new 2nd network card is configured correctly (IP address, etc). Again, we're assuming that you're using as the internal network (per the diagram at the beginning of this document):
      ifconfig_rl1="inet netmask"
    7. Ensure the following two lines are present. If they're not, then add them. Again, per the diagram at the beginning of this document, rl0 is your DHCP interface connected to your ISP. And lo0 (as always) is your loopback address).
    8. Reduce your vulnerability to Denial of Service (DoS) or hijack attacks by dropping ICMP redirect packets. Add the following line to the bottom of the file:
  10. Create a separate logfile for our firewall logs. Then edit the newsyslog configuration file so that your new logfiles are rotated properly.

    1. Create a new file for the firewall logs with the following commands:
      [root@numa /root]# touch /var/log/firewall_logs
      [root@numa /root]# chmod 600 /var/log/firewall_logs
    2. Modify your syslog configuration file (/etc/syslog.conf) so that the IPFILTER logged events (logged with IPMON) are sent to your new separate firewall log file. Insert the following line at the top of the file:
      security.*     /var/log/firewall_logs
      Then, add the entry "security.none" to the line for /var/log/messages. Add it to the middle of the line, separated from the other entries with a semi-colon. This will ensure that the firewall log entries don't end up in /var/log/messages...they'll only go to your firewall log, configured above.

    3. Modify your newsyslog configuration file (/etc/newsyslog.conf) so that your new firewall log files get rotated just like the primary syslog file (/var/log/messages). Add the following new line to the bottom of the file:
      /var/log/firewall_logs    600   14   100   *   J   /var/run/
  11. Create your IPFILTER and IPNAT rulesets

    1. Using vi, create a new IPFILTER firewall ruleset, /etc/ipf.rules, & add the following lines to it. Note: The assumption is that rl0 is the "outside" interface (i.e. connected to your ISP), and rl1 is the "inside" interface (i.e. connected to your internal network).  Also note that we're not performing egress filtering here.  We're blocking all inbound packets from the internet and allowing all internal network packets out (and keeping state on them so that they're allowed back in).  After your box is configured to your liking, I heavily recommend implementing egress filtering.

      For those new to egress filtering...all it means is that you only allow out of your network traffic that you explicitly want to let out. For example, you'd change the line that allows unrestricted outbound tcp traffic (the first rule in the ruleset) into 5 or more different rules. One which allows outbound traffic as long as it's going to port 80 (http). The second allows outbound traffic as long as it's going to port 25 (smtp), etc. Add as many rules as you need to define the outbound traffic that you're allowing. Then, add a rule before all of these that blocks all outbound traffic to broadcast addresses (i.e. anything that ends with a x.x.x.255). And you'd add another rule that blocks all outbound traffic if the source address isn't on the network or the IP address of your rl0 interface (the one that's connected to your ISP). So, you'd be blocking all packets that aren't coming from your network or your own system. In other words, you know that your users will only need to go out to web sites, send mail, etc. And they'll never need to send broadcast packets out to the Internet, etc. And they better not be spoofing their source IP address. This is only a sample of what egress filtering is all about. Good (read as 'restrictive') egress filtering can be quite complex, but is in the best interest of the Internet because it doesn't allow your box (in the off-chance that it does get hacked) to be used maliciously for things like "smurf" attacks and other broadcast amplification attacks (where your system sends out broadcast packets to a target network to get as many systems to respond as possible...which eats up their bandwidth). In addition, it lets you know if you have any systems on the inside of your network that are trying to access the internet over unauthorized protocols & services (read as "misbehaving users...").

      And as a final note, since we're using IPFILTER's stateful packet inspection abilities, we don't need to reject traffic spoofing non-routable or reserved addresses...they'll be blocked automatically since they don't match a corresponding packet in the state table. If you do allow certain services into your firewall (say, SSH access from the Internet so that you can manage the firewall remotely), then you'll have to add these filters in. To do so, block all incoming traffic on your rl0 interface that claims to have a source IP address of,, or any of the other reserved addresses, etc.

      Use this IPFILTER ruleset as a starting point. After you have everything running, add in whatever you want (egress filtering, protection from non-routable addresses, IP spoofing protection, etc.) to complete the job. This is only a starting point.

      Note: Remember to modify the bold red text (below) so that it matches the IP address of your ISP's DHCP server
      # Outside Interface

      # Allow out all TCP, UDP, and ICMP traffic & keep state on it
      # so that it's allowed back in.
      # If you wanted to do egress's where you'd do it.
      # You'd change the lines below so that rather than allowing out any
      # arbitrary TCP connection, it would only allow out mail, pop3, and http
      # connections (for example). So, the first line, below, would be
      # replaced with:
      #    pass out quick on rl0 proto tcp from any to any port = 25 keep state
      #    pass out quick on rl0 proto tcp from any to any port = 110 keep state
      #    pass out quick on rl0 proto tcp from any to any port = 80 keep state
      # ...and then do the same for the remaining lines so that you allow
      # only specified protocols/ports 'out' of your network
      pass out quick on rl0 proto tcp from any to any keep state
      pass out quick on rl0 proto udp from any to any keep state
      pass out quick on rl0 proto icmp from any to any keep state
      block out quick on rl0 all

      # Block all inbound traffic from non-routable or reserved address spaces
      block in log quick on rl0 from to any  #RFC 1918 private IP
      block in log quick on rl0 from to any   #RFC 1918 private IP
      block in log quick on rl0 from to any      #RFC 1918 private IP
      block in log quick on rl0 from to any     #loopback
      block in log quick on rl0 from to any       #loopback
      block in log quick on rl0 from to any  #DHCP auto-config
      block in log quick on rl0 from to any    #reserved for doc's
      block in log quick on rl0 from to any #Sun cluster interconnect
      block in quick on rl0 from to any         #Class D & E multicast

      # Allow bootp traffic in from your ISP's DHCP server only.
      pass in quick on rl0 proto udp from X.X.X.X/32 to any port = 68 keep state

      # If you wanted to set up a web server or mail server on your box
      # (which is outside the scope of this howto), or allow another system
      # on the Internet to externally SSH into your firewall, you'd want to
      # uncomment the following lines and modify as appropriate. If you
      # have other services running that you need to allow external access
      # to, just add more lines using these as examples.
      # If the services are on a box on your internal network (rather than
      # the firewall itself), you'll have to add both the filter listed below,
      # plus a redirect rule in your /etc/ipnat.rules file.
      # pass in quick on rl0 proto tcp from any to any port = 80 flags S keep state keep frags
      # pass in quick on rl0 proto tcp from any to any port = 25 flags S keep state keep frags
      # pass in quick on rl0 proto tcp from X.X.X.X/32 to any port = 22 flags S keep state keep frags

      # Block and log all remaining traffic coming into the firewall
      # - Block TCP with a RST (to make it appear as if the service
      # isn't listening)
      # - Block UDP with an ICMP Port Unreachable (to make it appear
      # as if the service isn't listening)
      # - Block all remaining traffic the good 'ol fashioned way
      block return-rst in log quick on rl0 proto tcp from any to any
      block return-icmp-as-dest(port-unr) in log quick on rl0 proto udp from any to any
      block in log quick on rl0 all

      # Inside Interface

      # Allow out all TCP, UDP, and ICMP traffic & keep state
      pass out quick on rl1 proto tcp from any to any keep state
      pass out quick on rl1 proto udp from any to any keep state
      pass out quick on rl1 proto icmp from any to any keep state
      block out quick on rl1 all

      # Allow in all TCP, UDP, and ICMP traffic & keep state
      pass in quick on rl1 proto tcp from any to any keep state
      pass in quick on rl1 proto udp from any to any keep state
      pass in quick on rl1 proto icmp from any to any keep state
      block in quick on rl1 all

      # Loopback Interface

      # Allow everything to/from your loopback interface so you
      # can ping yourself (e.g. ping localhost)
      pass in quick on lo0 all
      pass out quick on lo0 all
    2. Using vi, create a new IPNAT translation ruleset, /etc/ipnat.rules, & add the following lines to it.
      # Do 'normal' IP address translation. This line will take all packets
      # going out on your external NIC (rl0) that have a source address coming
      # from your internal network (, and translate it to whatever
      # IP address your external NIC happens to have at that time
      map rl0 -> 0/32

      # If you have a system on your internal network that needs to be
      # 'reachable' by external systems on the internet, you'll need a rule
      # similar to the one below. This one takes all inbound http traffic
      # (TCP port 80) that hits the firewall's external interface (rl0) and
      # redirects it to port 80 on the system on the internal network.
      # Simply uncomment the rule, change the IP address and port number so that
      # it does what you need. Remember that you have to enable the corresponding
      # inbound filter in your /etc/ipf.rules file, too.
      # rdr rl0 port 80 -> port 80 tcp
  12. Add "blackhole" support and restrict crontab access/usage

    1. Add "blackhole" support to make your system harder to port-scan. Edit your /etc/sysctl.conf file and add the following 2 lines to the end of the file. This will make it much harder for people to port-scan your system by not sending back telltale RST packets to closed TCP ports on your system (for example). By setting the TCP blackhole parameter to "2" it will drop unsolicited packets of any type (SYN, FIN, etc.) to closed ports. Setting the UDP blackhole parameter to "1" will perform the same function for UDP (since UDP isn't connection oriented).
    2. Create the file /var/cron/allow and add the following lines to it. Be sure to substitute 'newuser' for whatever your non-privileged user account is. This will only allow the root user and your non-privileged user account to schedule jobs with cron.
    3. Edit /etc/crontab and remove the 'at' job that runs every 5 minutes. The 'at' command is run commands at a later time (i.e. type in the command now, and it'll run 20 minutes later, etc.) If you don't use 'at' command (and chances are, you don't), then you don't need it running every 5 minutes. Hackers might want to use it to schedule a job to help give them access to your system or download rootkits, etc. So, edit your /etc/crontab file, and place a '#' at the beginning of the following line (i.e. comment it out).
      */5 * * * * root /usr/libexec/atrun
    4. Chmod your /etc/crontab file so that it is only readable by root. There's no need for other users to know what jobs are scheduled to run & when. It's a firewall, not a common-use workstation.
      [root@numa /etc]# chmod 600 /etc/crontab
  13. Re-compile your kernel & system binaries (i.e. add IPFILTER support, upgrade to -STABLE, add VESA support, and add support for a 2nd ISA Ethernet card)

    1. Change directory into /usr/src/sys/i386/conf
      cd /usr/src/sys/i386/conf
    2. Copy the file GENERIC to a new file - typically named after your hostname (I'll assume that your hostname is "FIREWALL")
    3. Using vi, edit your new file, FIREWALL, and make the following changes:

      1. In line 2 of the file (part of the main comment block) change the word, GENERIC, to your hostname, FIREWALL.

      2. On line 19 of the file (still part of the main comment block), change the word, GENERIC, to your hostname, FIREWALL

      3. On lines 22-24, comment out the "cpu" lines so that only the one for your specific chip is left. For an Athlon XP, I commented out all of them except line 24 - cpu "I686_CPU"

      4. On line 25, change the value of the ident parameter so that it's your hostname, FIREWALL

      5. At about line 135 (immediately following the line "device vga0"), add the following line to add support for VESA video modes (for 132x43 resolution):
        options VESA
      6. At about line 140 (immediately following the line "device sc"), also add the following line to add support for VESA video modes:
        options SC_PIXEL_MODE
    4. After saving your kernel configuration file, FIREWALL, type the following commands in this order to compile your kernel (assuming your new kernal name is "FIREWALL"), as well as all system binaries, and install them. With my AMD Athlon XP 2000+ with 768MB of SDRAM, the "make buildworld" command took about 1 hour since it rebuilds all of the system binaries; the "make buildkernel" command took 20 minutes; and the "make installkernel" command took about 1 minute.
      [root@numa /]# cd /usr/src
      [root@numa src]# echo "KERNCONF=FIREWALL" >> /etc/make.conf
      [root@numa src]# make buildworld
      [root@numa src]# make buildkernel
      [root@numa src]# make installkernel
  14. Next, edit your /etc/rc.conf file with vi and change the kernel security level to "2" (which is the 'Extreme' setting we mentioned all of the way at the beginning of this HOWTO) by doing the following:
    - Add a line that reads 'kern_securelevel_enable="YES"'
    - Add a line beneath it that reads 'kern_securelevel="2"'
  15. Lastly, modify the /etc/fstab file with vi so that we can change how each partition is ensure that hackers can do at little as possible if they (by chance alone) hack the box. Essentially, we're restricting some of the partitions so that they are 'nosuid', 'noexec', and 'ro'. The original /etc/fstab should look something like this. Yours might look a little different...the first column (device names) might be a little different, but that's OK. The stuff we'll be modifying is in the 4th column.
    # Device     Mountpoint   FStype   Options   Dump   Pass#
    /dev/ad0s1b  none         swap     sw        0      0
    /dev/ad0s1a  /            ufs      rw        1      1
    /dev/ad0s1d  /tmp         ufs      rw        2      2
    /dev/ad0s1f  /usr         ufs      rw        2      2
    /dev/ad0s1h  /usr/home    ufs      rw        2      2
    /dev/ad0s1g  /usr/local   ufs      rw        2      2
    /dev/ad0s1e  /var         ufs      rw        2      2
    /dev/acd0    /cdrom       cd9660   ro,noauto 0      0
    First, copy the original /etc/fstab file to /etc/fstab.original

    Then, make another copy of the /etc/fstab file and call it /etc/fstab.restrictive

    Then, modify the /etc/fstab.restrictive file so that it reads as follows:
    # Device     Mountpoint   FStype   Options                 Dump   Pass#
    /dev/ad0s1b  none         swap     sw                      0      0
    /dev/ad0s1a  /            ufs      rw,nosuid               1      1
    /dev/ad0s1d  /tmp         ufs      rw,noexec,nosuid,nodev  2      2
    /dev/ad0s1f  /usr         ufs      ro                      2      2
    /dev/ad0s1h  /usr/home    ufs      rw,noexec,nosuid        2      2
    /dev/ad0s1g  /usr/local   ufs      ro,nosuid               2      2
    /dev/ad0s1e  /var         ufs      rw,noexec,nosuid        2      2
    /dev/acd0    /cdrom       cd9660   ro,noauto               0      0
    Next, copy your new /etc/fstab.restrictive file and over-write the original /etc/ that your "real" fstab file has the restrictive settings, and you have the two other config files available (the original and restrictive one).
    [root@numa etc]# cp /etc/fstab.restrictive /etc/fstab
    Note that this will make adding new software, etc. much more difficult since /usr and /usr/local are mounted read-only. This means that programs which try to install their user-land programs in /usr/local/bin will fail during their install programs. And cvsup...which will try to update the kernel's source code in /usr/src and the ports in /usr/ports...well, they're now read-only because they fall under /usr. So, mounting your partitions in a very restrictive way is a double-edged sword. It limits what the hacker can do on your system, but it makes software installs and kernel upgrades more difficult (or impossible...if the partitions are still mounted in a restrictive way).

    Given that, if you want to add new software or upgrade the kernel & ports tree source code, you'll need to
    1. Change the partition's mounting in /etc/fstab back to their original values by copying your /etc/fstab.original file to /etc/fstab.
    2. Bump the kernel security level back down to "1" by setting the kern_securelevel paramater in your /etc/rc.conf file, and then
    3. Reboot the machine
    4. Update your sources with cvsup, then make buildworld, make buildkernel, and make installworld

    Then when you're done upgrading, recompiling, and installing, do the steps in reverse:
    1. Change the partition's mounting in /etc/fstab to their restrictive values by copying your /etc/fstab.restrictive file to /etc/fstab.
    2. Bump the kernel security level back up to "2" by setting the kern_securelevel paramater in your /etc/rc.conf file, and then
    3. Reboot the machine

    This may sound like a pain...I know. But this is your firewall, not a desktop workstation. This is the price you pay for a VERY, VERY secure machine. If you want an even more secure machine than this, then you can start setting the immutable flag on files in the filesystem by using the chflags command with the schg flag and remove the gcc compiler, etc...but those additional lock-down measures would be a whole separate howto in-and-of-itself. For now, though, you shouldn't need to manipulate immutable flags.

  16. Reboot the machine so we can finish the job...
    [root@numa /etc]# shutdown -r now
  17. If the system doesn't reboot, it means that you probably made an error in the kernel configuration file...possibly setting the wrong type of CPU. DON'T PANIC. We can still boot the machine so that you can fix the error. To boot into the original version of the kernel, following the steps, below:

    1. Reboot the machine (power off, then on)

    2. When you reboot the machine and get to the bootloader screen, select option 6, "Escape to loader prompt". This will give you an "OK" prompt at the bottom of the screen. Type these commands to boot from your "old" kernel:
      OK unload
      OK boot /boot/kernel.old/kernel
    3. After the old kernel boots, you'll want to copy the "old" kernel to a safe place before you recompile a new kernel. This is an important thing to do since "kernel.old" is overwritten when you install a new kernel. To do this, type the following commands:
      [root@numa /]# cp -R /boot/kernel.old /boot/kernel.good
      If subsequent kernel compiles still don't work, you can always manually boot off your good kernel from the "OK" prompt until you resolve the problem...just substitute "kernel.good" for "kernel.old" in the commands listed above.

    4. Now that you've saved a copy of your "good" kernel, modify your kernel configuration file and fix whatever was causing the problems, recompile & install, and then reboot and continue with the next step.

  18. After the system comes back up, you'll want to re-generate the AIDE database and replace the old one. Since you updated the kernal and all of the system binaries, the AIDE database signatures of those files is out of date. If you don't update the AIDE database, AIDE will find thousands of "changes" to the system binaries when it runs for the first time at 4AM in the morning. To update the database so that it has signatures for the newest kernel & system binaries, etc, just type the following commands:
    [root@numa /]# cd /var/db/aide
    [root@numa aide]# aide --init
    [root@numa aide]# mv databases/ databases/aide.db
    After you do should have a completely working firewall...enjoy!