Securing Small Networks with OpenBSD, Part 1

Like almost all things in life, good security costs good money. It has to be that way, because there are simply not enough skilled security specialists to look after all of the networks that need their attention. An unfortunate result of low supply and high demand is the migration of highly skilled personnel to clients who can meet their salary requirements. This leaves a lot of small and underfunded networks in the hands of less experienced administrators, who might not know how to design, configure, and monitor these networks' safety mechanisms, leaving them vulnerable to attacks from unscrupulous people looking for inside information, free warez storage, zombie hosts for DDoS attacks, or systems they can simply destroy for fun of doing it.

Fortunately, many good security products are available for free and can be implemented using commodity hardware components and free or open source software. This article describes the design and implementation of a small network with a split private/DMZ design that allows a high level of protection for its users while making some services available to the outside world. The design is easy to implement and administer, even for beginners, and can serve as a foundation for custom security installations.

Design Time

Our goal is to achieve maximum protection from attacks originating from outside of our network (insider attacks are a separate subject that I may get to in a separate article). At the same time, we do not want to spend a lot of money, which limits our options to open source or free software. This is not as bad as it sounds, because all major free operating systems contain high-quality network security software that can meet requirements of an enterprise client, let alone those of a small business or school network. Also, many of these free solutions are often incorporated into commercial products.

To keep things simple, I will assume that the network we are building will have just one connection to the Internet and that it will only have about a dozen or so internal users. Of course, you can always scale it up or down as you please, keeping in mind that you may need to use faster hardware, split the network into many smaller subnets to avoid bottlenecks, or even add more connection points to the outside world.

The design that seems to be the simplest to build and maintain uses a firewall that protects an internal private network, to which no outside connections can be made, and a so-called "demilitarized zone" (DMZ), in the form of a separate network with some services open to the outside world, also protected by the same firewall. These services might be DNS, WWW, mail, FTP, or news, but you are free to limit or expand that list as needed. The DMZ can consist of a single machine or a number of machines; it all depends on how complex you want it to be.

All traffic coming into our network will be checked and filtered by the firewall, running on a separate machine with three network interfaces:

Interface A connects the firewall to the Internet. The IP address of the interface is assigned by the ISP (in this article I'll use x.x.x.x).

Interface B connects the firewall to the internal private network. The IP address of this interface, in this article, will be 192.168.1.1.

Interface C connects the firewall to the DMZ network. The IP address of this interface, in this article, will be 192.168.2.1.

The general outline of our network will look like Figure 1.

Figure 1. A general outline of the network described in this article.

The firewall will use the following rules to manage traffic:

All packets sent from the internal private network to all legal addresses should pass through the firewall without any restrictions. (This rule could be more restrictive, if necessary.)

All packets sent from the Internet to the private network should be filtered, and only those that are valid responses to the queries originally sent from the internal private network should be allowed into the private network.

All packets sent from the Internet to the DMZ should be filtered, and only those that are destined to the services that are being made available on the Internet may pass through the firewall. Also, all valid responses to the queries originally sent from the DMZ should be allowed into the DMZ as well. (This rule could be more restrictive, if necessary.)

All packets sent from the DMZ that are valid responses to the queries originally sent from the internal private network should be allowed into the private network.

All packets sent from the DMZ to the Internet, but not the private network, should pass through the firewall without any restrictions. (This rule could be more restrictive, if necessary.)

All other traffic is discarded.

Apart from the packet filtering rules listed above, we also need a way to make services running on machines in the DMZ available to the outside world. It is perfectly possible to run all services opened to the public on the firewall machine, but by doing so we are asking for trouble, because the more services we actually run on the firewall, the "weaker" it becomes. By redirecting traffic to the DMZ, we are moving the target of a potential attack from the firewall to the DMZ and should the intruders break into these machines, they will still have to break into the firewall and the rest of the network, which considerably slows their efforts down and makes the whole network more secure. (This packet redirection mechanism is sometimes called "port forwarding.")

Because small networks will usually have a single IP address assigned by their ISP, the firewall will also need to run some sort of "masquerading" software, which makes all traffic from the internal network and the DMZ appear as if it originated from a single machine. This is an additional security measure that makes our network harder to compromise, because the IP addresses of the machines behind the firewall are never revealed to the outside world.

Free firewall products that can filter packets, redirect traffic to other hosts and perform IP address masquerading are ipfilter, ipfw, ipchains, and iptables. The first two packages are used in the BSD family of operating systems, while the last two packages are popular on the Linux platform.

Choosing Software and Hardware

The firewall software I choose to describe in this article is Daren Reed's ipfilter, running on OpenBSD 2.8. ipfilter is also available for NetBSD, Solaris, SunOS, BSD/OS, IRIX, HP-UX, and QNX. If you use other firewall software, you must remember to translate the rules presented later in this article into the syntax of that particular software package. (The general design principles will be identical in all cases.) I chose ipfilter, because a) I know it well, b) it's been thoroughly tested, c) it's the default firewall on OpenBSD, and d) I like its simple syntax. ipfilter performs stateful packet filtering; that is, it can recognize incoming packets that are replies to the queries sent by users located inside of the protected network, and lets them through without the need to write complex firewall rules. It also includes a straightforward packet redirection and masquerading module, ipnat.

