A web server and a mail server on the inside

Time passes, and needs change. Rather frequently, a need to run externally accessible services develops. This quite frequently becomes just a little harder because externally visible addresses are either not available or too expensive, and running several other services on a machine which is primarily a firewall is not a desirable option.

The redirection mechanisms in PF makes it relatively easy to keep servers on the inside. If we assume that we need to run a web server which serves up data in clear text (http) and encrypted (https) and in addition we want a mail server which sends and receives e-mail while letting clients inside and outside the local network use a number of well known submission and retrieval protocols, the following lines may be all that's needed in addition to the rule set we developed earlier:

webserver = "192.168.2.7"
webports = "{ http, https }"
emailserver = "192.168.2.5"
email = "{ smtp, pop3, imap, imap3, imaps, pop3s }"

rdr on $ext_if proto tcp from any to $ext_if port \
       $webports -> $webserver
rdr on $ext_if proto tcp from any to $ext_if port \
       $email -> $emailserver

pass proto tcp from any to $webserver port $webports \
   flags S/SA synproxy state
pass proto tcp from any to $emailserver port $email \
   flags S/SA synproxy state
pass proto tcp from $emailserver to any port smtp \
   flags S/SA synproxy state

Notice the flag 'synproxy' in the new rules. This means that PF will handle the connection setup (three way handshake) on behalf of your server or client before handing the connection over to the application. This provides a certain amount of protection against certain types of attacks.

Rule sets for configurations with DMZ networks isolated behind separate network interfaces and in some cases services running on alternative ports will not necessarily be much different from this one.

Taking care of your own - the inside

Everything I've said so far is excellent and correct as long as all you are interested in is getting traffic from hosts outside your local net to reach your servers.

If you want the hosts in your local net to be able to use the services on these machines, you will soon see that the traffic originating in your local network most likely never reaches the external interface. The external interface is where all the redirection and translation happens, and consequently the redirections do not quite work from the inside. The problem is common enough that the PF documentation lists four different solutions to the problem.[1]

The options listed in the PF user guide are

We need to intercept the network packets originating in the local network and handle those connections correctly, making sure any returning traffic is directed to the communication partner who actually originated the connection.

Returning to our previous example, we achieve this by adding these special case rules:

rdr on $int_if proto tcp from $localnet to $ext_if \
       port $webports -> $webserver
rdr on $int_if proto tcp from $localnet to $ext_if \
       port $email -> $emailserver
no nat on $int_if proto tcp from $int_if to $localnet
nat on $int_if proto tcp from $localnet to $webserver \
       port $webports -> $int_if 
nat on $int_if proto tcp from $localnet to $emailserver \
       port $email -> $int_if 

It is well worth noting that we do not need to touch the pass rules at all.

I've had the good fortune to witness via email or IRC the reactions of several network admins at the point when the truth about this five line reconfiguration sank in.

Notes

[1]

See Redirection and Reflection in the PF user guide.