[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.
int_if="xl0" tcp_services="{ 22, 113 }" icmp_types="echoreq" comp3="192.168.0.3"
The first line defines the internal network interface that filtering will happen on. By defining it here, if we have to move this system to another machine with different hardware, we can change only this line, and the rest of the rule set will be still usable. (For this example, the external interface will be handled by using the egress interface group. This is automatically set on any interface holding a default route, in this case, fxp0). The second and third 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 pppoe0 interface and not on the egress interface (fxp0).
set block-policy return set loginterface egress
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.
Note that we are skipping for all lo interfaces, this way, should we later add additional loopback interfaces, we won't have to worry about altering this portion of our existing rules file.set skip on lo
anchor "ftp-proxy/*"
Now we will add the rule needed to divert FTP connections so they are seen by ftp-proxy(8):
pass in quick on $int_if inet proto tcp to any port ftp \ divert-to 127.0.0.1 port 8021
This rule will intercept FTP connections to port 21 and divert them to an ftp-proxy(8) instance running on port 8021 and, through the use of the quick keyword, matching packets will not be further checked against the rest of the ruleset. If users regularly connect to FTP servers on other ports, then a list should be used to specify the destination port, for example: to any port { 21, 2121 }.
Note that both the anchor and the ftp-proxy(8) divert rule need to be located before any match rules for NAT or the ftp-proxy(8) will not work as expected.
Now we move on to some match rules. By itself, a match rule doesn't determine whether or not a packet is allowed to pass. Instead, packets matching this rule will have the parameters remembered; they will then be used in any pass rules which handle these packets.
This is powerful: parameters such as NAT or queueing can be applied to certain classes of packet, and then access permissions can be defined separately.
To perform NAT for the entire internal network the following match rule is used:
match out on egress inet from !(egress:network) to any nat-to (egress:0)
In this case, the "!(egress:network)" could easily be replaced by a "$int_if:network", 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. The :0 suffix is used so that, if the external interface has multiple addresses, only the first address is used for translation.
Lastly, the protocol family inet (IPv4) is specified. This avoids translating any inet6 (IPv6) packets which may be received.
Now the rules to control access permissions. Start with the default deny:
block in log
At this point all traffic attempting to come into an interface will be blocked, even that from the internal network. These packets will also be logged. 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 choose 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:
By using quick, outbound packets can avoid being checked against the following rules, improving performance.pass out quick
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 egress inet proto tcp from any to (egress) \ port $tcp_services
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.
The next 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:
pass in on egress inet proto tcp to (egress) port 80 rdr-to $comp3
ICMP traffic needs to be passed:
pass in inet proto icmp all icmp-type $icmp_types
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 on $int_if
TCP, UDP, and ICMP traffic is permitted to exit the firewall towards the Internet due to the earlier "pass out" line. State information is kept so that the returning packets will be passed back in through the firewall.
# macros int_if="xl0" tcp_services="{ 22, 113 }" icmp_types="echoreq" comp3="192.168.0.3" # options set block-policy return set loginterface egress set skip on lo # FTP Proxy rules anchor "ftp-proxy/*" pass in quick on $int_if inet proto tcp to any port ftp \ divert-to 127.0.0.1 port 8021 # match rules match out on egress inet from !(egress:network) to any nat-to (egress:0) # filter rules block in log pass out quick antispoof quick for { lo $int_if } pass in on egress inet proto tcp from any to (egress) \ port $tcp_services pass in on egress inet proto tcp to (egress) port 80 rdr-to $comp3 pass in inet proto icmp all icmp-type $icmp_types pass in on $int_if |
[Previous: Firewall Redundancy with CARP and pfsync] [Contents]