As for the hardware, the machine running the ipfilter firewall software can be any computer supported by OpenBSD or another operating system for which ipfilter is available (either commercial or free), such as NetBSD, as long as it can accommodate at least three network interfaces (one to connect to the Internet, one to connect to the internal private network, and one to connect to the DMZ). My choice was a generic desktop PC with a low-end Pentium processor, equipped with a serial port and three free expansion slots on the motherboard, but it could just as well be an Alpha, Sun, VAX, or any other machine that meets the requirements listed above. In any case, the hard disk that the firewall machine will use should have a capacity of at least 540MB. It is fine to use an IDE drive instead of a SCSI device, as the disk system will not be stressed too much, as long as the machine has at least 24MB of RAM.

Other important components are the network interface cards. For connecting the internal network and the DMZ to the firewall I recommend 10/100Mb/s cards with 10BASE-T sockets for twisted-pair cables with RJ-45 plugs, as they are inexpensive new or secondhand. The rest of the equipment necessary to make everything work together includes twisted-pair cables and 10/100Mb/s hubs with 10BASE-T sockets (or one hub for the private network and one crossover twisted-pair cable, if the whole DMZ sits on a single machine).

I recommend that you use twisted-pair cables (and cards and hubs with RJ-45 sockets) instead of coaxial 10BASE2 cable, because twisted-pair installations are much more reliable, even though they require a hub. (A single failure in a coaxial installation leaves all computers connected to the same cable without access to the network, while a failure of a single twisted-pair cable disconnects only one machine.) That's it -- that's all the hardware you need, besides a Phillips screwdriver.

Now, before you raid second-hand equipment shops in your area or log on to eBay, I suggest that you carefully check the supported hardware lists posted on the OpenBSD Web site, as well as the INSTALL documents found in the top directory for the hardware platform you want to use (i386 for Intel x86 and Pentium processors). This will save you a lot of trouble during installation.

You should also download all available configuration utilities for your computer, disks, controller cards, network interface cards, and the actual chips that these cards use. I recently found a strange Ethernet card that could not be configured using the card manufacturer's own utilities. I used the card chip's manufacturer's utility instead, and it did work as expected. (Thank you, Nick!)

If you are having trouble with a hard disk, try downloading a configuration/setup utility from the disk manufacturer's Web site. If that does not help, go to the computer manufacturer's Web site and download appropriate utilities. These might only be available from the major computer manufacturer's sites; that is why it pays to buy used hardware made by companies that are still around, like IBM, Compaq, Dell, or Gateway.

The last important software tool is a bootable DOS disk or a Windows 95/98 rescue disk from which you will need to boot your computer before you can use the configuration utilities. Since I am not too sure what Microsoft thinks of using DOS or Windows boot disks to configure computers in order to install other operating systems, I suggest that you either obtain a legitimate copy of DOS or MS Windows that you will use for that purpose, buy a copy of DOS from IBM, or better still, download FreeDOS, a free DOS clone. Once you have DOS in one form or another, make a boot disk, remove all unnecessary files from it, and copy the necessary utilities onto it.

