If you can read this, you might want to skip straight to the content. Also, kindly take a moment to read my rant about Web design—especially if you’re wondering why this site looks a bit…dull.

Network Design

Approacing a Secure and Practical Network

by Ben Goren

Introduction

Network security is hard. Even if you’re only connecting a single computer to the Internet with a traditional dial-up account, there are significant security considerations to worry about. And when you consider corporate networks with scores of servers and thousands of workstations….

Lots of people have written lots of things on network security. The concepts I present here are not Earth-shattering; indeed, they’re significantly influenced by the excellent book, Building Internet Firewalls by D. Brent Chapman and Elizabeth D. Zwicky. (My copy is the first edition; I’m sure the second edition is even better.) I don’t want to give the impression that this paper is in any way a thorough treatment of the subject; rather, it is a description of a small but complete network that represents my current views on the best practices to observe. Along the way, I discuss the reasoning behind decisions made and possible variations.

I say, “current views,” because security is not a product; rather, it is a process. Computer science is evolving; security practices must evolve to reflect that evolution. New tools and techniques will emerge; others now in use will be rendered obsolete. Fortunately, this process is relatively slow: it’s unlikely that there’ll be anything other than minor refinements made to this paper over the next year or two. A decade from now, though…all bets are off.

Not only is security not a product, it isn’t a single product. “Firewalls” are often sold as a magic turnkey solution to making computers and networks secure. First, the term, “firewall,” is a bit fuzzy, meaning different things to different marketdroids. But, more importantly, there are lots of security threats that the best possible firewall, even when properly configured, can’t hope to save a vulnerable computer that’s supposed to be protected. A firewall is a first line of defence, much like your skin. Your skin does an excellent job at protecting you from all kinds of harm, but it won’t do you any good against food poisoning or a bullet; firewalls have similar limitations—as do all tools in the network administrator’s toolchest.

Security lies in the careful analasis of threats and the proper application of available tools to protect against them. The analasys is ongoing; it can never end.

Perfect security is as unobtainable as perfect health. Only the dead need not guard against injury and disease, and only they are invulnerable to them.

The Network

Road Map

Throughout this paper I’ll be referring to the following diagram:

The Network

You might find it useful to have it handy in a new window; many browsers will let you accomplish that by using the right mouse button. If not, scroll, baby, scroll!

Legend

Boxes represent servers. Computer names that start with “U” are untrusted for some reason. Those that start with “T” are, by contrast, more trusted than not. A server whose name starts with “P” is a proxy server.

Desktop computers (there aren’t enough elements whose names start with “W” else I’d call them workstations) have names that start with “C” and are represented as hexagons.

The ovals represent hubs, switches, modem banks, or any other doohickey that allows you to connect a bunch of computers together in a single logical network. I used the word “hub” because it’s only three letters, even though most hubs are being replaced with switches for various reasons.

Directory

Each straight line represents a network cable. One end plugs into a hub; the other into a network interface. There are no IP addresses associated with the hubs, but there is one with most of the network interfaces. Here’s a table of IP addresses and what services can be found on them.

InterfaceAddressServices
Bridging Firewallnone
Platinum192.0.2.1Various proxies
Titanium192.0.2.2SMTP, DNS, SSH
Tungsten192.0.2.3HTTP{,S}, SSH
Uranium192.0.2.65HTTP{,S}, FTP, SSH
Ununnilium192.0.2.66H.323
Routing Firewall, 192.0.2 network192.0.2.254IPSEC
Routing Firewall, 169.254.0 network169.254.0.254none
Tin169.254.0.1SMTP, DNS, DHCP, SSH
Tellurium169.254.0.2SSH and various file, database, etc.
CarbonAssigned by DHCPnone
CopperAssigned by DHCPnone
CoboltAssigned by DHCPnone
Routing Firewall, 169.254.1 network169.254.1.254none
Tantalum169.254.1.1DHCP, SSH and various file, database, etc.
CesiumAssigned by DHCPnone
CadmiumAssigned by DHCPnone
CalciumAssigned by DHCPnone

The ISP has provided us with the Class C block, 192.0.2.0; everything that should be reachable by the outside world will have an address in the range 192.0.2.1 - 192.0.2.254.

General Principles

One of the most fundamental concepts in designing a secure network—one concept which won’t change—is that of a layered defense in depth. If an attacker manages to penetrate one mechanism, there should be another, different mechanism in place to stop further advances. If the barbarians make it past the archers, they still have to get past the moat, over the castle walls, through the boiling oil, and past the King’s personal guard; each requires a different, relatively unrelated skill or technique. It’s no coincidence that the path from the Internet to any given workstation is a long and convoluted one.

