PF on OpenBSD: The fast track - Security & Cryptography

Users browsing this thread:
eksith
Long time nixers
Anyone in a rush to configure the firewall for a quick server? I know I was, so what follows is an incredibly ill-advised recommendation ;) Copy the config.

I know I'm not alone in feeling like this, but OpenBSD is, by and large, the simplest OS to setup if you want an all-in-one box for domain and web hosting for yourself. If you're going all out with a lot of scripting or programming tech, then it's probably not the best suited, but for someone creating a small personal server, it's basically ideal. The down side is securing it.

Of course, OpenBSD has a reputation for security, but it must be emphasized that this is for the default install. Any new *thing* you add to your server is another *thing* that can potentially go wrong. I.E. The more services you have running, the more avenues to naughtiness from the Internet. What to do?

Configure the firewall

OpenBSD comes with PF, one of the most venerable pieces of software you'll find on a *nix system. It's been improved and modified since its introduction which effectively makes a lot of tutorials out there somewhat obsolete (a few language changes here and there may break your configurations). Of course, the best resource to setup your PF is the official manual, however it doesn't hurt to have a working config ready for quick deployment. I'll try to explain each bit as much as I can.

This configuration assumes your server is a single box with one network card connected to a switch/router and that your system has already been installed

The PF configuration will reside in /etc/pf.conf . If you don't like to use Vi to edit files, you can install nano, a more user-friendlish editor ;)
Code:
pkg_add nano

Tables are your friends

You rarely want to add an IP into pf.conf directly. Instead, you'll more than likely add it to a table list of IPs you can keep track of as necessary. Maybe you only want to block them for a bit and take them off later. Well, let's create a few lists you may need.

Let's make a folder for these first
Code:
mkdir /etc/pflists
Get into the habit of organizing your lists, configs etc... so you don't drive yourself nuts looking for them later. ;)

Now let's make a few table files for your IPs
Code:
cd /etc/pflists
touch blacklist
touch spammers
touch abuse
You can sorta guess what each list will hold there. "blacklist" Will house IPs that you never want to (or can) serve ever. In lingo, these are called "Martians"; usually spoofed IP addresses that you know are impossible to own or have for a connecting client. You don't want to know them, have contact with them or even let them know you exist.

"spammers" Of course are the unsavory types that may plague your application/site once you get it up and running. These can be manually added to and appended. "abuse" Is basically an automatically filled table. PF can be configured to send abusive clients (those who have way too many connections, download too fast etc...) to this table with no human intervention.

To add an IP or IP range to any of these lists you can invoke the following:
Code:
pfctl -t spammers -T add 218.70.0.0/16
And you can remove it by running
Code:
pfctl -t spammers -T delete 218.70.0.0/16
You can read up more on adding, deleting and showing the list of existing IPs in those tables by checking out the PF tables documentation

Now then, before continuing, backup your configuration
Code:
cp /etc/pf.conf /etc/pf.conf.1
It's always good practice to do this since if you accidentally nuke something, you can always roll it back.

Now begin editing pf.conf
Code:
nano /etc/pf.conf

Get rid of everything and include these lines...
Code:
# External interface
ext        = "nf0"
Substitute *nf0* with the name OpenBSD gave your network interface card.

Code:
# All allowed protocols
allp    = "{ tcp, udp, icmp }"
This is a safe list of protocols that you may be expected to serve. TCP of course is involved in web serving. UDP for domain hosting and ICMP for diagnostics.


Macros are also your friend

The nice thing about PF is the ability to set shortcuts for yourself that mean more. E.G. Let's say you're blocking from a specific list of IPs (like those table files we made). Instead of :
Code:
block in log quick from <blacklist>
block in log quick from <spammers>
block in log quick from <abuse>

We can write ourselves a macro :
Code:
bquick = "block in log quick from "

And then later :
Code:
# Table Rules
$bquick <blacklist>
$bquick <spammers>
$bquick <abuse>

Let's create some of those handy macros so your pf.conf file will look as follows so far :
Code:
# External interface
ext        = "nf0"

# All allowed protocols
allp    = "{ tcp, udp, icmp }"

# Pass macro
pproto    = "pass in log quick on egress inet proto "

# Block macro for tables
bquick    = "block in log quick from "

# Throttling macro
mconn    = " (max-src-conn 100, max-src-conn-rate 15/5, overload <abuse> flush)"

The block/pass macros are shortcuts to specific types of TCP flags (or packet patterns). There are lots of resources online that explain these flags as they pertain to PF and internet security, but for now, I'll leave them as-is. Note the protocol block macro "pproto". The keyword *egress* refers to our single network interface card. PF will figure out that "egress inet proto" means outgoing traffic to the internet using protocols... and we'll include those protocols later.

Connection throttling is a good idea if you're using this server for web use. You don't want too many connections to your machine from the same IP which is a good hint it may be a scraper. Of course, this does *not* mitigate DDoS (Distributed Denial of Service) attacks since... well... they're "distributed" ;) These are launched by many different clients, bots usually, which all have their own IP address. Mitigating DDoS is beyond the purview of this tutorial.

The above throttling macro basically says: The maximum number of connections per IP is 100 with a connection rate of 15 per 5 seconds and if this is exceeded, it's sent to the *abuse* table.

Now, you need to link those table files we created earlier
Code:
# Tables
table <blacklist> persist file "/etc/pflists/blacklist"
table <spammers> persist file "/etc/pflists/spammers"
table <abuse> persist file "/etc/pflists/abuse"
Note, we created those files in the /etc/pflists directory. If you created these files elsewhere, you need to adjust the path.

