It's been a while since my last post, but today I decided to finish a post that I started some time ago, but never finished. It's about a configuration issue with the PF firewall when using NAT for the systems behind the firewall. All the examples you will find on the net define the following rule for NAT-ing internal traffic before it is send to the internet:
# NAT inside -> outside
nat on $ext_if from !($ext_if) to any -> ($ext_if)
This may a look a bit cryptic at first sight, but this basically performs NAT on the external interface for packets which have an ip source address, which is not equal (exclamation mark) to the dynamic ip address (denoted by the parentheses) of the external interface. This rule only performs the NAT and does not let the network packets actually flow through the firewall. We need two more rules. One to let the traffic in on the internal interface and one to let the traffic go out on the external interface:
# allow internal lan to access the Internet
pass quick from $int_lan to any
# allow packets out
pass out quick on $ext_if
These rules are not very strict and basicly allow all outgoing traphic, but even if we replace them with stricter ones, something odd happens. In the past I have used iptables a lot and there you have the concept of chains, namely INPUT, OUTPUT and FORWARD. The INPUT and OUTPUT chain are for the local machine or firewall and the FORWARD chain along with the PREROUTING and POSTROUTING chains are for the systems behind the firewall and NAT.
PF does not have that concept and the rules above will also allow all outgoing traffic initiated from the firewall itself! This is probably not what we intended here and is caused by the NAT rule that translates the source ip address of the packets that are about to leave the firewall. Since the source ip address is already translated before the last pass out rule is hit, this rules is not able to differentiate local generated traffic from NAT-ed traffic and will allow all traffic to leave the firewall. So is there a way to prevent this? Luckily there is an option with PF to tag a packet, which labels a packet with a tag that can be read by other rules and acted upon. This way we can differentiate traffic that flows through a NAT rule and traffic that does not.
Let me show you how this works in practice. First we tag packets on the NAT rule:
# NAT inside -> outside
nat on $ext_if from !($ext_if) to any tag NAT -> ($ext_if)
Next we pass out only tagged traffic on the external interface:
# allow NAT-ed packets out
pass out quick on $ext_if tagged NAT
Keep an eye out for this pitfall when performing security reviews on PF rulesets, because I have seen many examples on the internet without the tag option.