Closely tied to a layered defense is compartmentalization. As much as possible, a given machine on the network should do one thing, and do it well: you shouldn’t be reading email on your public Web server. When economics dictates consolidation of functions, similar functions should be grouped together as much as possible. Hosting a Web site and an anonymous FTP server on the same computer probably isn’t a bad idea, especially if they’re serving the same repository of files.

A computer connected to a hub can snoop on the communications of any other computer also connected to that hub. Ideally, everything critical will be encrypted wherever it goes; still, it’s a good idea to keep physically separate, as much as possible, friendly and unfriendly computers. If your Web server is on the same network as your workstations, and the Web server gets compromised, the attacker can get an awful lot of potentially sensitive information just by eavsdropping on network communications.

Of course, a network that is impervious to attack is useless if legitimate activities are blocked or significantly hindered. Not only should normal, authorized useage be permitted to pass freely, it should be sped up and enhanced wherever possible.

The Pieces

I’ll now take you on a guided tour of the network. Each piece has a function to serve and a role to play.

Bridging Firewall

Let’s start with the bridging firewall—and start here with a few definitions. A bridge is used to transparently join two or more networks into one single large one. It is, in essence, an expensive extension cable. A router is a computer with two or more network interfaces, each connected to a different network, that copies traffic from one network to another. The main distinction between a bridge and a router is that a router has a reachable address on each network for which it routes whereas a bridge has no reachable addresses.

By “firewall,’ I mean a stateful packet filter. If you don’t know what a packet is, take a moment to read through my introduction to Internet protocols. A packet filter examines packets that arrive on one of its interfaces and makes a rule-based decision as to whether or not it should forward it through another interface to its destination. Due to the mechanics of IP and the protocols it transports, it’s not possible to examine a solitary packet in the middle of a transaction and determine whether or not it’s legitimate; a stateful packet filter will examine packets in the overall context of already-established connections.

Somewhere upstream of the bridging firewall there must be a default router to the Internet. Such routers come in such a variety of flavors that I won’t attempt to describe here what it might be. I will note that many such routers have fiewalling capabilities of their own, some excellent, many quite substandard. While it’s not a bad idea to take a belt-and-suspenders approach and use the router’s firewall to its full potential, I wouldn’t recommend relying solely upon it unless that firewall truly is excellent. Even then, the belt-and-suspenders approach might dictate the supplemental use of a bridging firewall. All computers with public IP addresses will use the default router as their…well…default router.

A bridging firewall has some distinct advantages over a routing firewall. Because it requires no IP addresses of its own, it can very easily be inserted into any existing network: simply replace the single cable which needs to be filtered with a pair of cables and the firewall. No configuration outside the firewall is necessary; no other computer need be aware that the firewall exists. Further, because it has no IP addresses, it presents an extremely low profile to attackers. Indeed, only a very skilled attacker with access to a computer on a physical network directly connected to the firewall even has a chance of communicating directly with the firewall. To a garden-variety attacker, the firewall itself is purely invisible, though hints as to its presence can be detected by the fact that certain packets simply never reach their destination.

The purpose of the bridging firewall is not so much to permit or deny traffic to and from your computers as it is to provide a platform on which you can build such restrictions. It’s important to understand that a packet filter only looks at the packet headers (envelopes). Since any packet can carry any kind of payload (contents), and the packet filter never looks at the payload, the briding firewall is incapable of making decisions based upon the actual information being communicated.

Some people mistakenly use a packet filter to, for example, try to restrict workstations to only surfing the Web. They do this by only permitting outbound connections on TCP ports 80 (HTTP) and 443 (HTTPS). Circumventing such a restriction is trivial, as pretty much any type of server can be set up to listen on any port. An employee could, for example, tell his chat server at home to listen on TCP port 80, tell his chat client to connect to his home computer on TCP port 80, and suddenly he’s blown right past your firewall.

If you’re going to permit access to one port on a computer over which you have no control, you might as well permit access to all ports on that computer and save yourself from the delusion that you’re blocking anything.

That’s not to say that packet filters are useless—far from it. It does mean that you should look at your filtering policies with a critical eye to ensure that the rules actually have some sort of meaning.

So, what do you actually do with the bridging firewall? To start, you don’t let anything through. It’s nearly always to start with a “default deny” stance in which everthing is blocked except for that which is explicitely permitted.

Next, you’ll start opening up access to selected ports on selected computers—but, before doing so, let me tell you about the rest of the players.

Platinum, the Proxy Server

A proxy server is a computer that accepts request for information and then carries out those requests on behalf of the requestor. In normal operation, a Web browser (for example) will directly open a connection to TCP port 80 on the Web server, send a request for a document to the server again on TCP port 80, and recieve the Web page over the corresponding connection. With a proxy server, the broswer will contact the proxy server on its own TCP port (often 3128 or 8000, but this varies widely) and asks the proxy server for the document. The proxy server essentially puts the browser on hold while it, in turn, connects to the “real” Web server on port 80 to request and recieve the desired document. Once that’s complete (or, in more sophisticated settings, while it’s going on), the proxy server gives the requested document back to the browser.

