Paranoid Penguin - Linux Firewalls for Everyone
The Linux kernel includes some of the most powerful and flexible firewall code in any general-purpose operating system. This code is called Netfilter, though most of us refer to it by the name of its user-space command, iptables. Netfilter/iptables allows your Linux kernel to inspect all network traffic that passes through your system, deciding what to do with that traffic based on a very rich set of criteria.
Building Linux firewalls with iptables is a big topic—entire books have been written about it (see Resources). In fact, firewall engineering is a profession unto itself (my profession, in fact). So, alas, nobody can tell you everything you need to know about building firewalls with iptables in one magazine article.
I can, however, provide an overview of the things iptables can do, some sound principles for Linux firewall design, descriptions of some handy tools for building different types of Linux firewalls and pointers to more detailed information on Linux firewalls.
Firewalling, or more precisely, packet filtering, can be used for many things. It can be used locally on individual servers and desktop systems for host-level protection from network-based attacks. It can be used at the network infrastructure level to protect entire networks from other networks, and it can be used to redirect, or even alter, network packets in various ways.
A Linux firewall can be a dedicated hardware appliance based on Linux, a PC with multiple network interfaces, or it even can be an ordinary, single-interfaced workstation or server. Many commercial firewall appliances are Linux/iptables-based. Contrary to what you might think, PC-based Linux firewalls can perform and scale quite well, if deployed on sufficiently powerful hardware.
Those are the form factors Linux firewalls take, and they serve in two different roles. Firewall appliances and multi-interface PC-based firewalls are used as what I call network firewalls. They serve as dedicated network devices, logically equivalent to IP routers that regulate traffic between different networks. (Technically, firewalls are routers; they're just fussier about what they route than ordinary routers.) Network firewalls also often perform Network Address Translation (NAT), typically to allow hosts with non-Internet-routable IP addresses to communicate with the Internet.
Then, there are what I call local firewalls—workstations or servers whose primary function isn't firewalling at all, but the need to protect themselves. In my opinion, any computer connected to the Internet, whether server or workstation, should run a local firewall policy. In the case of Linux systems, we have no excuse for not taking advantage of Linux's built-in Netfilter/iptables functionality. Furthermore, this is the easiest type of firewall script to create, as I show later in this article.
Before we discuss Linux firewall tools, we should cover some general firewall design principles. Most of these principles are (or should be) equally valid whether you're using iptables to protect a single host or entire networks.
First, here are some terms:
Packet filtering: the practice of inspecting individual network packets, comparing against a set of rules and processing them accordingly.
Firewall policy: either a specific set of iptables commands or a higher-level set of design goals that your iptables commands enforce.
Firewall rules or packet-filtering rules: the individual components of a firewall policy—that is, individual iptables command iterations.
The first step in building any set of packet-filtering rules is to decide precisely what you want your firewall to do—that is, to formulate your high-level firewall policy. For example, if I'm creating a local firewall script for a workstation, my logical policy might look like this:
Allow outbound DNS queries, Web surfing via HTTP and HTTPS, e-mail retrieval via IMAP, outbound SSH and outbound FTP transactions from the local system to the entire outside world.
Allow inbound SSH connections to this system from the other workstation in my basement.
Block everything else.
Skipping this crucial step of defining your high-level policy is akin to writing a software application without first defining requirements. You run the risk of wasting time on rules you don't need and of overlooking rules that you do need.
I further recommend that whatever policy you decide on, you make it as restrictive as is feasible. Marcus Ranum very succinctly stated the guiding principle for firewall design many years ago: “that which is not expressly permitted is forbidden”. The reason for this is quite simple; just because you can't think of how an allowed but unnecessary network transaction can't be abused, doesn't mean some attacker can't abuse it nonetheless.
Every firewall policy, therefore, must logically end with a rule that blocks everything not specified earlier.
This is true not only for network/enterprise firewall policies, but also of personal/local firewall policies. A common blunder on personal firewalls is to allow all “outbound” transactions, on the assumption that all local processes are “trusted”. If your system is infected with a worm, trojan or virus, however, this assumption breaks down.
In the event of such an infection, you probably don't want the malware to be able to use your system to send spam, participate in distributed denial-of-service attacks and so forth. Therefore, it's preferable to restrict not only “inbound” (externally originated) network transactions, but also outbound (internally/locally originated) transactions, even on the local firewall policies of desktop systems and servers.
Another important firewall design principle is, whenever possible, to group similar risks together. In other words, systems and networks with different levels of trust and different levels of exposure to risk should be isolated from each other by network firewalls.
The classic example of this principle in action is the DMZ or de-militarized zone, which is a network containing all of an organization's Internet-accessible systems. Figure 1 shows the relationship between such a DMZ: the “internal” network containing an organization's workgroups and other non-public-facing network resources and the Internet.
With firewalls separating the DMZ network from both the Internet and the internal (trusted) network, you can write rules that specify, in a very granular way, how hosts in these three zones can interact with each other. In formulating such rules, you should assume that, being exposed to a nearly infinite range of possible attackers (via the Internet), the hosts in your DMZ should be treated as semi-trusted at best—that is, you should assume that any host in the DMZ may be compromised at some point. Accordingly, you should allow as few transactions as possible to be initiated from the DMZ to the internal network.
You also should take into consideration the threat a compromised DMZ host could pose to the outside world. If an Internet-based attacker compromises your DNS server, for example, even if the attacker's attempts to hack into your internal network are blocked by firewall rules, that attacker can still cause your organization embarrassment or even legal problems if the compromised server is able to connect arbitrarily (that is, attack) to other systems on the Internet. I can't state this often or strongly enough: firewall policies should allow only the bare minimum set of network transactions necessary for your users and systems to do their jobs. Unnecessary dataflows can and will be abused, sooner or later.
You probably noticed that in Figure 1, two firewalls are used. This is the classic firewall sandwich DMZ architecture, but many organizations opt instead for a more economical multi-homed-firewall DMZ architecture (Figure 2), in which a single firewall with multiple network interfaces interconnects and restricts traffic between different networks. Although the sandwich topology provides greater protection against, for example, the external firewall itself being compromised in some way (assuming the other firewall isn't subject to the exact same vulnerability), the multi-homed-firewall approach can be equally effective, so long as you write your rules carefully enough.
Also, regardless of whether you use a single multi-homed firewall or pairs of firewalls, it's extremely important that each network zone (inside, outside/Internet and DMZ) be connected to a dedicated physical network interface on a firewall. Yes, this does make your firewall a potential single point of failure. However, if it's possible for hosts in one network zone to route packets to other network zones without traversing the firewall, your firewall will have little practical value!
The last general firewall design principle I mention for now applies only to multi-interface firewalls (that is, not to local/personal firewalls): always use anti-spoofing rules.
Consider the Internet-facing firewall in Figure 1. It has two network interfaces: inside (which faces the DMZ) and outside (which faces the Internet). Suppose that the internal network in Figure 1 uses IP addresses in the Class C network space 192.168.55.0/24, and the DMZ uses 192.168.77.0/24.
This firewall therefore can and should drop any packets arriving on its Internet interface having source IP addresses from either of those two private IP ranges. Such packets safely can be assumed to be forged (spoofed). Attackers sometimes forge the source IP addresses of their packets, attempting to pass them through firewalls or to defeat other source-IP-based authentication mechanisms (TCPwrappers, hosts.equiv and so on).
In fact, any Internet-facing network interface on any firewall should drop packets with source IP addresses from any non-Internet-routable IP range, specifically those specified in RFC 1918: 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16. (If these numbers, which are ranges of IP addresses expressed in CIDR notation, confuse you, don't panic! Some of the iptables tools discussed later in this article assume no particular networking expertise.)
To express this important firewall design principle even more generally: you should configure your firewall to drop any packet bearing an impossible source IP address.
Those are some things all firewalls should do. Now, how do we make them do those things?