# Firewalling rules for OpenBSD PF # Complete configuration file for packet filtering, NAT, and anti-spam # Copyright (C) 2015 by C. Terrell Prude', Jr. # This rulelist is released under the GNU GPL version 3 or, at your option, # any later version, as published by the Free Software Foundation. # BEGIN INTRO # This is my complete "pf.conf" file for firewalling on my network. # This is my actual production config file, with only the IP addresses changed, # to protect the innocent. :-) Just substitute your IP addresses in # for where the aliases are, and you should be good to go. # The setup is a three-interface firewall running OpenBSD, # running OpenBSD, with a DMZ where the email, Web, and DNS servers live. # This configuration is suitable for most enterprises, actually, be they # small or large. The firewalling fundamentals are exactly the same. # This config file is suitable for OpenBSD, versions 4.7 to 5.7 (current # at the time of this writing). For the upcoming 5.8, simply replace # the "rdr-to" with "divert-to", and you should be fine. Something like # "sed 's/rdr-to/divert-to/g' should do the trick nicely. # END OF INTRO # Set up some aliases # This isn't strictly necessary, but it does help with maintanence # later on down the road, so I like to do it. ext_if = "hme0" int_if = "fxp0" dmz_if = "fxp1" internalnet = $int_if:network dmznet = $dmz_if:network emailserver = "10.0.0.101" webserver = "10.0.0.102" dnsserver = "10.0.0.103" dadsbox = "1.2.3.4" # Here are addresses we should never see from the Internet # You can do something fun with this, like redirect any traffic # from the Internet, with these "from" addresses, to a honeynet. Heh heh! # I'll do something with this eventually. :-) table persist {10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/5} # Set up our tables for spamd # The spammers automatically get put in here by spamd. # This is spamd's default, built-in "blacklist", basically, # and it is dynamic. Spamd itself maintains this blacklist. table persist # Spamd puts you in here after your greylist "purgatory" expires # and if you don't send to a greytrap address, # i. e. if you're a "good guy". Like with the "spamd" table above, # the spamd daemon itself maintains this dynamic "whitelist" as well. table persist # This is our custom white/black list section. # Sometimes we need to allow or deny email servers manually. # These can, if you wish, be pre-populated at PF start-up time by reading # from a plain ol' text file. table persist table persist file "/etc/whitelist.txt" # OK, now we set up our NAT and packet-filtering rules # so we can actually be a firewall! # In the strategy used here (and generally my preferred one), # we use a "default deny" for inbound traffic, but a # "default allow" for outbound traffic. For this reason, # we don't need specific "allow" statements for outbound traffic. # The reason I mention this is because some how-to pages, # and some books (e. g. Peter Hansteen's excellent "The Book of PF"), # do implement a "default deny" policy for outbound traffic as well, # and thus you will see "pass out" rules in their pf.conf files. # Neither is "right" or "wrong", but rather, "what does your security # policy require?" For most corporate networks, "default allow" for # outbound traffic is best, I've discovered. # Just so's you're aware. :-) # Another note regarding the general packet filtering strategy used here. # By default, OpenBSD PF uses a rule-matching strategy where you read # all the lines, and then you act on the *last* rule that matches the packet. # This is called, "last match". # Cisco ACL's, Linux Netfilter/Iptables, and several others, by contrast, use # a "first match" strategy. That means the first rule that matches the packet, # you stop right there and act on that rule. Neither is "wrong" or "right", # but rather a personal preference. Since I first learned on ciscos (yes, the # small "c" was intentional) and Linux ipfwadm/ipchains/iptables, I prefer the # "first match" strategy. This is why I use the "quick" keyword in the PF rules # in this config file. Most books about PF use the default "last match" # strategy. So, if you understand cisco ACL's, this config file should make # perfect sense. # OK, now that you know our strategies here, time to actually do some firewalling. # LET'S GET TO IT! :-) # Set up the NAT rules, to let traffic out. match out on $ext_if from $internalnet nat-to ($ext_if) match out on $ext_if from $dmznet nat-to ($ext_if) # Stop the spammers! # Redirect anything on the built-in whitelist (table "spamd-white") straight to the real mail server # Also redirect anything on our custom whitelist (table "whitelist") right to the real mail svr # Everything else...you get to talk to spamd first! # Why don't I "log" on the spamd line? Read the man page. :-) pass in quick log on $ext_if inet proto tcp from to $ext_if port smtp rdr-to $emailserver pass in quick log on $ext_if inet proto tcp from to $ext_if port smtp rdr-to $emailserver pass in quick on $ext_if inet proto tcp to $ext_if port smtp rdr-to 127.0.0.1 port spamd # Allow IMAP traffic from the Internet to the mail server # I happen to like logging IMAP access from the Internet, just 'cuz I'm paranoid # Uncommenting this line will allow IMAP access from anywhere #pass in quick log on $ext_if inet proto tcp from any to $ext_if port imap rdr-to $emailserver # Allow only Dad's computer to do IMAP to the mail server pass in quick log on $ext_if inet proto tcp from $dadsbox to $ext_if port imap rdr-to $emailserver # Allow DNS traffic to the DNS server pass in quick on $ext_if inet proto udp from any to $ext_if port 53 rdr-to $dnsserver pass in quick on $ext_if inet proto tcp from any to $ext_if port 53 rdr-to $dnsserver pass out quick on $dmz_if inet proto udp from any to $dnsserver port 53 pass out quick on $dmz_if inet proto tcp from any to $dnsserver port 53 # Allow Web traffic to the Web server pass in quick on $ext_if inet proto tcp from any to $ext_if port 80 rdr-to $webserver pass out quick on $dmz_if inet proto tcp from any to $webserver port 80 ######################################### # TEMPORARY MOD FOR EMERGENCY TRAVELING # Allow SSH traffic directly to this box, # but only from Dad's box. ######################################### pass in quick on $ext_if inet proto tcp from $dadsbox to $ext_if port 22 # Turn on stateful packet filtering, allowing all outbound from the inside and the DMZ # Note: Ideally, we'd use "modulate state" for everything, but # for some reason, "modulating state" for UDP and ICMP doesn't work, # so we just "keep state" for those. pass out quick on $ext_if inet proto tcp from $internalnet to any modulate state pass out quick on $ext_if inet proto udp from $internalnet to any keep state pass out quick on $ext_if inet proto icmp from $internalnet to any keep state pass out quick on $ext_if inet proto tcp from $dmznet to any modulate state pass out quick on $ext_if inet proto udp from $dmznet to any keep state pass out quick on $ext_if inet proto icmp from $dmznet to any keep state # Also have to explicitly allow the firewall's own traffic to come back in! # And yes, we have to use the "quick" keyword. # This is because we're denying everything from the external interface as our "default deny" policy. # If we don't explicitly allow this traffic, it'll match on the catch-all deny statement. Oops! # That would not be what we'd want, so we do this. pass out quick on $ext_if inet proto tcp from $ext_if to any modulate state pass out quick on $ext_if inet proto udp from $ext_if to any keep state pass out quick on $ext_if inet proto icmp from $ext_if to any keep state # Also allow ICMP echo-requests to the external interface for troubleshooting purposes. # Google and Yahoo allow ping; I can, too. But only echo-request. pass in quick on $ext_if inet proto icmp to $ext_if icmp-type echoreq # Deny everything else! block in on $ext_if inet all