While most people are familiar with Web proxies, proxy server software exists for most well-designed protocols. Indeed, some protocols are essentially always proxied, due to their nature. Here’s a tour of protocols and what it takes to proxy them.

HTTP

HTTP is the protocol spoken by Web servers, and there are lots of HTTP proxy server applications. In terms of sheer numbers, Apache would have to be the most widely spread simply because it’s the most widely deployed Web server. Not every Apache Web server is configured to permit proxying, however—indeed, most Apache Web servers should have proxying disabled (as is the default configuration). I mention Apache mainly because you probably don’t have to look far to find a good HTTP proxy server program.

Personally, I’m fond of Squid. Squid stores the restult of recent queries and sends the browser a local copy where appropriate, thus saving the time and expense of transferring the file again across the (relatively) slow Internet connection. It performs excellently; your Web browsers will feel turbocharged if they go through a nearby, properly-configured Squid process. Squid is highly configurable and includes a number of options to restrict access. You could, for example, ban a range of client computers from accessing selected Web pages during certain hours on certain days.

Advertising in general and on the Web in particular has gotten very obnoxious of late. Junkbuster does an excellent job of keeping advertisements from your browser. It also has options to selectively filter cookies, referrer strings, and other potentially privacy-invading tokens. As an added benefit, the network bandwidth you don’t spend downloading advertisements is available for legitimate traffic. As with Squid, browsers that are configured to use a Junkbuster proxy perform much better.

There’s lots of offensive content on the Web. Since definitions of the term, “offensive,” vary so widely, I personally believe that, say, employees surfing pornography sites need to be dealt with at an administrative, rather than techincal level. Squid and Apache both can be configured to keep excellent logs of surfing habits which can be used to aid in disciplinary action. An employee which cannot be trusted to behave in a professional manner should be fired. Having said that, there are those who want a technical solution. A friend of mine recommends DansGuardian as an effective and versatile tool.

Web proxies can be chained and multiple proxies can be run on the same computer. I typically have my browsers point to a Junkbuster proxy server which in turn points to a Squid proxy server. If you use a restrictive proxy server, be aware of the likely possibility that some legitimate sites will be unreachable or unusable; you should provide some (possibly limited) means for bypassing the restrictive proxies and directly using an unrestricted proxy.

FTP

FTP is one of the oldest protocols on the ’Net, and quite a headache from a security standpoint. Squid works fine as an FTP proxy and can make a fair amount of the pain go away. For those cases of FTP that Squid has trouble dealing with, you really shouldn’t be using FTP at all.

SMTP

SMTP is already a store-and-forward protocol; your SMTP server is already a SMTP proxy server. Make sure your mail clients use a local SMTP server; so long as it’s properly configured, the rest is automatic. In our case, Platnium will not be providing any SMTP services, though; that job will be handled by Titanium.

POP and IMAP

If you need to check remote mail on a POP or IMAP server—a situation generally to be avoided (better to handle mail yourself)—then, rather than let mail clients speak directly to the remote server, you should use something like fetchmail, which fetches remote mail and makes it available for local retrieval. Some consideration should be made as to whether this should be done on Platinum or Titanium in this network.

NNTP

USENET is wonderful. USENET is awful. Both statements are true, depending on which groups you examine. Running a full feed is prohibitive for all but the largest and best connected sites, but you can have the appearance of a full feed with a program such as Leafnode. When a client asks Leafnode to display messages for a group which nobody’s asked to view before, Leafnode instead displays a placeholder message. A day or so later, that group will be fully populated with messages. So long as clients request messages from that group on a semi-regular basis, Leafnode will keep the messages in that group current. Leafnode supports a regular-expression based mechanism for determing which articles should not be downloaded; thus, all articles from a particular group, author, etc, could be banned.

DNS

Like SMTP, DNS naturally lends itself to proxying. You probably want to provide your own DNS services; even if you don’t, run a caching DNS server (usually the default configuration for most DNS servers) and have all internal clients use your DNS server. Again, like magic, it should just work—and, again, the job here falls to Titanium and Tin rather than Platinum.

Other Protocols

There are lots of protocols in widespread and not-so-widespread use on the ’Net; I’m not going to try to detail all of them. The rest fall into two broad categories: those which can be proxied, and those which can’t. For the former, the only real challenge is finding (or, perhaps, writing) a proxy server which is suitable for your needs. The latter deserves a bit of discussion.

