Filtering Rules

Filtering rules are the heart of PF. You can use PF without doing any of the fancy redirection, address translation, load balancing, or redundancy, but packet filtering is the bedrock on which most of these features are based. To start with, however, basic packet filtering is defined as access control for network packets by source, destination, protocol, and protocol characteristics.

PF processes filtering rules in order. The last rule that matches a packet is acted on. A typical packet-filtering rule looks like this:

1pass 2in 3on egress 4proto tcp 5from any 6to 192.0.2.12 7port 80

The first word of the filter rule is a keyword that describes the results of this rule 1. PF will either pass or block packets that match a rule. (There’s also match, which we’ll look at in the next chapter.) The rest of the line is a description of matching packets. If the packet matches the description, the rule is applied.

The second statement is the direction the packet is going. Packets are either going in or out. In this rule, the packet is going in 2—it is entering the system. Not only do we define a direction, but we also define an interface group. Packets must be entering this system on an interface in the egress group to match this rule 3.

We then have several statements that define traffic characteristics. (This rule is almost like a regular expression for TCP/IP.) This rule applies to TCP connections 4, coming from any IP address 5, if the connection is made to the IP address 192.0.2.12 6 on port 80 7.

If a packet matches all of these characteristics, it can pass. If any of these characteristics isn’t matched, the packet does not match this rule, and PF continues processing the rules, looking for a matching one.

TCP and UDP rules implicitly check connection state. A TCP packet that matches this rule needs to be a SYN packet, the start of a standard TCP/IP connection. PF uses the state table to manage follow-up packets in the same connection (see Filtering Rules and the State Table).

Default Permit or Default Deny

I touched earlier on the idea of default accept versus default deny. Set this stance at the beginning of your packet-filtering rules with one of the following two statements:

pass
block

The default pf.conf has a default pass stance, but it’s for people who haven’t yet configured a firewall. I recommend starting your filter rules with a lone block statement, and then adding rules to explicitly permit desirable traffic. Remember that the last matching rule wins.

Packet Pattern Matching

One of the most intensive parts of PF is the syntax used to describe packets. Most filter rules describe packets by protocol, port, direction, and other characteristics. PF compares each arriving packet to the state table, and if the packet isn’t part of the state table, it compares the packet to the filter rules. If the rule matches the packet description, the packet is passed or blocked as desired.

Once you define whether you’re in a default accept or default deny stance, the filter rules describe exceptions to your default. So if you block packets by default, most of your filter rules will be pass statements that describe particular desirable connections.

Direction

The keywords in and out describe the direction the packets are going. In many commercial firewalls, the word in means traffic entering the protected network, and out refers to traffic leaving the protected network. OpenBSD does not magically know which side of the network is protected and which is not. As far as PF knows, it’s managing traffic between two interfaces. The keyword in means traffic flowing into the machine from the network, and out means traffic leaving the machine and entering the network.

When you see in or out in a PF rule, do not think about your network as a whole. Instead, imagine that you’re very small and sitting on your CPU, grilling steaks over the heat sink and watching packets enter and leave the computer. You cannot see what lies beyond the case, just the packets as they come and go. Packets coming in are approaching you, and packets going out are receding.

Interface Matching

The on keyword describes an interface or interface group to which this rule applies. You must specify an interface.

If you want a rule to match every interface on the system, use the interface name all. This example stops all traffic entering the machine on the interface fxp0, but allows all traffic leaving the system on the interface group egress:

block in on fxp0
pass out on egress

This ruleset implies that interface fxp0 is special for some reason, so it’s not treated like the rest of the egress group.

Address Families

Rules can apply to specific address families, either inet for IPv4 or inet6 for IPv6. Here’s how to prohibit IPv4 but permit IPv6:

block in on egress inet
pass in on egress inet6

Presumably, you have later rules that more tightly restrict IPv6.

Network Protocol

PF can recognize almost any network protocol by number or name. The proto keyword tells PF to match a protocol. Network protocols can be given by name from /etc/protocols, protocol number, or even a list (see Using Lists).

block in on egress proto tcp
pass in on egress proto udp

You can use this to pass protocols other than IP and IPv6. Here’s how to allow the protocols necessary for IPsec:

pass in on egress proto esp
pass in on egress proto ah

This functionality somewhat overlaps the inet and inet6 statements. If you prefer, you could explicitly allow IP, ICMP, TCP, UDP, and all the various IPv6 protocols.

Source and Destination Address

Almost every filter rule specifies a source and/or destination address.