(Note that you do not need a copy of DOS or Windows to install OpenBSD itself; bootable disk images are a part of the system's distribution.)

Installation

The OpenBSD installation process is quite straightforward and you can find a good description of the whole procedure in the section 4 of the OpenBSD FAQ. Therefore, I will not go into the details of the configuration, but will focus my attention on things that may confuse first-time users:

Minimum amount of swap space. Use about twice the amount of the RAM memory you have installed in your computer, and you should be OK. If in doubt, use the partition-size values listed in section 4 of the OpenBSD FAQ.

Large disk support. When the hard disk will not boot, even though the system installation went fine, you will need to make it bootable using the hard disk or computer manufacturer's configuration utilities. Sometimes, particularly when dealing with machines with old BIOSs, your system might not correctly recognize the disk's size. That should not affect the OpenBSD installation, and you can simply make the disk bootable after you install OpenBSD on it, using utilities downloaded from the disk or computer manufacturer's Web site. If you are still having problems, read the INSTALL documents, where you will find detailed descriptions of various ways to solve problems with large disks.

Network interface address. When OpenBSD asks you during installation, it is up to you to decide which interface you want to configure; it doesn't matter too much which you choose, since we can change that configuration later on. The only thing to remember is that if you are configuring the interface to the Internet, you must use the IP address that your ISP gave you, while for networks behind a firewall you should use one of the reserved addresses for networks not connected to the Internet, like 192.168.1.x, in which the interface that connects the firewall to the rest of the network would have address of 192.168.1.1 and the interface that connects the firewall to the DMZ would have address of 192.168.2.1 (192.168.2.x is another network). You will not be able to configure the interface connected to the serial port at this time, as it requires additional software (pppd). You can complete that step later on.

IRQ conflicts. To avoid IRQ conflicts, remove all unnecessary hardware from your system; sound cards should go first, as they are of no use on a firewall; TV/radio, scanner controllers, and similar superfluous cards can go too. All you need are network cards and a SCSI controller card, if your computer is equipped with SCSI disks. Of course, you should leave the graphics card, as you will need to use a monitor, at least during installation.

When you encounter IRQ-related problems with network card devices, the devices will either be invisible to the system, will clash with other devices using the same IRQs, or will not be able to configure themselves properly during the system initialization stage. Common signs that you might be having these problems are lack of connection to other computers (the ping command followed by the IP address of another computer on the private network or the DMZ reports 100% packet loss), "device timeout" messages, or crashes during system boot or soon afterwards. These problems are easily solved using appropriate network card configuration utilities and the User Kernel Config tool.

When such mishaps happen, boot the computer from the DOS or Windows boot disk, run the card configuration utility, disable automatic configuration, set the network media type to 10BASE-T, and choose IRQ from the list of available interrupts. Repeat the process for every card in your system, exit the configuration utility, and boot the computer into OpenBSD.

If you are still having problems, you will need to configure the kernel via UKC. You can enter the UKC console in two ways: at boot time, when you see the boot> prompt (type boot -c), or from the root account (type config -e /bsd). When you see the UKC> prompt, you can search for devices, add new devices, disable or enable existing ones, and search for IRQ conflicts between them. If you find that the IRQ you chose for one of the network cards is taken by another device, you might disable that device, as long as it is not an essential part of the system; for example, I always disable the mouse device on a firewall machine, because it is not needed on a machine that does not run X Windows. Similarly, all sound devices can be disabled as well, as they are of no use on a firewall. Once you find a configuration that does work, write it down and use config -e /bsd (from the root account) to create and save a new kernel that will be used instead of the generic kernel (saving a copy of the generic kernel is a good idea too). You will find additional information in the config manual page (man config is your friend). IRQ-related problems with SCSI controllers are solved in a similar way. To learn more about UKC, read section 5 of the OpenBSD F.A.Q. The list of supported devices (http://www.openbsd.org/i386.html), and the Ethernet Adapters and SCSI Host Adapters sections in particular, will also come in handy when you are trying to figure out what is going wrong.

To check that the network cards are working, connect the firewall to the private network and the DMZ and use the ping command followed by the IP number of machines on these networks. Long delays and 100% packet loss are a sure sign of trouble.

Network media selection problems. These are solved either by configuring cards with appropriate configuration utilities or by setting the media selection options in the hostname.xxx files (they reside in the /etc directory). You can discover which interfaces you have in your system with ifconfig -a, which will display the list of all interface devices. Then read the appropriate manual page (e.g. for ne cards, read man ne) and find out which options are responsible for forcing media selection. If you are having problems identifying network devices by their names, a look at the Ethernet Adapters section of the OpenBSD Web site will help. When you find out which interface you need to configure, look for the file /etc/hostname.devicename (e.g. /etc/hostname.ne1 for device ne1) and edit it as necessary. You may need to create this file from scratch. There should be one such file for each Ethernet interface (for more information, read the manual page for hostname.if).

When the installation is finished and the system boots without problems, properly recognizing installed network cards, you can proceed to edit the following files (you must be logged in as root):

/etc/hostname.devicename -- these files store basic information that the system needs to configure network cards. There should be one such file for each card; for more information read section 5 of the the OpenBSD F.A.Q. as well as man hostname.if. A sample configuration file for an ne1 (an NE1000/NE2000 compatible) device that connects the firewall to the internal private network might look like this:

inet 192.168.1.1 0xffffff00 NONE

/etc/hosts -- the host name database for the firewall machine, this file contains a list of IP numbers and hostnames that the firewall machine "knows;" it should contain the entries for the names and addresses under which it is known on the internal private network and on the DMZ (it is perfectly possible for a single machine to be a member of many networks and be known under many different names and addresses), which in our case are 192.168.1.1 (private internal network) and 192.168.2.1 (DMZ):

The IP addresses of the firewall interfaces listed in /etc/hosts are identical to the IP addresses listed in the /etc/hostname.devicename files. If the firewall is connected to the Internet via an Ethernet card, there will be another entry in /etc/hosts with the IP address and hostname assigned by the ISP. Remember to change foo.com and bar.com to the actual domain names for these networks. These do not necessarily have to be registered domain names. I am using two different domain names for the internal private network and the DMZ, because it helps me differentiate between the two networks, but you may of course use only one domain name (or you could use more than two domain names, it's up to you).

/etc/resolv.conf -- the name resolver configuration file should contain the following entries:

Notice that I assumed that the nameservers for domains foo.com and bar.com are listening at addresses 192.168.1.2 and 192.168.2.1, but your configuration will quite probably be different and should be changed accordingly.

/etc/rc.conf -- the main system configuration file should have the following options enabled:

ipfilter=YES
ipnat=YES

After you make all of these modifications, reboot the system with sync; reboot and check if the network interfaces are functioning properly (ping requests to hosts on the private internal network or on the DMZ should report no lost packets). Remember that if the network interface connecting the firewall to the Internet is plugged into the serial port, you will need to configure the pppd daemon (read man ppp). The device name used by such interfaces is tun0. (You do not need to create /etc/hostname.tun0 for it.)

Remember that you must configure other hosts on the private internal network and in the DMZ in a way that will allow smooth communication with the firewall and other hosts on the internal private network, the DMZ, and the Internet. To do so, you should configure the TCP/IP protocol on each machine on the private internal network to use 192.168.1.1 as the gateway, and give each machine an IP address in the same address space (192.168.1.2, 192.168.1.3, 192.168.1.4, and so on). Similarly, the machines in the DMZ should be configured to use the address 192.168.2.1 as their gateway, and give each machine IP addresses from the 192.168.2.2-192.168.2.254 range. Also, every machine should be given a unique host name.

Once that is finished, you should consider running an internal DNS server on the private part of your network, to make internal communication just a bit easier. The DNS entries should list all hosts on the private network and in the DMZ. The DMZ machines will not have access to that data for security reasons (they are banned from sending any packets to the private network altogether), but machines on the private network will need it so users can easily update documents on the WWW server, read and send mail to other hosts on the Internet, and use other services located in the DMZ.

Firewall Configuration

Once you have the OpenBSD system and networking up and running, you will need to configure the firewall. The set of rules I am going to present below is rather restrictive, and many administrators may not agree with it, but I prefer to start with simple, tight rules and later on loosen them up as required.

The ipfilter can be divided into two modules: network address translator (NAT) and packet filter. The first performs masquerading (hiding internal IP addresses behind a single external IP address) and packet redirection between hosts and ports. The packet filter picks up packets modified by NAT and checks if they can be allowed to enter the networks behind the firewall. You can find a detailed explanation of the ipfilter design and rules syntax at Daren Reed's official ipfilter site. man ipf and man ipnat will provide you with plenty of additional information.

Since NAT gets to look at packets first, we'll configure it first. Network address translation and packet redirection rules are saved in the /etc/ipnet.rules file. Open this file in a text editor and enter the following rules:

These rules tell the NAT engine to map all connections from the internal private network to address "x.x.x.x" on ports from 10000-20000, and to map all connections from the DMZ to address "x.x.x.x" on ports 20001-30000. The "x.x.x.x" string should be replaced with the actual IP address assigned by your ISP. tun0 is the name of the Internet interface device. Replace it with a different netowork device name if the external interface is not using the serial port.

We can now make connections to the Internet from the internal private network and from the DMZ, but hosts outside of our network cannot access the services in the DMZ. This is solved by redirection. A sample set of rules for redirecting HTTP traffic is shown below:

Again, "x.x.x.x" is the IP address of the exernal interface that connects the firewall to the Internet. 19.2.168.2.254 is the IP address of the hosts that runs the HTTP server in the DMZ. For security reasons, the server listens for requests at an unprivileged port 8080. Note that there must be a rule that redirects packets sent to port 80 at "x.x.x.x" for every interface, or some packets will not arrive at the right destination.

To redirect another kind of service, you must copy the rules for HTTP requests changing the port numbers; for example, to redirect mail, add the following rules to /etc/ipnat.rules:

Note that I used a different IP address on which the SMTP daemon runs, but it could just as well be running on the same machine that the HTTP server runs on.

Once we have our NAT rules in place, we can proceed to add the packet filtering rules. These are stored in the /etc/ipf.rules file. Open it in a text editor and add the following two lines at the beginning:

pass out quick on lo0 all
pass in quick on lo0 all

lo0 is the loopback interface, which we'll leave alone, as trying to block traffic here would be a waste of processor cycles that could be better used elsewhere. The quick keyword instructs the firewall to immediately stop processing other rules as soon as the packet matching this rule arrives; this saves a considerable amount of CPU cycles and simplifies rule writing. There is not much we can add here, so we can proceed to the tun0 interface rules, where we need to block all outgoing traffic destined for illegal addresses (this technique is used by some crackers and we certainly do not want to help them):

block out quick on tun0 from any to 192.168.0.0/16
block out quick on tun0 from any to 172.16.0.0/12
block out quick on tun0 from any to 127.0.0.0/8
block out quick on tun0 from any to 10.0.0.0/8
block out quick on tun0 from any to 0.0.0.0/8
block out quick on tun0 from any to 169.254.0.0/16
block out quick on tun0 from any to 192.0.2.0/24
block out quick on tun0 from any to 204.152.64.0/23
block out quick on tun0 from any to 224.0.0.0/3

Having stopped dangerous packets from polluting the Internet, we should add rules that let the legitimate traffic through. The following three rules will allow all traffic from legal addresses on the private internal network to be sent to the Internet:

pass out quick on tun0 proto tcp from 192.168.1.0/24 to any keep state
pass out quick on tun0 proto udp from 192.168.1.0/24 to any keep state
pass out quick on tun0 proto icmp from 192.168.1.0/24 to any keep state

The keep state keywords tell ipfilter to "remember" connection state, so packets sent back to the hosts originating the connection can pass through the firewall. The proto keyword defines the protocol to which a particular rule will apply.

We also want to allow all traffic from legal addresses in the DMZ to the Internet; all we have to do is copy the rules for the internal private network and replace the network address (192.168.2.0/24 instead of 192.168.1.0/24 -- the /24 is the netmask):

pass out quick on tun0 proto tcp from 192.168.2.0/24 to any keep state
pass out quick on tun0 proto udp from 192.168.2.0/24 to any keep state
pass out quick on tun0 proto icmp from 192.168.2.0/24 to any keep state

All other traffic that tries to reach the Internet through tun0 is blocked as a safety precaution:

block out quick on tun0 all

We now have a set of rules that define which packets can leave our network and try to reach other hosts on the Internet. In the next step, we will add rules that block unwanted packets sent from other hosts on the Internet to our network.

As before, we need to drop all packets sent from illegal addresses, as they will almost always be sent by people with bad intentions. This is done with the following set of rules:

block in quick on tun0 from 192.168.0.0/16 to any
block in quick on tun0 from 172.16.0.0/12 to any
block in quick on tun0 from 10.0.0.0/8 to any
block in quick on tun0 from 127.0.0.0/8 to any
block in quick on tun0 from 0.0.0.0/8 to any
block in quick on tun0 from 169.254.0.0/16 to any
block in quick on tun0 from 192.0.2.0/24 to any
block in quick on tun0 from 204.152.64.0/23 to any
block in quick on tun0 from 224.0.0.0/3 to any
block in log quick on tun0 from x.x.x.x/32 to any
block in log quick on tun0 from any to x.x.x.0/32
block in log quick on tun0 from any to x.x.x.255/32

The log keyword tells ipfilter to log packets that match a particular rule.

When all illegal packets have been dropped, we can check if there are any packets destined at ports that we made open to the public and let them through. The following rule checks for packets sent to port 80, the place where we can usually find some sort of HTTP server listening for requests from HTTP clients.

pass in quick on tun0 proto tcp/udp from any to x.x.x.x/32 port = 80 keep state
pass in quick on tun0 proto tcp/udp from any to 192.168.2.254/32 port = 8080 keep state

Note that the address/port pairs used in ipf.rules must match address/port pairs used in ipnat.rules. A set of rules that allows connection to the SMTP server in the DMZ is shown below:

pass in quick on tun0 proto tcp/udp from any to x.x.x.x/32 port = 25 keep state
pass in quick on tun0 proto tcp/udp from any to 192.168.2.253/32 port = 25 keep state

All other packets sent to the external interface from the Internet will be discarded.

block in quick on tun0 all

Our external interface is protected quite well now, and we can focus our efforts on the interface to the internal private network and the DMZ. Assuming that the private internal network is connected to the interface ne1, we can write the following rule that blocks all packets trying to reach hosts on the internal private network:

block out quick on ne1 all

This rule cuts off access to the DNS nameserver for the internal private network listed in the /etc/resolv.conf file mentioned earlier in this article. You can enable acess to the nameserver with

pass out quick on ne1 proto tcp from 192.168.1.1 to 192.168.1.2/32 port = 53 keep state
pass out quick on ne1 proto udp from 192.168.1.1 to 192.168.1.2/32 port = 53 keep state

Better still, instead of using internal nameservers, use external nameservers, in which case the /etc/resolv.conf would look like this ("y.y.y.y" and "z.z.z.z" are IP addresses of external nameservers, which you should be given by your ISP):

lookup file bind
nameserver y.y.y.y
nameserver z.z.z.z

It is worth remembering, that "out" rules define filtering policy for packets leaving the firewall and entering some network (in this case, the internal private network), and "in" rules define filtering policy for packets sent from networks to the firewall. This has been known to cause a few problems to novices.

The rules that defines which packets sent from the internal private network can enter the firewall are similar, though not identical, to the "in" rules for tun0:

block in quick on ne1 from 172.16.0.0/12 to any
block in quick on ne1 from 10.0.0.0/8 to any
block in quick on ne1 from 127.0.0.0/8 to any
block in quick on ne1 from 0.0.0.0/8 to any
block in quick on ne1 from 169.254.0.0/16 to any
block in quick on ne1 from 192.0.2.0/24 to any
block in quick on ne1 from 204.152.64.0/23 to any
block in quick on ne1 from 224.0.0.0/3 to any
block in log quick on ne1 from x.x.x.x/32 to any
block in log quick on ne1 from any to x.x.x.0/32
block in log quick on ne1 from any to x.x.x.255/32
pass in quick on ne1 proto tcp from 192.168.1.0/24 to any keep state
pass in quick on ne1 proto udp from 192.168.1.0/24 to any keep state
pass in quick on ne1 proto icmp from 192.168.1.0/24 to any keep state
block in quick on ne1 all

Unlike the internal private network, the DMZ is partially open to the outside world. Therefore, before we drop all packets trying to reach it, we will need to let some through. The first set of rules will let all traffic from the internal private network through without any restrictions:

pass out quick on ne2 proto tcp from 192.168.1.0/24 to 192.168.2.0/24 keep state
pass out quick on ne2 proto udp from 192.168.1.0/24 to 192.168.2.0/24 keep state
pass out quick on ne2 proto icmp from 192.168.1.0/24 to 192.168.2.0/24 keep state

Next, we'll block packets sent to illegal addresses:

block out quick on ne2 from any to 192.168.0.0/16
block out quick on ne2 from any to 172.16.0.0/12
block out quick on ne2 from any to 127.0.0.0/8
block out quick on ne2 from any to 10.0.0.0/8
block out quick on ne2 from any to 0.0.0.0/8
block out quick on ne2 from any to 169.254.0.0/16
block out quick on ne2 from any to 192.0.2.0/24
block out quick on ne2 from any to 204.152.64.0/23
block out quick on ne2 from any to 224.0.0.0/3

And finally, we'll let packets through that arrive from the outside and are destined for publicly-open addresses:

pass out quick on ne2 proto tcp from any to 192.168.2.254/32 port = 8080 keep state
pass out quick on ne2 proto udp from any to 192.168.2.254/32 port = 8080 keep state
pass out quick on ne2 proto tcp from any to 192.168.2.253/32 port = 25 keep state
pass out quick on ne2 proto udp from any to 192.168.2.253/32 port = 25 keep state

All other packets are blocked for safety reasons.

block out quick on ne2 all

As for packets sent from the DMZ to the Internet, we let all packets sent from illegal addresses through:

block in quick on ne2 from 172.16.0.0/12 to any
block in quick on ne2 from 10.0.0.0/8 to any
block in quick on ne2 from 127.0.0.0/8 to any
block in quick on ne2 from 0.0.0.0/8 to any
block in quick on ne2 from 169.254.0.0/16 to any
block in quick on ne2 from 192.0.2.0/24 to any
block in quick on ne2 from 204.152.64.0/23 to any
block in quick on ne2 from 224.0.0.0/3 to any
block in log quick on ne2 from x.x.x.x/32 to any
block in log quick on ne2 from any to x.x.x.0/32
block in log quick on ne2 from any to x.x.x.255/32
pass in quick on ne2 proto tcp from 192.168.2.0/24 to any keep state
pass in quick on ne2 proto udp from 192.168.2.0/24 to any keep state
pass in quick on ne2 proto icmp from 192.168.2.0/24 to any keep state
block in quick on ne2 all

The rules we have now are quite restrictive and but also quite secure. You can enable them with the following commands:

ipf -A -Fa -f /etc/ipf.rules -E
ipnat -FC -f /etc/ipnat.rules

Our network will be quite immune to attacks from the outside, at the price of some inconvenience:

We cannot use traceroute -- the solution is to relax our rules. Read the ipfilter HOW-TO file for more information on how you can accomplish that feat. It will be a good exercise in configuring firewalls.

pings and connection attempts from the outside to our network report 100% packet loss -- this is a problem when you want to monitor the firewall from the outside by sending pings at regular time intervals, to see if the machine is running. You can replace pings with connections to public ports.

Should you want to open additional services to the outside world, remember to add appropriate entries in the /etc/ipnat.rules and /etc/ipf.rules.

Final Thoughts

What services you make available to the public is up to you. The most obvious candidate for running in the DMZ is the HTTP server. Another popular option is to have an email server and the DNS server for virtual domain hosting.

There are many solutions available on the market, but I suggest that you stick with what you know best, or, if you do not know how to manage a particular piece of software, my advice would be to start with simple things. For example, if you do not know how to configure and manage DNS, you can use the /etc/hosts files until you learn more about DNS. If you do need DNS for virtual domain hosting, use external DNS servers. You can rent a DNS server for as low as $5 per month per domain, or maybe you can ask your ISP to host these domains on their nameserver for you.

If you want to run a mail server, consider postfix, a free sendmail replacement that is much easier to configure and manage and enjoys a reputation of being much more secure than sendmail.

As for the HTTP server software, if you have never tried running a Web server, consider thttpd instead of the Apache Web server. thttpd is smaller, easier to learn and manage, and teaches some good administration habits. And if you really want to run a publicly-accessible namesever, try djbdns. Learn various configuration options for the servers you want to make publicly available and find out how to run them in the chroot jail, which greatly increases security of the system.

And finally, learn as much about network security, firewalls, TCP/IP management as you can. The design I presented in this article strikes a nice balance between security and inconvenience, but you should not assume it is without flaws, nor that it is the most secure design ever devised. It is only a beginning: you are supposed to improve it and adapt to your own needs. Read all available manuals, HOW-TOs, and tutorials. When in doubt, ask older/more experienced administrators and learn from them.

Jacek Artymiak
started his adventure with computers in 1986 with Sinclair ZX Spectrum. He's been using various commercial and Open Source Unix systems since 1991. Today, Jacek runs devGuide.net, writes and teaches about Open Source software and security, and tries to make things happen.