Some protocols, such as telnet, don’t lend themselves easily to transparent proxying. (Yes, there’s SOCKS, but as I’m not fond of SOCKS I’ll refrain from making statements about it which could be unfair.) If you really need to permit telnet access to remote sites, give the person who needs access an account on a computer (perhaps the main proxy server) that has a telnet client and access to the outside world. You’ll be placing a great deal of trust in anybody you let do this, so use judgement. Ideally, you’ll be able to identify a small set of trustworty computers you’ll permit access to, which mitigates the risks somewhat.

And then…there are protocols like H.323, the “Netmeeting” protocol. It’s a real nightmare: not only do you have to allow unrestricted outgoing traffic, but also unrestricted incoming traffic! If you’re going to let H.323 on your network, you can’t have a firewall. To me, that’s simply unacceptable; I will never let H.323 pass through my networks. Kjell Wooding has an excellent analysis that goes into greater detail.

You might also set up sacrifical goats, computers with a (possibly restricted) connection to the outside world but no connection to your network. Logistical problems await you at every turn; if at all possible, just don’t let bad protocols on your network.

Titanium and Tungsten, the Trusted Servers

Many organizations publish information to the world. Most people immediately think of Web servers in this context, but public DNS, SMTP, NNTP, and other servers are also common.

Titanium and Tungsten, our two trusted servers, should be well hardened. Because pretty much anybody can connect to them, you must be vigilant in applying all necessary security fixes, monitoring log files for suspicious activity, performing regular security audits, and generally following all best security practices for these particular computers. Access to anything other than the public areas of the computer should be carefully guarded: a Web designer might be granted file upload priveleges to the Web document directory, but that’s all she sould be permitted to do.

Titanium

Titanium will be a server that nobody really interacts with directly; as a result, no user accounts or other kinds of special privileges need be granted to anybody other than the systems administrators. In particular, it’s running the main public SMTP and DNS servers.

SMTP

If your MX records are properly configured, mail to your site will get delivered to your public SMTP server which can then distribute internally it as is appropriate (with the help of aliases and MX records, as is appropriate). Put some consideration into which SMTP server you run, as it’ll be a prime target for attack. It should have no known vulnerabilities and a good track record. Personally, I like the audited version of Sendmail that ships with OpenBSD, but there are other good possibilities.

It’s worth considering wrapping your MTA in something like Smtpd/Smtpfwdd. Smtpd is a very simple SMTP server that just accepts mail into a spool directory. Smtpfwdd checks the spool directory at regular, short intervals and hands files it finds there to your MTA for further disposition. It runs with no special priveleges and places a barrier between your MTA and potential attackers.

Spam is a big problem these days. If you filter for spam on your public SMTP server, you have the option of dealing with the spam while you still have the open SMTP dialog with the spam relay. Bounces and teergrub tactics are much more likely to be effective at this stage. At the same time, spam filtering introduces another level of complexity at an already potentially vulnerable point; if the general public can interact with your spam filter, you better be darn sure that there aren’t any vulnerabilities in that filter.

DNS

If you’re running your own DNS server, there’s a good chance that you don’t want just anybody to know about all your internal, private machines. The DNS server you run here should just have information on public servers. Your internal clients should not be configured to talk directly to this DNS server; instead, they should talk to your “real” internal DNS server. Note that, just because you’re running a DNS server on a computer doesn’t mean that that computer has to consult its local DNS server. Proper configuration of the resolver can have it using a different DNS server for its own lookups.

Tungsten

Tungsten is the main Web server. Local Web designers probably need considerable access, as many Web sites these days take advantage of complex CGI interfaces and database stores. CGI programs are notorious for security programs; you should therefore take care to audit any CGI programs you run here and make sure your CGI programmers are well-trained about the importance of security.

Uranium and Ununnilium, the Untrusted Computers

You might need to provide network bandwidth to computers over which you have relatively little control. For example, you might provide Web hosting, or there might be an internal project which needs its own server that you—for whatever reason—can’t administer. Maybe you can’t avoid H.323. Maybe they’re running Microsoft softw—ah, sorry ’bout that. These computers must be isolated: they are at a significantly higher risk of compromise, and you don’t want an attacker to leverage the toehold of local access into a greater penetration of your network.

In this example, Uranium is a Web server we’re hosting for somebody else, and Ununnilium is there for those videoconferences the veep insists he can’t live without.

In a bit, I’ll describe how the bridging firewall is configured to protect the rest of the network when one of these goes rogue, but, otherwise, they’re treated the same way as the trusted servers.

The Routing Firewall

Everything on the upstream side of the routing firewall can directly communicate, in one way or another, with computers on the Internet. Nothing on the downstream side of the routing firewall is permitted such access, though they are permitted restricted access to some of the publicly-reachable computers.