pass in on egress from 198.51.100.0/24 to 192.0.2.0/24

IP addresses can appear either as individual addresses or as an address with a netmask (as shown in the preceding example). The keyword any means any IP address. The keyword all is shorthand for “from any to any.”

You can also use hostnames instead of IP addresses. pfctl will check the IP address of the host when loading the rules, and insert the actual IP address into the rules.

pass in on egress from www.michaelwlucas.com

If the IP address of the host changes, PF won’t notice until you reload the rules with pfctl. If the hostname cannot be found, the rules won’t parse, and pfctl will not be able to load them. I recommend not using hostnames in filter rules, much as I recommend not wearing medieval plate armor while swimming, but it is an available option.

To say “anything but this address,” use the exclamation point as a negation character.

block in from !192.0.2.0/24

This says “block everything except the addresses 192.0.2.0/24.” That’s not the same as saying “pass 192.0.2.0/24,” but it can help simplify your rules.

You can also use lists, macros, and tables as IP addresses. Lists and macros are discussed later in this chapter, and tables are covered in the next chapter.

Source and Destination Variants

You can use the name of an interface or interface group instead of an IP address.

pass out on egress from egress

This lets traffic leave via the egress interface group, from any IP address on any interface in that group, to any IP address.

If you put the interface name or group in parentheses, PF updates its rules whenever the IP address on the interface changes. This is useful for dial-up connections, or if you add and remove IP addresses from an interface.

pass out on egress from (egress)

You can specify a network that is directly attached to an interface or an interface group by following the name with :network.

pass in on egress from egress:network

Suppose the egress group has only one interface, and that interface has an IP address of 192.0.2.88/25. This rule would translate to the following:

pass in on egress from 192.0.2.0/25

This rule means that any host on the local network to an egress interface can communicate anywhere. When you add another interface to the egress group, the rules automatically update to accommodate the new interface’s network.

To filter on broadcast traffic for an interface or group, use the :broadcast modifier.

block in on egress from egress:broadcast

Again, suppose that the egress group has only one interface, and that interface has an IP address of 192.0.2.88/25. This rule would translate to the following, blocking broadcast traffic on the local subnet:

block in on egress from 192.0.2.127

Use the :peer modifier to indicate the IP address of the far side of a point-to-point link, such as a dial-up connection.

pass in on egress from egress:peer

Here, we completely trust our dial-up provider.

Interface Main Address

To use only the first IP address on an interface, add the :0 modifier with an interface or group name.

pass out on egress from (egress:0)

The egress interface group might have 98 IP addresses scattered across three interfaces, but only one address on each interface is the first address. This host can communicate out through the egress interface group, but only from primary IP addresses. The aliased IP addresses cannot initiate outbound connections.

The problem with the :0 modifier is that the kernel has a very weak idea of what is the “first” address on an interface. The kernel has a list of addresses associated with an interface. The address at the top of this list is the “first” or “main” address at the moment, but this address can change. If this might cause problems, specify an IP address in your rule rather than rely on :0.

You can attach :0 to any of the other interface modifiers, that is, to IP addresses other than the first from the rule. OpenBSD can’t tell if IP addresses on remote machines are aliases or actual IP addresses, but you can prohibit traffic to or from aliases on the local machine.

Note that the first address on an interface is either an IPv4 address or an IPv6 address. If you want to allow the first address of each protocol, specify the address family in the rule.

pass out on egress inet from egress:0
pass out on egress inet6 from egress:0

Otherwise, PF will use only the first address it sees, regardless of address family.

Source and Destination Port

Filter rules can describe TCP and UDP ports.

pass in on egress proto tcp from any to 192.0.2.12 port 80

This example permits access to TCP port 80 on the server 192.0.2.12. Presumably, this is a web server.

You could use a service name from /etc/services instead of a port number, or even use a list (as described later in this chapter). You can also use ranges, as shown in Table 21-1.

Table 21-1. Table 21-1: Port Ranges

Symbol

Meaning

!=

Not equal

<

Less than

>

Greater than

<=

Less than or equal to

>=

Greater than or equal to

><

Range

<>

Inverse range

For example, to specify all ports over 1024, you could use the greater-than operator (>).

pass in on egress proto tcp from any to 192.0.2.12 port > 1024

To specify all ports between 1000 and 2000, excluding both 1000 and 2000, use the range operator (><).

pass in on egress proto tcp from any to 192.0.2.12 port 1000 >< 2000

