The Firewall (pf)

The firewall? Already ?

Absolutely. You want to keep your fresh system safe, don't you?

We'll start with simple examples and suggest a few more for thoses interested.

On OpenBSD, the firewall is pf as in "packet filter". It is configured in "/etc/pf.conf". You will see, it's syntax is much easier than Linux's iptables.

First, you have to ask yourself what do you need. A web server (http)? Mail server? Depending on your answers, you won't open the same port number. We actually will close every port except the ones you really need.

To find the ports on which services listen, look at the contents of the "/etc/services" file. Thus, writing "www" or "80" is the same for pf, but is more readable for humans.

The rules you will define will be applied in the order they are written.

A first example

Ready? Let's go for a detailed but simple example. We are going to configure the firewall for a website with access on ports 80 (www) and 443 (https).

We start by blocking everything, and recording in the log the prohibited connections with the word "log" (obvious 😀).

block log

To make our life easier, we are going to put the ports to be opened in a variable. This will be particularly practical when there are more ports to manage.

tcp_pass = "{ 80 443 }"

Note that this is identical to 'tcp_pass = "{ www https }"'

Then, we allow potential visitors to access your server. These will be so-called "incoming" connections, so we will use the "in" keyword. Instead of looking for the name of your network interface (which depends on your hardware), we will specify the name of the group that these interfaces belongs to so that it works without worry: "egress".

pass in quick on egress proto tcp to port $tcp_pass

You're cute eh, but that's all gibberish!

Okay, we'll explain what this syntax means. We can translate the previous configuration line as follows: "let pass inward (pass in) without taking into account the rules that could follow (quick) through the egress interface (on egress) for the tcp protocol (proto tcp) to the ports in $tcp_pass (to port $tcp_pass).

Fiew! 😋

Finally, we allow all the output traffic (keyword "out"):

pass out on egress

Finally :

tcp_pass = "{80,443}"
block log
pass in quick on egress proto tcp port $tcp_pass
pass out on egress all

Easy, right? 😁

I suggest that you replace the last line with something a little more restrictive:

pass out on egress proto {tcp udp icmp ipv6-icmp} modulate state

We simply specify the protocols authorized on exit with a precaution for each outgoing connection (modulate state).

You will understand, pf allows us a first order control on our server. At first, you'll just want to choose which ports should be opened.

This makes a lot of new concepts, take the time to reread what has been written so far if necessary.

When you are more comfortable, know that we can filter incoming and outgoing traffic, and even redirect part of it to internal services (eg anti-spam). I invite you to read the part "going-further-with-pf". You will see how to ignore certain lists of IPs known as harmful that will be recorded in tables, rules against bruteforce attacks or even a proposed tool that adds potential hackers to the blacklist in real time.

I let you build your /etc/pf.conf file according to your needs. Start from the example seen above then add the ports to open. Do not hesitate to consult the example provided at the end of this document.

To be sure, can we have an example to open the SSH port?

If the configured SSH port is 22, then you will have in the pf configuration:

pass in quick on egress proto tcp to port 22

Of course, you can also just change the tcp_pass variable:

tcp_pass = "{80 443 22}"
pass in ...

Convenient commands for pf

Find below suggestions of useful commands to manage pf:

Go further with pf

pf-badhost: IP blacklist

Some IPs are known to be harmful. Between those that scan for an open port or those that attempt bruteforce attacks, the list goes on. Fortunately, we can configure the firewall so that it completely ignores these IPs and thus protect and lighten the load on our server.

This is what allows the pf-badhost project.

Author's blacklists

I keep up to date lists of IP who recentrly tried bad stuff on my server.

feel free to use them! 😉


One method hackers use to attack a server is called "bruteforce". Suck attacks consist of trying all possible username / password combinations until they find the correct one.

If you choosed strong passphrases, you should be safe. However, these attacks abuse resources on your server and can potentially succeed one day (admit that it would be bad luck).

In order to thwart these attacks, you should take the time to analyze the logs of the services you host (in "/var/log" and "/var/www/logs") for identification failures and notice where these attacks are coming from to banish the source. But let's be honest, it's both painful and time consuming.

Ideally, a tool that monitors logs for you and takes care of blacklisting "attacker" IPs on your firewall. Good thing, you can use vilain or sshguard which are made for that.

In the meantime, pf already integrates a protection which limits the number of connections on a port during a given time. It is very effective and remains light. Read the next paragraph 😉.

Anti-Bruteforce integrated into pf

pf has a very interesting feature: "tables". This will allow us to remember certain IP addresses of hackers who would try to compromise the server. For example, to protect the SSH service, we will proceed as follows:

So that gives us this:

ssh_port = "22"
table <ssh_abuse> persist
block in log quick proto tcp from <ssh_abuse> to any port $ssh_port
pass in on egress proto tcp to any port $ssh_port flags S/SA keep state (max-src-conn-rate 3/60, overload <ssh_abuse> flush global)

Note that we have recorded the port number used for the SSH server. It is useless, because one can put in the place of "$ssh_port" simply "ssh", the name of the service. However, you may want to change the default port of the SSH server, as described in the chapter on this service.

Here is another example for a website (http and https ports):

http_ports = "{www https}" # ports http (s)
# If more than 40 connections every 5 seconds on the http (s) ports
# or if she tries to connect more than 100 times
# we add the ip to block it.
pass in on egress proto tcp to any port $http_ports flags S/SA keep state (max-src-conn 100, max-src-conn-rate 40/5, overload <http_abuse> flush global)

Anti-bruteforce in real time with vilain

I would like to offer you a tool that I wrote with the help of Vincent Delft in order to blacklist possible hackers when the attack takes place.

The script in question is called vilain.

Anti-bruteforce in real time with sshguard

Contrary to what its name suggests, sshguard is able to check connection attempts on multiple services, not just SSH.

Its installation in OpenBSD is not very complicated to set up. You will need to create a table in pf that will contain all the blacklisted IPs. Thus, we add in the /etc/pf.conf file a new table containing the IPs of the attackers who are immediately blocked:

table <sshguard> persist
block in from <sshguard>

Reload the firewall with pfctl -f /etc/pf.conf.

Now, we install sshguard as we usually do:

# pkg_add sshguard

Copy the configuration file given as an example:

# cp /usr/local/share/examples/sshguard/sshguard.conf.sample /etc/sshguard.conf

Edit this file if you want to make it more or less harsh. The default options are quite reasonable.

We can now enable and run sshguard:

# rcctl enable sshguard
# rcctl start sshguard

And there you go, your server is now safer.

You may want to whitelist some IP addresses with the "-w" option. Do this (this option can be used more than once).

rcctl set sshguard flags -w -w

Trap with iblock

solene@ wrote a tool named iblock.

iblock ban every IP trying to access your server on ports that are actually unused.

This is definitely a must-have.