To help partition off the protected computers, they’re all given addresses from the space set aside in RFC 1918. Many sites that use RFC 1918 addresses also use NAT, but this network does not. NAT is only necessary if the outside world needs direct access to these computers or vice-versa, and such is not the case. Giving them un-routable but functioning addresses raises the bar for attackers while also reducing the need for expensive public IP addresses.

The routing firewall has a public, routable IP address. The publically-accessible computers, such as the proxy server, route all traffic to the private networks through this address. If VPN service is to be provided, VPN clients will connect to a VPN server that can be accessed at that public address; logically, when authenticated, they will be considered to be attaching to another network interface and filtered as appropriate. Otherwise, nothing on the Internet should be able to contact the routing firewall.

Additionally, if the private networks need to be partitioned from each other, the routing firewall will serve that purpose.

All computers with private IP addresses will use the private IP of the routing firewall’s interface that they’re connected to as their default route.

Tin, Tellurium, and Tantalum: Private Trusted Servers

While there may well be many servers run internally to provide file stores, print spooling, database access, and other services, there are a few services that must be run on readily-accessible private servers.

Tin

Tin is the main infrastructure computer on the internal network. It provides critical services to everybody on the inside.

DNS

One of those servers must run your “real” DNS server; in this example, we’ll give that job to Tin. All computers on your network (except for the untrusted computers) will query this server for all name lookups. It should know about all the names you use on your network.

For those names it can’t resolve—namely, non-local names—it should be configured to ask your public DNS server (and only ask your public DNS server). That server will, in turn, forward the request to better-connected DNS servers and relay the response back.

SMTP

All local mail should get delivered from the public SMTP server to a single internal trusted server. That internal server—again, Tin in our case—should then distribute the mail as is appropriate (which might mean forwarding the mail further or providing POP access). This kind of centralization makes for simpler configuration and further reduces potentially vulnerable targets should the public SMTP server be compromised.

Telluirium and Tantalum

These servers take care of the main day-to-day server tasks, such as serving up files, managing printers, hosting databases, and the like. They’re there for internal use only and have nothing to do with the outside world.

Carbon and Calcium and Copper and…the Workstations

The rest of the computers on the network are regular, ordinary workstations that do whatever it is that you need them to do. They don’t speak even when spoken to, but they do do a lot of talking all on their own.

Putting It All Together…

It’s now time to come back to the glue that holds everything together: the firewalls. The proxy server also deserves further examination in a bit.

Policies for the Bridging Firewall

As mentioned earlier, the starting point for the briding firewall is to block everything. Only traffic which is explicitely approved may pass. The mechanics of specifing such rules vary widely from packet filter to packet filter. If you’re lucky, you’re using pf, the packet filter included with OpenBSD from version 3.0 on. You’re lucky in part because it’s an excellent packet filter with a clear and concise rule definition language, and also because I include a sample ruleset in Appendix I.

The proxy server—and only the proxy server—is given unrestricted access to the outside world. It can talk to anybody it pleases in any manner it pleases. Later, we’ll discuss how the proxy server itself controls access to the outside world.

The untrusted computers are given very carefully considered restricted access to the outside world, depending on exactly what they need to do. If somebody manages to take control of one of these computers, they shouldn’t be able to use it as a platform to launch attacks on others. I generally only allow them to talk to the upstream provider’s DNS servers on TCP and UDP port 53 because I don’t want them to have any connection at all to the rest of the network, DNS included, and because there’s little chance that there’ll be anything other than a DNS server running on the upstream provider’s DNS servers.

The trusted servers are not permitted to initiate connections to the outside world. If such access is needed, they must be configured to use the proxy server.

Incoming connections are restricted to only those services that are known to be running on a particular server. If Titanium, for example, is running a Web server and nothing else, then only incoming connections on TCP ports 80 and 443 are permitted to IP 192.0.2.2. Should some other service on some other port be accidentally enabled—or if it’s desired to provide that service to internal but not external clients—then it remains unreachable to the outside world. This policy holds true for trusted and untrusted computers alike. Ideally, the proxy server should be unreachable by the outside world.

Again, the untrusted computers are explicitely barred from talking to the rest of the internal network.

Policies for the Proxy Server

The proxy server is granted unrestricted access to the world; in turn, it bears the responsibility for deciding what is and what isn’t legitimate access. How this is done depends largely on the proxy server software. If you decide, for example, that only a select few computers should be able to surf the Web, then you would configure the HTTP proxy server to deny requests from everybody else. If you don’t want anybody to read USENET, then you simply wouldn’t run an NNTP proxy server at all.

The proxy server might be given free reign itself, but, so long as it remains under careful observation and control, it also serves as a choke point for everything that wants to talk to the outside.