To include ports 1000 and 2000 in your range, use the inclusive range operator (:). (Note that you cannot have space on either side of the colon.)

pass in on egress proto tcp from any to 192.0.2.12 port 1000:2000

To pass traffic on all ports less than 1000 and greater than 2000, use the inverse range operator (<>).

pass in on egress proto tcp from any to 192.0.2.12 port 1000 <> 2000

Ranges let you express large numbers of ports in very few rules.

A Complete Ruleset

The following is a complete ruleset for a desktop machine, using many of the features described previously. We’ll look at some more complicated rulesets later, but this illustrates many basic principles of PF rules.

Interface group egress is attached to the public network, and interface group inside is connected to my private network.

1 set skip on lo
2 block
3 pass in on egress from egress:network
4 pass in on inside from inside:network
5 pass in on egress proto tcp from any to egress:0 port 22
6 pass out all

The first rule disables packet filtering on the loopback interface 1, and the second defines a default deny stance 2. The second and third rules permit all connections from IP addresses directly connected to the external 3 and internal interfaces 4. If I install a web server on my desktop, I want to be able to view it from any machine on the network I control. Then I permit inbound SSH connections from anywhere in the world to the primary IP address on any egress interface 5. Finally, I permit all outbound traffic, so my desktop can freely access the outside world 6.

I’ve said before that PF rules are processed in order, and these rules illustrate that. I establish a default, blocking all traffic, and then use individual rules to carve out exceptions to that global block.

Activating Rules

For your PF rules to take effect, you must load them into the kernel using pfctl -f.

# pfctl -f /etc/pf.conf

First, pfctl reads and parses the rules file. If the file parses correctly, pfctl expands any variables in the file, performs any necessary DNS lookups to transform hostnames into IP addresses, and feeds the complete rules into the kernel. The kernel reads the new rules, and then swaps between the old and new rules in one operation. At no time are the packet-filtering rules missing, scrambled, or a hybrid of the two rulesets. Also note that pfctl -f won’t enable PF if it’s disabled.

Personally, I like to know that my edited packet-filter configuration parses before the scheduled change time. It’s embarrassing to announce to your team that “the new firewall configuration will be active at noon” and spend the whole time tracking down a misplaced comma or a parenthesis where you should have put in a curly brace. To test your syntax without installing the rules, use the -n flag with -f. Add -v for verbose mode, to see how pfctl expands your macros, groups, and so on.

# pfctl -nvf /etc/pf.conf

The rules might still have errors, but only errors of comprehension rather than syntax.

Loading new rules doesn’t remove any existing open connections or state entries. If my old ruleset allowed outbound SSH connections, and I remove that permission from the newly installed rules, existing SSH connections remain open. I can either specifically kill those connections with pfctl -k or flush the state table.

Viewing Active Rules

To see how these rules are interpreted inside PF, view the currently installed ruleset with pfctl -s rules. Here are the rules generated by the configuration in A Complete Ruleset:

  # pfctl -s rules
1 block drop all
2 pass in on egress inet6 from 2001:db8:4::/64 flags S/SA
3 pass in on egress inet from 192.0.2.0/28 flags S/SA
4 pass in on inside inet from 192.168.1.0/24 flags S/SA
5 pass in on egress inet proto tcp to 192.0.2.5 port = 22 flags S/SA
6 pass out all flags S/SA

The first rule establishes a default deny stance 1. I then specifically allow connections from hosts on the networks local to interfaces in the egress group, for both IPv6 2 and IPv4 3. This desktop also accepts connections from my private network 4.

The private network permits connections only from IPv4 addresses because the interface in the private group has only an IPv4 address. (I really should add an IPv6 address, but it hasn’t caused me any trouble, so I’ll probably forget all about it once again.) Then there’s a rule permitting inbound SSH traffic 5, followed by a final rule to pass all outbound traffic 6.

If I change any IP address on my desktop, my firewall rules update to accommodate them. That’s a really nice feature of interface groups. If I moved my desktop regularly, I would put the interface group names in parentheses so PF would watch for IP address changes.

Note

One thing you’ll probably notice is that the pass rules end with flags S/SA. This means that out of the SYN and ACK flags, matching packets can have only the SYN flag set, indicating that these are requests to establish a connection. You can filter on TCP flags, but doing so requires in-depth understanding of TCP, and most people should never do it. To see how SYN and SYN+ACK packets affect connections, you need to understand the state table.

To see how often a packet triggers each rule, add -v to the pfctl command.

To see how the rules impact traffic in a constantly updating display, run systat rules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.178.181