[Previous: Firewall Redundancy with CARP and pfsync] [Contents]
[ COMP1 ] [ COMP3 ] | | ---+------+-----+------- xl0 [ OpenBSD ] fxp0 -------- ( 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., except for COMP3 which is also running a small web server. The internal network is using the 192.168.0.0 / 255.255.255.0 network block.
The OpenBSD firewall is a Celeron 300 with two network cards: a 3com 3c905B (xl0) and an Intel EtherExpress Pro/100 (fxp0). The firewall has a cable 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.
ext_if="fxp0"
int_if="xl0"
tcp_services="{ 22, 113 }"
icmp_types="echoreq"
comp3="192.168.0.3"
The first two lines define the network interfaces that filtering will happen on. By definining them here, if we have to move this system to another machine with different hardware, we can change only those two lines, and the rest of the rule set will be still usable. 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 accepted at the firewall machine. Finally, the last line defines the IP address of COMP3.
Note: If the Internet connection required PPPoE, then filtering and NAT would have to take place on the tun0 interface and not on fxp0.
set block-policy return
set loginterface $ext_if
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. On OpenBSD, the loopback interface is lo(4). It is considered best practice to disable all filtering on loopback interfaces. Using set skip will accomplish this.
set skip on loNote that we are skipping the entire interface group lo, this way, should we later add additional loopback interfaces, we won't have to worry about altering this portion of our existing rules file.
scrub in
nat on $ext_if from !($ext_if) to any -> ($ext_if)
The "!($ext_if)" could easily be replaced by a "$int_if" in this case, but if you added multiple internal interfaces, you would have to add additional NAT rules, whereas with this structure, NAT will be handled on all protected interfaces.
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.
As we will want to have the FTP proxy working, we'll put the NAT anchor in, too:
nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"
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 }.
The last redirection rule catches any attempts by someone on the Internet to connect to TCP port 80 on the firewall. Legitimate attempts to access this port will be from users trying to access the network's web server. These connection attempts need to be redirected to COMP3:
rdr on $ext_if proto tcp from any to any port 80 -> $comp3
block in
At this point all traffic attempting to come into an interface will be blocked, even that from the internal network. Later rules will open up the firewall as per the objectives above as well as open up any necessary virtual interfaces.
Keep in mind, pf can block traffic coming into or leaving out of an interface. It can simplify your life if you chose to filter traffic in one direction, rather than trying to keep it straight when filtering some things in, and some things out. In our case, we'll opt to filter the inbound traffic, but once the traffic is permitted into an interface, we won't try to obstruct it leaving, so we will do the following:
pass out keep state
We need to have an anchor for ftp-proxy(8):
anchor "ftp-proxy/*"It is good to use the spoofed address protection:
antispoof quick for { lo $int_if }
Now open the ports used by those network services that will be available to the Internet. First, the traffic that is destined to the firewall itself:
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.
In addition to having an rdr rule which passes the web server traffic to COMP3, we MUST also pass this traffic through the firewall:
pass in on $ext_if inet proto tcp from any to $comp3 port 80 \
flags S/SA synproxy state
For an added bit of safety, we'll make use of the TCP SYN Proxy to further protect the web server.
ICMP traffic needs to 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 many environments.
pass in quick on $int_if
TCP, UDP, and ICMP traffic is permitted to exit the firewall towards the Internet due to the earlier "pass out keep state" line. State information is kept so that the returning packets will be passed back in through the firewall.
# macros ext_if="fxp0" int_if="xl0" tcp_services="{ 22, 113 }" icmp_types="echoreq" comp3="192.168.0.3" # options set block-policy return set loginterface $ext_if set skip on lo # scrub scrub in # nat/rdr nat on $ext_if from !($ext_if) -> ($ext_if:0) nat-anchor "ftp-proxy/*" rdr-anchor "ftp-proxy/*" rdr pass on $int_if proto tcp to port ftp -> 127.0.0.1 port 8021 rdr on $ext_if proto tcp from any to any port 80 -> $comp3 # filter rules block in pass out keep state anchor "ftp-proxy/*" antispoof quick for { lo $int_if } pass in on $ext_if inet proto tcp from any to ($ext_if) \ port $tcp_services flags S/SA keep state pass in on $ext_if inet proto tcp from any to $comp3 port 80 \ flags S/SA synproxy state pass in inet proto icmp all icmp-type $icmp_types keep state pass in quick on $int_if |
[Previous: Firewall Redundancy with CARP and pfsync] [Contents]