Policies for the Routing Firewall

The routing firewall also starts with a default deny policy. The only incoming queries that are permitted are SMTP from the public SMTP server to the private SMTP server, and DNS from anybody on the public network. It might be appropriate to permit SSH or a similar remote administration protocol from a computer running a publicly-accessible SSH server to a well-trusted SSH server on a private network.

It would be reasonable to permit unrestricted outgoing traffic; otherwise, you would let the internal SMTP and DNS servers talk to the public SMTP and DNS servers and let everybody talk only to the proxy server on proxy server ports.

Similarly, it is reasonable to allow all traffic between private networks. If you need to restrict such access for administrative or other reasons, the routing firewall would be appropriate for enforcing such policies. For example, you might have a rule that prevents computers in one network from talking to a particular server in another network.

…and Taking It All Apart Again

I can’t stress enough that security is a process, not a product. The network I described here should make a good starting point for your plans when designing your own network, but it’s just a starting point. You’ll have different needs from what I describe. But, whatever your final network design, that’s exactly what it shouldn’t be: final. Be sure to review it periodically, pick it apart, have somebody else come in and look at it with fresh eyes, and question everything.

And try to not lose sleep over it. Design the network carefully in the first place—in a manner that doesn’t involve sleep deprivation so you avoid critical mistrakes—so you don’t lose sleep later worrying about all your vulnerabilities.

Appendix I: pf.conf for the Bridging Firewall

A bit of caution: since this is a ruleset for a non-existant firewall on a non-existant network, I can’t test it as thoroughly as I do real rulesets. It’s closely modeled on the ruleset I use for my bridging fiewall in my home network, so errors shouldn’t be too glaring. I’ve also stripped out all “log” directives, as what should and shouldn’t get logged is a personal decision that depends on how much time the administrator is willing to review the logs.

##################################################################
# bridge pf ruleset                                              #
##################################################################

##################################################################
# Variables
#
#       This is where  most (but not all) of  the customization is
#       done.

# The name of the interface connected to the big, bad Internet

Internet_IF = "if0"

# The name  of the interface  connected to the  protected internal
# network

Trusted_IF = "if1"

# The name  of the interface  connected to the  untrusted internal
# network

Untrusted_IF = "if2"

# The local network of publically-accessible IPs

Local_Network = "192.0.2.0/24"

# Our default router

Gateway = "192.0.2.255/32"

# The IP of the proxy server

Proxy_Server = "192.0.2.1"

# Various hosts

Platinum = "192.0.2.1/32"
Titanium = "192.0.2.2/32"
Tungsten = "192.0.2.3/32"
Uranium = "192.0.2.65/32"
Ununnilium = "192.0.2.66/32"

# The IP of the inner, routing, non-bridging firewall

Inner_Firewall = "192.0.2.254/32"

# Our ISP’s DNS servers

ISP_DNS = "{ 10.0.1.11, 10.0.5.31, 10.0.15.13 }"

# Martians are  those whose addresses simply  can’t be reached. At
# any given point  in time, IANA might have a  lot more than these
# reserved; the paranoid  will add them here…and spend  a lot of
# time keeping  them current.  Be  sure to properly deal  with the
# RFC 1918 addresses if you’re  actually using them. Note that the
# 192.0.2  network  is  reserved  for  documentation  purposes. By
# including  it in  the  Maritians definition  below, this  entire
# setup  becomes non-functional. In  any real  setup, however,  it
# should definitely  be included  as nobody  actually has  or ever
# will have IPs from that network.

Martians = "{ 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8, \
              172.16.0.0/12, 169.254.0.0/16, 192.0.2.0/24, \
              192.168.0.0/16, 224.0.0.0/3 }"

##################################################################
# Router
#
#       Permit the  proxy to  ping the gateway.  This must be done
#       before the anti-spoofing.

pass out quick \
        on $Internet_IF \
        inet proto icmp \
        from $Proxy_Server \
        to $Gateway \
        icmp-type echoreq \
        code 0 \
        keep state

##################################################################
# Spoofs
#
#       Block those who’ve  (intentionally or otherwise) attempted
#       to steal our IP address(es).

block in  quick on $Internet_IF from $Local_Network to any
block out quick on $Internet_IF from any to $Local_Network

block in  quick on $Internet_IF from any to ! $Local_Network
block out quick on $Internet_IF from ! $Local_Network to any

##################################################################
# Martians
#
#       Don’t permit access from or to unroutable addresses.

block in  quick from $Martians to any
block out quick from any to $Martians

##################################################################
# Broadcasts
#
#       Broadcast messages from the outside are often noise.

block in quick on $Internet_IF from any to 255.255.255.255

