[OpenBSD]

[Previous: Authpf: User Shell for Authenticating Gateways] [Contents]

PF: Example #1: Firewall for Home or Small Office


Table of Contents


The Scenario

In this example, PF is running on an OpenBSD machine acting as a firewall and NAT gateway for a small network in a home or office. The overall objective is to provide Internet access to the network and to allow limited access to the firewall machine from the Internet. This document will go through a complete ruleset that does just that.

The Network

The network is setup like this:
    
  [ COMP1 ]    [ COMP3 ]
      |            |                               ADSL
   ---+------+-----+------- fxp0 [ OpenBSD ] ep0 -------- ( Internet )
             |
         [ COMP2 ]

There are a number of computers on the internal network; the diagram shows three but the actual number is irrelevant. These computers are regular workstations used for web surfing, email, chatting, etc. The internal network is using the 192.168.0.0 / 255.255.255.0 network block.

The OpenBSD router is a Pentium 100 with two network cards: a 3com 3c509B (ep0) and an Intel EtherExpress Pro/100 (fxp0). The router has an ADSL connection to the Internet and is using NAT to share this connection with the internal network. The IP address on the external interface is dynamically assigned by the Internet Service Provider.

The Objective

The objectives are:

Preparation

This document assumes that the OpenBSD host has been properly configured to act as a router, including verifying IP networking setup, Internet connectivity, and setting net.inet.ip.forwarding to "1".

The Ruleset

The following will step through a ruleset that will accomplish the above goals.

Macros

The following macros are defined to make maintenance and reading of the ruleset easier:
int_if = "fxp0"
ext_if = "ep0"

tcp_services = "{ 22, 113 }"
icmp_types = "echoreq"

priv_nets = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"

The first two lines define the network interfaces that filtering will happen on. The third and fourth lines list the TCP port numbers of the services that will be opened up to the Internet (SSH and ident/auth) and the ICMP packet types that will be permitted to reach the firewall machine. The last line defines the loopback and RFC 1918 address blocks.

Note: If the ADSL Internet connection required PPPoE, then filtering and NAT would have to take place on the tun0 interface and not on ep0.

Options

The following two options will set the default response for block filter rules and turn statistics logging "on" for the external interface:
set block-policy return
set loginterface $ext_if

Scrub

There is no reason not to use the recommended scrubbing of all incoming traffic, so this is a simple one-liner:
scrub in all

Network Address Translation

To perform NAT for the entire internal network the following nat rule is used:
nat on $ext_if from $int_if:network to any -> ($ext_if)

Since the IP address on the external interface is assigned dynamically, parenthesis are placed around the translation interface so that PF will notice when the address changes.

Redirection

The only redirection needed is for ftp-proxy(8) so that FTP clients on the local network can connect to FTP servers on the Internet.
rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021

Note that this rule will only catch FTP connections to port 21. If users regularly connect to FTP servers on other ports, then a list should be used to specify the destination port, for example: from any to any port { 21, 2121 }.

Filter Rules

Now the filter rules. Start with the default deny:
block all

At this point nothing will go through the firewall, not even from the internal network. The following rules will open up the firewall as per the objectives above as well as open up any necessary virtual interfaces.

Every Unix system has a "loopback" interface. It's a virtual network interface that is used by applications to talk to each other inside the system. In general, all traffic should be passed on the loopback interface. On OpenBSD, the loopback interface is lo(4).

pass quick on lo0 all

Next, the RFC 1918 addresses will be blocked from entering or exiting the external interface. These addresses should never appear on the public Internet, and filtering them will ensure that the router does not "leak" these addresses out from the internal network and also block any incoming packets with a source address in one of those networks.

block drop in  quick on $ext_if from $priv_nets to any
block drop out quick on $ext_if from any to $priv_nets

Note that block drop is used to tell PF not to respond with a TCP RST or ICMP Unreachable packet. Since the RFC 1918 addresses don't exist on the Internet, any packets sent to those addresses will never make it there anyways. The quick option is used to tell PF not to bother evaluating the rest of the filter rules if one of the above rules matches; packets to or from the $priv_nets networks will be immediately dropped.

Now open the ports used by those network services that will be available to the Internet:

pass in on $ext_if inet proto tcp from any to ($ext_if) \
   port $tcp_services flags S/SA keep state

Specifying the network ports in the macro $tcp_services makes it simple to open additional services to the Internet by simply editing the macro and reloading the ruleset. UDP services can also be opened up by creating a $udp_services macro and adding a filter rule, similar to the one above, that specifies proto udp.

ICMP traffic must now be passed:

pass in inet proto icmp all icmp-type $icmp_types keep state

Similar to the $tcp_services macro, the $icmp_types macro can easily be edited to change the types of ICMP packets that will be allowed to reach the firewall. Note that this rule applies to all network interfaces.

Now traffic must be passed to and from the internal network. We'll assume that the users on the internal network know what they are doing and aren't going to be causing trouble. This is not necessarily a valid assumption; a much more restrictive ruleset would be appropriate for some environments.

pass in on $int_if from $int_if:network to any keep state

The above rule will permit any internal machine to send packets through the firewall; however, it will not permit the firewall to initiate a connection to an internal machine. Is this a good idea? That depends on some of the finer details of the network setup. If the firewall is also a DHCP server, it may need to "ping" an address to verify its availability before assigning it. Permitting the firewall to connect to the internal network also allows someone who has ssh'ed into the firewall from the Internet to then access machines on the network. Keep in mind that not allowing the firewall to communicate directly to the network is not a large security benefit; if someone gets access to the firewall they can probably alter the filter rules anyways. By adding the following rule, the firewall will be able to initiate connections to the internal network:

pass out on $int_if from any to $int_if:network keep state

Note that if both of these lines are in place, the keep state option is not needed; all packets will be able to pass through the internal interface because there is a rule to pass packets in both directions. However, if the pass out line is not included, the pass in line must include keep state. There is also some performance benefit to keeping state: State tables are checked before rules are evaluated, and if a state match is found, the packet is passed through the firewall without going through ruleset evaluation. This can offer a performance benefit on a heavily loaded firewall, though in a system this simple it is unlikely to generate enough load to matter.

Finally, pass traffic out on the external interface:

pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state

TCP, UDP, and ICMP traffic is permitted to exit the firewall towards the Internet. State information is kept so that the returning packets will be passed in through the firewall.

The Complete Ruleset

# macros
int_if = "fxp0"
ext_if = "ep0"

tcp_services = "{ 22, 113 }"
icmp_types = "echoreq"

priv_nets = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"
	  
# options
set block-policy return
set loginterface $ext_if

# scrub
scrub in all

# nat/rdr
nat on $ext_if from $int_if:network to any -> ($ext_if)
rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 \
   port 8021

# filter rules
block all

pass quick on lo0 all

block drop in  quick on $ext_if from $priv_nets to any
block drop out quick on $ext_if from any to $priv_nets

pass in on $ext_if inet proto tcp from any to ($ext_if) \
   port $tcp_services flags S/SA keep state

pass in inet proto icmp all icmp-type $icmp_types keep state

pass in  on $int_if from $int_if:network to any keep state
pass out on $int_if from any to $int_if:network keep state

pass out on $ext_if proto tcp all modulate state flags S/SA
pass out on $ext_if proto { udp, icmp } all keep state

[Previous: Authpf: User Shell for Authenticating Gateways] [Contents]


[back] www@openbsd.org
$OpenBSD: example1.html,v 1.12 2004/01/01 04:16:17 nick Exp $