Now we need to make a few exceptions to the loopback (127.0.0.1) interface as well as prevent or reduce spoofing attacks
Code:
# Loopback skip and anti spoof
set skip on lo0
set block-policy return
antispoof for $ext

If you need to collect statistics on everything (be careful with this), you can optionally add
Code:
set loginterface egress
. This will be necessary if you want to run a tool like pfstat

Now we can get to PFs greatest feature: It's pretty good at cleaning up. There are lots of odd forms of traffic out there; broken packets, odd TCP flag combinations, weird connections etc... PF can be asked nicely to deal with these so you don't have to :
Code:
# Scrub all incoming
match in all scrub
This one, simple, line gets rid of a whole swath of unsavory types of traffic so it's highly recommended you keep it as-is. You can read more about the scrub option in the PF documentation.

The fun begins

It may come as a surprise to you, but we haven't actually set any rules yet ;) What we've done is make a few files for IPs, created a few helper macros and setup some basic configurations. Now we need some rules and let's set one to rule them all :
Code:
# Base Rules ( Do not remove! )
block all

This is the "kill everything first" principle. If you have a party, it makes sense to have a guest list to make sure you only allow certain people to attend (unless you're a teenager throwing a house-party when your parents are out, in which case, you can imagine what a disaster that will end up in). Firewalls should be setup the same way; we can't account for every incoming *thing* so will disallow all *things* and only let in what we absolutely need. With the *block all* rule, we have effectively disconnected our server from everything.

Let's also make sure that we block from those lists:
Code:
# Table Rules
$bquick <blacklist>
$bquick <spammers>
$bquick <abuse>
See how simple everything is with macros? ;)

Now we need to allow two services to come into our server. Domain services (DNS) which uses UDP and web services (http(s)) which uses TCP. Remember the *pproto* macro we created earlier? That's what we'll use along with *mconn* macro to avoid getting flooded.
Code:
# Manual rules
$pproto udp from any to egress port domain
$pproto tcp from any to egress port www flags S/SA synproxy state $mconn
Note how DNS is *domain* and http/s serving is *www*. The flags S/SA only allow SYN and ACK through and *synproxy state* combines SYN proxying (which will really help protect any web servers we run E.G. Apache or Nginx from spoofed SYN TCP floods) and also tells PF to maintain state. This will really help with performance. If you want to learn more, take a peek at the PF documentation on these flags.

Now let's add some diagnostic protocols so we can test for PING and such and let necessary protocols leave the server.
Code:
# Diagnostics
pass in log quick proto icmp

# Log everything leaving this server
pass out log proto $allp
Remember we created an *allp* macro earlier.

And that concludes a good starting point for a basic server. Here's the complete pf.conf file :
Code:
# Web server PF configuration

# External interface
ext        = "nf0"

# All allowed protocols
allp    = "{ tcp, udp, icmp }"

# Pass macro
pproto    = "pass in log quick on egress inet proto "

# Block macro for tables
bquick    = "block in log quick from "

# Throttling macro
mconn    = " (max-src-conn 100, max-src-conn-rate 15/5, overload <abuse> flush)"

# Tables
table <blacklist> persist file "/etc/pflists/blacklist"
table <spammers> persist file "/etc/pflists/spammers"
table <abuse> persist file "/etc/pflists/abuse"

# Loopback skip and anti spoof
set skip on lo0
set block-policy return
antispoof for $ext

set loginterface egress

# Scrub all incoming
match in all scrub

# Base Rules ( Do not remove! )
block all

# Table Rules
$bquick <blacklist>
$bquick <spammers>
$bquick <abuse>

# Manual rules
$pproto udp from any to egress port domain
$pproto tcp from any to egress port www flags S/SA synproxy state $mconn

# Diagnostics
pass in log quick proto icmp

# Log everything leaving this server
pass out log proto $allp

Enjoy!

p.s. Errors, omissions, improvements? Please reply! :)

Edit: Some major corrections. Originally I had written, "connection rate of 15 per 5 minutes" (!) That should be "5 seconds". I guess I didn't have enough coffee :P Also fixed some spelling and grammar issues.
eksith
Long time nixers
Thanks! PF is lazy-people friendly so it's definitely up my alley ;) There are a few changes from different installations because they all use a slightly different syntax. As always, the best resource to start with is the PF man pages for the latest version syntax.
shtols
Long time nixers
pf *taught* me that not all firewall systems are ugly. And that's why I love it. Great post!

@NeoTerra: I highly recommend "The Book of PF".

*EDIT: Grammar.
NeoTerra*
eksith
Long time nixers
Thanks guys! That's an excellent resource, shtols. The latest edition is a treasure trove of information on configuring (basically) a military grade firewall appliance with free software. Can't beat that! :D
shtols
Long time nixers
And thanks for the grammar correction .. I have no idea what my brain was thinking.
Phyrne
Long time nixers
Nice to see a PF post :) Good work eksith.

+1 for The Book of PF, it's a great read.

I'll eventually get around to doing a post about my OpenBSD/PF ALIX firewall/router build :)
hades
Long time nixers
Nice tutorial! I've been thinking of giving one of the BSDs a try on one of my old desktops, and I'll give this tutorial a go as well.
delucks
Members
(31-10-2013, 12:18 PM)Phyrne Wrote: I'll eventually get around to doing a post about my OpenBSD/PF ALIX firewall/router build :)

You should!

Good post, eksith. Helped me a lot to get going.
sodaphish
Long time nixers
one thing I've been looking for -- outside of Snort -- is an open-source application identification engine that could be integrated with any of the various firewall systems available across *NIX. Thoughts?