##################################################################
# The internal network
#
#       Explicitely  block everything  to  and  from the  internal
#       firewall  except for  IPSEC.  Since everything  from there
#       needs to be proxied,  those packets should  never hit this
#       computer.

pass out quick \
        on $Trusted_IF \
        proto udp \
        from ! $Local_Network \
        to $Inner_Firewall \
        port isakmp \
        keep state

pass out quick \
        on $Trusted_IF \
        proto { ah, esp } \ 
        from ! $Local_Network \
        to $Inner_Firewall \
        keep state

block in  quick from any to $Inner_Firewall
block out quick from $Inner_Firewall to any

##################################################################
# Pick the interface to filter
#
#       pf sees packets  on the bridge twice, in  on one interface
#       and out  on the  other. It makes  sense to  let everything
#       through on one  interface and do all the  filtering on the
#       other. Naturally,  this is  done after  filtering out  the
#       line noise above.

scrub in on $Internet_IF all  
pass in quick on $Internet_IF all
pass out quick on $Internet_IF all

##################################################################
# The proxy server 
#
#       Everything  is  fair  game for  the  proxy  server. Access
#       restrictions will be taken care of on the server, itself.

pass in quick \
        on $Trusted_IF \
        proto tcp \
        from $Proxy_Server \
        to any \
        flags S/SA \
        modulate state

pass in quick \ 
        on $Trusted_IF \
        proto udp \
        from $Proxy_Server \
        to any \
        keep state

pass in quick \
        on $Trusted_IF \
        inet proto icmp \
        from $Proxy_Server \
        to any \
        icmp-type echoreq \
        code 0 \
        keep state

##################################################################
# The untrusted network
#
#       Permit  *very*   restricted  access  to  and   from  these
#       hosts. Yes, they’re crippled—that’s the intent.

# First, we gotta make sure that the proxy server can contact ’em.

pass out quick \
        on $Untrusted_IF \ 
        from $Proxy_Server \
        to any \
        keep state

# Permit  anybody  to access  selected  services  hosted on  these
# computers…

pass out quick \
        on $Untrusted_IF \
        proto tcp \
        from any \
        to { $Uranium } \
        port { ftp-data, ftp, ssh, www, https } \
        flags S/SA \
        keep state

# …and we’ll  let these  hosts access  selected services  on the
# outside…

# DNS to the ISP
pass in quick \
        on $Untrusted_IF \
        proto tcp \
        from { $Uranium, $Ununnilium } \
        to $ISP_DNS \
        port domain \
        flags S/SA \
        keep state

pass in quick \
        on $Untrusted_IF \
        proto udp \
        from { $Uranium, $Ununnilium } \
        to $ISP_DNS \
        port domain \
        keep state

# Dang H.323!

pass in quick \
        on $Untrusted_IF \
        from $Ununnilium \
        to any \
        keep state

pass out quick \
        on $Untrusted_IF \
        from $Ununnilium \
        to any \
        keep state


# …and that’s all, folks!

block return-icmp out quick on $Untrusted_IF proto udp all
block return-icmp in  quick on $Untrusted_IF proto udp all
block return-rst  out quick on $Untrusted_IF proto tcp all
block return-rst  in  quick on $Untrusted_IF proto tcp all
block             out quick on $Untrusted_IF           all
block             in  quick on $Untrusted_IF           all

##################################################################
# Public servers on the trusted network
#
#       Permit  incoming packets  to  selected  hosts on  selected
#       ports.

pass out quick \
        on $Trusted_IF \
        proto tcp \
        from any \
        to { $Tungsten } \
        port { www, https } \
        flags S/SA \
        keep state

pass out quick \
        on $Trusted_IF \
        proto tcp \
        from any \
        to $Titanium \
        port { ssh, smtp, domain } \
        flags S/SA \
        keep state

pass out quick \
        on $Trusted_IF \
        proto udp \
        from any \
        to $Titanium \
        port { domain } \
        keep state

##################################################################
# Default deny
#
#       Everything  else is  blocked unless  explicitely permitted
#       above. Or, in other words, it’s prefereable to have broken
#       services than open ports.

block return-icmp out quick proto udp all
block return-icmp in  quick proto udp all
block return-rst  out quick proto tcp all
block return-rst  in  quick proto tcp all
block             out quick           all
block             in  quick           all

Appendix II: pf.conf for the Routing Firewall

The caveats for the ruleset for the briding firewall applies here, too.

##################################################################
# routing firewall pf ruleset                                    #
##################################################################

##################################################################
# Variables
#
#       This is where  most (but not all) of  the configuration is
#       done.

# The name of the interface connected to the DMZ

Outside_IF = "if0"

# IP address of same

Outside_IP = "192.0.2.254/32"

# The local network of publically-accessible IPs

Public_Network = "192.0.2.0/24"

# Our private, especially protected IPs

Private_Network = "169.254.0.0/16"

# The name of the interface  connected to the especially protected
# internal network

Inside_IF1 = "if1"

# The name of the second internal interface

Inside_IF2 = "if2"

# The IP of the proxy server

Proxy_Server = "192.0.2.1/32"

# Various hosts

Platinum = "192.0.2.1/32"
Titanium = "192.0.2.2/32" 
Tungsten = "192.0.2.3/32"

Tin = "169.254.0.1/32"

# These hosts are untrusted under any circumstances.

Untrusted_Hosts = "{ 192.0.2.65, 192.0.2.66 }"

##################################################################
# Untrusted network
#
#       Block  the  untrusted hosts  from  doing  anything on  the
#       inside.

block in quick on $Outside_IF from $Untrusted_Hosts to any

##################################################################
# Spoofs
#
#       Block those who’ve  (intentionally or otherwise) attempted
#       to steal our IP address(es).

# The outside interface  should only have traffic to  and from the
# public and private networks.

block in  quick on $Outside_IF from ! $Public_Network to any
block out quick on $Outside_IF from any to ! $Public_Network
block in  quick on $Outside_IF from any to ! $Private_Network
block out quick on $Outside_IF from ! $Private_Network to any

##################################################################
# loopback
# 
#       Let programs on the local computer talk to each other.

pass in quick on lo0 \
        from 127.0.0.0/8 \
        to 127.0.0.0/8 \
        keep state

pass out quick on lo0 \
        from 127.0.0.0/8 \
        to 127.0.0.0/8 \
        keep state

##################################################################
# Pick the interface
#
#       Make  all  the  policy  decisions  on  the  least  trusted
#       interface;  this  means   passing  everything  we  haven’t
#       already blocked  on the  other interfaces. This  also lets
#       the inside networks talk to each other un-restricted.

pass in  quick on { $Inside_IF1, $Inside_IF2 } \
        from $Private_Network to any
pass out quick on { $Inside_IF1, $Inside_IF2 } \
        from any to $Private_Network

##################################################################
# The goods
#
#       Pass selected  services from selected clients  to selected
#       hosts.

# IPSEC and friends
pass in quick \
        on $Outside_IF \
        proto udp \
        from any \
        to $Outside_IP \
        port isakmp \
        keep state
pass in quick \
        on $Outside_IF \
        proto { esp, ah } \
        all \
        keep state

# Ping, any internal to any external
pass out quick \
        on $Outside_IF \
        inet proto icmp \
        from $Private_Network \
        to $Public_Network \
        icmp-type echoreq \
        code 0 \
        keep state

# SSH, Platinum to Tin
pass in quick \
        on $Outside_IF \
        proto tcp \
        from $Platinum \
        to $Tin \
        port ssh \
        flags S/SA \
        keep state

# SSH, Private Network to anywhere
pass out quick \
        on $Outside_IF \
        proto tcp \
        from $Private_Network \
        to any \
        port ssh \
        flags S/SA \
        keep state

# Email, Titanium to Tin
pass in quick \
        on $Outside_IF \
        proto tcp \
        from $Titanium \
        to $Tin \
        port smtp \
        flags S/SA \
        keep state

# Email, internal network to Titanium
pass out quick \
        on $Outside_IF \
        proto tcp \
        from $Private_Network \ 
        to $Titanium \
        port smtp \
        flags S/SA \
        keep state

# DNS, DMZ to Tin
pass in quick \
        on $Outside_IF \
        proto tcp \
        from $Public_Network \
        to $Tin \
        port domain \
        flags S/SA \
        keep state
pass in quick \
        on $Outside_IF \
        proto udp \
        from $Public_Network \
        to $Tin \ 
        port domain \
        keep state

# DNS, Tin to Titanium
pass out quick \
        on $Outside_IF \
        proto tcp \
        from $Tin \
        to $Titanium \
        port domain \
        flags S/SA \ 
        keep state
pass out quick \
        on $Outside_IF \
        proto udp \
        from $Tin \
        to $Titanium \
        port domain \
        keep state

# Proxy servers (Squid, Junkbuster)
pass out quick \
        on $Outside_IF \
        proto tcp \
        from $Private_Network \
        to $Proxy_Server \
        port { 3128, 8000 } \
        flags S/SA \
        keep state

##################################################################
# Default deny 
#
#       Everything  else is  blocked unless  explicitely permitted
#       above. Or, in other words, it’s prefereable to have broken
#       services than open ports.

block return-icmp out quick proto udp all
block return-icmp in  quick proto udp all
block return-rst  out quick proto tcp all
block return-rst  in  quick proto tcp all
block             out quick           all
block             in  quick           all